diff --git a/expression/src/main/java/io/smallrye/common/expression/Expression.java b/expression/src/main/java/io/smallrye/common/expression/Expression.java
index 555a04d..bb30c92 100644
--- a/expression/src/main/java/io/smallrye/common/expression/Expression.java
+++ b/expression/src/main/java/io/smallrye/common/expression/Expression.java
@@ -260,6 +260,9 @@ private static Node parseString(Itr itr, final boolean allowExpr, final boolean
}
// check to see if it's a dangling $
if (!itr.hasNext()) {
+ if (flags.contains(Flag.NO_$$)) {
+ continue;
+ }
if (!flags.contains(Flag.LENIENT_SYNTAX)) {
// TP 2
throw invalidExpressionSyntax(itr.getStr(), idx);
@@ -269,16 +272,16 @@ private static Node parseString(Itr itr, final boolean allowExpr, final boolean
start = itr.getNextIdx();
continue;
}
- // enqueue what we have acquired so far
- if (idx > start) {
- // TP 4
- list.add(new LiteralNode(itr.getStr(), start, idx));
- }
// next char should be an expression starter of some sort
idx = itr.getNextIdx();
ch = itr.next();
switch (ch) {
case '{': {
+ // enqueue what we have acquired so far
+ if (idx > start) {
+ // TP 4
+ list.add(new LiteralNode(itr.getStr(), start, idx - 1));
+ }
// ${
boolean general = flags.contains(Flag.GENERAL_EXPANSION) && itr.hasNext() && itr.peekNext() == '{';
// consume double-{
@@ -406,10 +409,19 @@ private static Node parseString(Itr itr, final boolean allowExpr, final boolean
//throw Assert.unreachableCode();
}
case '$': {
+ if (idx > start) {
+ // TP 4
+ list.add(new LiteralNode(itr.getStr(), start, idx - 1));
+ }
// $$
if (flags.contains(Flag.MINI_EXPRS)) {
// TP 13
list.add(new ExpressionNode(false, LiteralNode.DOLLAR, Node.NULL));
+ } else if (flags.contains(Flag.NO_$$)) {
+ list.add(LiteralNode.DOLLAR);
+ if (!itr.hasNext() || itr.peekNext() != '{') {
+ itr.prev();
+ }
} else {
// just resolve $$ to $
// TP 14
@@ -419,6 +431,11 @@ private static Node parseString(Itr itr, final boolean allowExpr, final boolean
continue;
}
case '}': {
+ // enqueue what we have acquired so far
+ if (idx > start) {
+ // TP 4
+ list.add(new LiteralNode(itr.getStr(), start, idx - 1));
+ }
// $}
if (flags.contains(Flag.MINI_EXPRS)) {
// TP 15
@@ -452,6 +469,11 @@ private static Node parseString(Itr itr, final boolean allowExpr, final boolean
//throw Assert.unreachableCode();
}
case ':': {
+ // enqueue what we have acquired so far
+ if (idx > start) {
+ // TP 4
+ list.add(new LiteralNode(itr.getStr(), start, idx - 1));
+ }
// $:
if (flags.contains(Flag.MINI_EXPRS)) {
// $: is an expression
@@ -485,7 +507,22 @@ private static Node parseString(Itr itr, final boolean allowExpr, final boolean
}
//throw Assert.unreachableCode();
}
+ case '\\': {
+ if (flags.contains(Flag.NO_$$)) {
+ if (itr.hasNext() && itr.peekNext() == '$') {
+ list.add(new LiteralNode(itr.getStr(), start, idx));
+ start = itr.getNextIdx();
+ itr.next();
+ continue;
+ }
+ }
+ }
default: {
+ // enqueue what we have acquired so far
+ if (idx > start) {
+ // TP 4
+ list.add(new LiteralNode(itr.getStr(), start, idx - 1));
+ }
// $ followed by anything else
if (flags.contains(Flag.MINI_EXPRS)) {
// TP 25
@@ -493,7 +530,7 @@ private static Node parseString(Itr itr, final boolean allowExpr, final boolean
Node.NULL));
start = itr.getNextIdx();
continue;
- } else if (flags.contains(Flag.LENIENT_SYNTAX)) {
+ } else if (flags.contains(Flag.LENIENT_SYNTAX) || flags.contains(Flag.NO_$$)) {
// TP 26
// just treat it as literal
start = itr.getPrevIdx() - 1; // we can use 1 here because unicode '$' is one char in size
@@ -551,7 +588,22 @@ private static Node parseString(Itr itr, final boolean allowExpr, final boolean
//throw Assert.unreachableCode();
}
case '\\': {
- if (flags.contains(Flag.ESCAPES)) {
+ if (flags.contains(Flag.NO_$$)) {
+ int escape = itr.getNextIdx();
+ if (itr.hasNext() && itr.peekNext() == '$') {
+ itr.next();
+ if (itr.hasNext() && itr.peekNext() == '{') {
+ list.add(new LiteralNode(itr.getStr(), start, escape - 1));
+ list.add(new LiteralNode(itr.getStr(), escape, itr.getNextIdx()));
+ start = itr.getNextIdx();
+ } else if (itr.hasNext() && itr.peekNext() == '$') {
+ list.add(new LiteralNode(itr.getStr(), start, escape));
+ start = itr.getPrevIdx();
+ itr.prev();
+ }
+ }
+ continue;
+ } else if (flags.contains(Flag.ESCAPES)) {
if (idx > start) {
list.add(new LiteralNode(itr.getStr(), start, idx));
start = idx;
@@ -694,6 +746,11 @@ public enum Flag {
* character.
*/
ESCAPES,
+ /**
+ * Escaping $
with $$
or /$
only applies when {
follows
+ * the initial escaped $
.
+ */
+ NO_$$,
/**
* Treat expressions containing a double-colon delimiter as special, encoding the entire content into the key.
*/
diff --git a/expression/src/test/java/io/smallrye/common/expression/ExpressionTestCase.java b/expression/src/test/java/io/smallrye/common/expression/ExpressionTestCase.java
index 53046ca..ee2b93a 100644
--- a/expression/src/test/java/io/smallrye/common/expression/ExpressionTestCase.java
+++ b/expression/src/test/java/io/smallrye/common/expression/ExpressionTestCase.java
@@ -4,6 +4,8 @@
import static io.smallrye.common.expression.Expression.Flag.NO_SMART_BRACES;
import static org.junit.jupiter.api.Assertions.*;
+import java.util.EnumSet;
+
import org.junit.jupiter.api.Test;
import io.smallrye.common.constraint.Assert;
@@ -693,4 +695,110 @@ void expressions() {
b.append(c.getExpandedDefault());
}));
}
+
+ @Test
+ void no$$() {
+ doubleDollarExpressions(EnumSet.of(NO_$$));
+ doubleDollarExpressions(EnumSet.of(NO_$$, ESCAPES));
+
+ assertEquals("", Expression.compile("$a", NO_$$, MINI_EXPRS).evaluate((c, b) -> {
+ assertEquals("a", c.getKey());
+ }));
+ assertEquals("a", Expression.compile("$$a", NO_$$, MINI_EXPRS).evaluate((c, b) -> {
+ assertEquals("$", c.getKey());
+ }));
+ }
+
+ private void doubleDollarExpressions(EnumSet flags) {
+ assertEquals("$", Expression.compile("$", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("$$", Expression.compile("$$", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("\\$", Expression.compile("\\$", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("\\$$", Expression.compile("\\$$", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("$$foo", Expression.compile("$$foo", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("foo$$", Expression.compile("foo$$", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("$$foo", Expression.compile("$$foo", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("foo$$bar", Expression.compile("foo$$bar", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("${foo}", Expression.compile("$${foo}", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("$${foo}", Expression.compile("$$${foo}", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("$${foo}$", Expression.compile("$$${foo}$", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("$${foo}$$", Expression.compile("$$${foo}$$", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("foo${bar}", Expression.compile("foo$${bar}", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("foo$${bar}", Expression.compile("foo$$${bar}", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("foo$$$${bar}", Expression.compile("foo$$$$${bar}", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("foo$$$${bar}$$$baz", Expression.compile("foo$$$$${bar}$$$baz", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("foo$$$$", Expression.compile("foo$$$$", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("${foo:bar}", Expression.compile("$${foo:bar}", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("$${foo:bar}", Expression.compile("$$${foo:bar}", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("${foo:}", Expression.compile("$${foo:${bar}}", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("${foo:${bar}}", Expression.compile("$${foo:$${bar}}", flags).evaluate((c, b) -> {
+ }));
+
+ assertEquals("", Expression.compile("${foo}", flags).evaluate((c, b) -> {
+ assertEquals("foo", c.getKey());
+ }));
+ assertEquals("", Expression.compile("${foo}${bar}", flags).evaluate((c, b) -> {
+ if ("foo".equals(c.getKey()))
+ assertEquals("foo", c.getKey());
+ if ("bar".equals(c.getKey()))
+ assertEquals("bar", c.getKey());
+ }));
+ assertEquals("foobar", Expression.compile("foo${foo}${bar}bar", flags).evaluate((c, b) -> {
+ if ("foo".equals(c.getKey()))
+ assertEquals("foo", c.getKey());
+ if ("bar".equals(c.getKey()))
+ assertEquals("bar", c.getKey());
+ }));
+ assertEquals("foo${foo}bar", Expression.compile("foo$${foo}${bar}bar", flags).evaluate((c, b) -> {
+ if ("bar".equals(c.getKey()))
+ assertEquals("bar", c.getKey());
+ }));
+ assertEquals("foo${foo}bar", Expression.compile("foo$${foo${bar}}bar", flags).evaluate((c, b) -> {
+ if ("bar".equals(c.getKey()))
+ assertEquals("bar", c.getKey());
+ }));
+ assertEquals("", Expression.compile("${}", flags).evaluate((c, b) -> {
+ assertEquals("", c.getKey());
+ }));
+ assertEquals("", Expression.compile("${:}", flags).evaluate((c, b) -> {
+ assertEquals("", c.getKey());
+ }));
+
+ assertEquals("${foo}", Expression.compile("\\${foo}", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("${foo}bar", Expression.compile("\\${foo}bar", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("\\$\\{%s}", Expression.compile("\\$\\{%s}", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("foo${bar}", Expression.compile("foo\\${bar}", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("foo\\${bar}", Expression.compile("foo\\\\${bar}", flags).evaluate((c, b) -> {
+ }));
+
+ assertEquals("foo\\${bar}", Expression.compile("foo\\$${bar}", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("foo$${bar}", Expression.compile("foo$\\${bar}", flags).evaluate((c, b) -> {
+ }));
+ assertEquals("foo$$\\{bar}", Expression.compile("foo$$\\{bar}", flags).evaluate((c, b) -> {
+ }));
+ }
}