Skip to content

Commit 0904d0d

Browse files
committed
Add string extensions quote and reverse
Add `strings.quote` and `reverse` extensions to match Go implementations.
1 parent b92f1f6 commit 0904d0d

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed

extensions/src/main/java/dev/cel/extensions/CelStringExtensions.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,17 @@ public enum Function {
137137
SimpleType.STRING,
138138
SimpleType.STRING)),
139139
CelFunctionBinding.from("string_lower_ascii", String.class, Ascii::toLowerCase)),
140+
QUOTE(
141+
CelFunctionDecl.newFunctionDeclaration(
142+
"strings.quote",
143+
CelOverloadDecl.newGlobalOverload(
144+
"strings_quote",
145+
"Takes the given string and makes it safe to print (without any formatting"
146+
+ " due to escape sequences). If any invalid UTF-8 characters are"
147+
+ " encountered, they are replaced with \\uFFFD.",
148+
SimpleType.STRING,
149+
ImmutableList.of(SimpleType.STRING))),
150+
CelFunctionBinding.from("strings_quote", String.class, CelStringExtensions::quote)),
140151
REPLACE(
141152
CelFunctionDecl.newFunctionDeclaration(
142153
"replace",
@@ -164,6 +175,16 @@ public enum Function {
164175
"string_replace_string_string_int",
165176
ImmutableList.of(String.class, String.class, String.class, Long.class),
166177
CelStringExtensions::replace)),
178+
REVERSE(
179+
CelFunctionDecl.newFunctionDeclaration(
180+
"reverse",
181+
CelOverloadDecl.newMemberOverload(
182+
"string_reverse",
183+
"Returns a new string whose characters are the same as the target string,"
184+
+ " only formatted in reverse order.",
185+
SimpleType.STRING,
186+
SimpleType.STRING)),
187+
CelFunctionBinding.from("string_reverse", String.class, CelStringExtensions::reverse)),
167188
SPLIT(
168189
CelFunctionDecl.newFunctionDeclaration(
169190
"split",
@@ -449,6 +470,57 @@ private static Long lastIndexOf(CelCodePointArray str, CelCodePointArray substr,
449470
return -1L;
450471
}
451472

473+
private static String quote(String s) {
474+
StringBuilder sb = new StringBuilder(s.length() + 2);
475+
sb.append('"');
476+
for (int i = 0; i < s.length(); ) {
477+
int codePoint = s.codePointAt(i);
478+
if (!Character.isValidCodePoint(codePoint)
479+
|| Character.isLowSurrogate(s.charAt(i))
480+
|| (Character.isHighSurrogate(s.charAt(i))
481+
&& (i + 1 >= s.length() || !Character.isLowSurrogate(s.charAt(i + 1))))) {
482+
sb.append('\uFFFD');
483+
i++;
484+
continue;
485+
}
486+
switch (codePoint) {
487+
case '\u0007':
488+
sb.append("\\a");
489+
break;
490+
case '\b':
491+
sb.append("\\b");
492+
break;
493+
case '\f':
494+
sb.append("\\f");
495+
break;
496+
case '\n':
497+
sb.append("\\n");
498+
break;
499+
case '\r':
500+
sb.append("\\r");
501+
break;
502+
case '\t':
503+
sb.append("\\t");
504+
break;
505+
case '\u000B':
506+
sb.append("\\v");
507+
break;
508+
case '\\':
509+
sb.append("\\\\");
510+
break;
511+
case '"':
512+
sb.append("\\\"");
513+
break;
514+
default:
515+
sb.appendCodePoint(codePoint);
516+
break;
517+
}
518+
i += Character.charCount(codePoint);
519+
}
520+
sb.append('"');
521+
return sb.toString();
522+
}
523+
452524
private static String replaceAll(Object[] objects) {
453525
return replace((String) objects[0], (String) objects[1], (String) objects[2], -1);
454526
}
@@ -504,6 +576,10 @@ private static String replace(String text, String searchString, String replaceme
504576
return sb.append(textCpa.slice(start, textCpa.length())).toString();
505577
}
506578

579+
private static String reverse(String s) {
580+
return new StringBuilder(s).reverse().toString();
581+
}
582+
507583
private static List<String> split(String str, String separator) {
508584
return split(str, separator, Integer.MAX_VALUE);
509585
}

extensions/src/test/java/dev/cel/extensions/CelStringExtensionsTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ public void library() {
7070
"lastIndexOf",
7171
"lowerAscii",
7272
"replace",
73+
"reverse",
7374
"split",
75+
"strings.quote",
7476
"substring",
7577
"trim",
7678
"upperAscii");
@@ -1467,6 +1469,58 @@ public void stringExtension_functionSubset_success() throws Exception {
14671469
assertThat(evaluatedResult).isEqualTo(true);
14681470
}
14691471

1472+
@Test
1473+
@TestParameters("{string: 'abcd', expectedResult: 'dcba'}")
1474+
@TestParameters("{string: '', expectedResult: ''}")
1475+
@TestParameters("{string: 'a', expectedResult: 'a'}")
1476+
@TestParameters("{string: 'hello world', expectedResult: 'dlrow olleh'}")
1477+
@TestParameters("{string: 'ab가cd', expectedResult: 'dc가ba'}")
1478+
public void reverse_success(String string, String expectedResult) throws Exception {
1479+
CelAbstractSyntaxTree ast = COMPILER.compile("s.reverse()").getAst();
1480+
CelRuntime.Program program = RUNTIME.createProgram(ast);
1481+
1482+
Object evaluatedResult = program.eval(ImmutableMap.of("s", string));
1483+
1484+
assertThat(evaluatedResult).isEqualTo(expectedResult);
1485+
}
1486+
1487+
@Test
1488+
public void reverse_unicode() throws Exception {
1489+
CelAbstractSyntaxTree ast = COMPILER.compile("s.reverse()").getAst();
1490+
CelRuntime.Program program = RUNTIME.createProgram(ast);
1491+
1492+
Object evaluatedResult = program.eval(ImmutableMap.of("s", "😁😑😦"));
1493+
1494+
assertThat(evaluatedResult).isEqualTo("😦😑😁");
1495+
}
1496+
1497+
@Test
1498+
@TestParameters("{string: 'hello', expectedResult: '\"hello\"'}")
1499+
@TestParameters("{string: '', expectedResult: '\"\"'}")
1500+
@TestParameters("{string: 'contains \\\"quotes\\\"', expectedResult: '\"contains \\\\\\\"quotes\\\\\\\"\"'}")
1501+
public void quote_success(String string, String expectedResult) throws Exception {
1502+
CelAbstractSyntaxTree ast = COMPILER.compile("strings.quote(s)").getAst();
1503+
CelRuntime.Program program = RUNTIME.createProgram(ast);
1504+
1505+
Object evaluatedResult = program.eval(ImmutableMap.of("s", string));
1506+
1507+
assertThat(evaluatedResult).isEqualTo(expectedResult);
1508+
}
1509+
1510+
@Test
1511+
public void quote_escapesSpecialCharacters() throws Exception {
1512+
CelAbstractSyntaxTree ast = COMPILER.compile("strings.quote(s)").getAst();
1513+
CelRuntime.Program program = RUNTIME.createProgram(ast);
1514+
1515+
Object evaluatedResult =
1516+
program.eval(
1517+
ImmutableMap.of(
1518+
"s", "\u0007bell\u000Bvtab\bback\ffeed\rret\nline\ttab\\slash 가 😁"));
1519+
1520+
assertThat(evaluatedResult)
1521+
.isEqualTo("\"\\abell\\vvtab\\bback\\ffeed\\rret\\nline\\ttab\\\\slash 가 😁\"");
1522+
}
1523+
14701524
@Test
14711525
public void stringExtension_compileUnallowedFunction_throws() {
14721526
CelCompiler celCompiler =

0 commit comments

Comments
 (0)