This is an automated email from the ASF dual-hosted git repository. henrib pushed a commit to branch JEXL-440 in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
commit c224a05a0a75b05dc19c965519d8a6424e55d4cf Author: Henrib <hbies...@gmail.com> AuthorDate: Fri Jun 6 21:17:27 2025 +0200 JEXL-440 : added switch/case/default syntax for statement and expressions; - Debugger handling of new AST nodes; --- .../org/apache/commons/jexl3/JexlFeatures.java | 21 ++- .../apache/commons/jexl3/internal/Debugger.java | 188 ++++++++++----------- .../apache/commons/jexl3/internal/Interpreter.java | 116 +++---------- .../commons/jexl3/internal/ScriptVisitor.java | 115 +++---------- .../commons/jexl3/parser/ASTCaseExpression.java | 30 ++++ .../commons/jexl3/parser/ASTCaseStatement.java | 57 +++++++ .../commons/jexl3/parser/ASTSwitchExpression.java | 33 ++++ .../commons/jexl3/parser/ASTSwitchStatement.java | 115 +++++++++++++ .../apache/commons/jexl3/parser/JexlParser.java | 120 ++++++++++++- .../apache/commons/jexl3/parser/NumberParser.java | 14 ++ .../org/apache/commons/jexl3/parser/Parser.jjt | 111 +++++++++++- .../apache/commons/jexl3/parser/ParserVisitor.java | 8 + .../org/apache/commons/jexl3/FeaturesTest.java | 6 +- .../org/apache/commons/jexl3/Issues400Test.java | 26 ++- .../java/org/apache/commons/jexl3/LambdaTest.java | 26 ++- 15 files changed, 664 insertions(+), 322 deletions(-) diff --git a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java index 3d933e13..d2cb4dbe 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlFeatures.java +++ b/src/main/java/org/apache/commons/jexl3/JexlFeatures.java @@ -73,7 +73,7 @@ public final class JexlFeatures { "global assign/modify", "array reference", "create instance", "loop", "function", "method call", "set/map/array literal", "pragma", "annotation", "script", "lexical", "lexicalShade", "thin-arrow", "fat-arrow", "namespace pragma", "namespace identifier", "import pragma", "comparator names", "pragma anywhere", - "const capture", "ref capture" + "const capture", "ref capture", "ambiguous statement" }; /** Registers feature ordinal. */ private static final int REGISTER = 0; @@ -125,13 +125,13 @@ public final class JexlFeatures { public static final int CONST_CAPTURE = 23; /** Captured variables are reference. */ public static final int REF_CAPTURE = 24; - /** Captured variables are reference. */ - public static final int STRICT_STATEMENT = 25; + /** Ambiguous or strict statement allowed. */ + public static final int AMBIGUOUS_STATEMENT = 25; /** * All features. * Ensure this is updated if additional features are added. */ - private static final long ALL_FEATURES = (1L << REF_CAPTURE + 1) - 1L; // MUST REMAIN PRIVATE + private static final long ALL_FEATURES = (1L << AMBIGUOUS_STATEMENT + 1) - 1L; // MUST REMAIN PRIVATE /** * The default features flag mask. * <p>Meant for compatibility with scripts written before 3.3.1</p> @@ -168,13 +168,12 @@ public final class JexlFeatures { /** * Protected future syntactic elements. - * <p><em>throw, switch, case, default, class, instanceof, jexl, $jexl</em></p> + * <p><em>class, jexl, $jexl</em></p> * @since 3.3.1 */ private static final Set<String> RESERVED_WORDS = Collections.unmodifiableSet( - new HashSet<>(Arrays.asList( - "switch", "case", "default", "class", "jexl", "$jexl"))); + new HashSet<>(Arrays.asList( "class", "jexl", "$jexl"))); /* * *WARNING* @@ -719,6 +718,14 @@ public final class JexlFeatures { } } + public void setAmbiguousStatement(final boolean flag) { + setFeature(AMBIGUOUS_STATEMENT, flag); + } + + public boolean supportsAmbiguousStatement() { + return getFeature(AMBIGUOUS_STATEMENT); + } + /** * Sets whether side effect expressions are enabled. * <p> diff --git a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java index 1859e699..a0944e53 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java @@ -17,6 +17,7 @@ package org.apache.commons.jexl3.internal; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; @@ -25,102 +26,7 @@ import org.apache.commons.jexl3.JexlExpression; import org.apache.commons.jexl3.JexlFeatures; import org.apache.commons.jexl3.JexlInfo; import org.apache.commons.jexl3.JexlScript; -import org.apache.commons.jexl3.parser.ASTAddNode; -import org.apache.commons.jexl3.parser.ASTAndNode; -import org.apache.commons.jexl3.parser.ASTAnnotatedStatement; -import org.apache.commons.jexl3.parser.ASTAnnotation; -import org.apache.commons.jexl3.parser.ASTArguments; -import org.apache.commons.jexl3.parser.ASTArrayAccess; -import org.apache.commons.jexl3.parser.ASTArrayLiteral; -import org.apache.commons.jexl3.parser.ASTAssignment; -import org.apache.commons.jexl3.parser.ASTBitwiseAndNode; -import org.apache.commons.jexl3.parser.ASTBitwiseComplNode; -import org.apache.commons.jexl3.parser.ASTBitwiseOrNode; -import org.apache.commons.jexl3.parser.ASTBitwiseXorNode; -import org.apache.commons.jexl3.parser.ASTBlock; -import org.apache.commons.jexl3.parser.ASTBreak; -import org.apache.commons.jexl3.parser.ASTConstructorNode; -import org.apache.commons.jexl3.parser.ASTContinue; -import org.apache.commons.jexl3.parser.ASTDecrementGetNode; -import org.apache.commons.jexl3.parser.ASTDefineVars; -import org.apache.commons.jexl3.parser.ASTDivNode; -import org.apache.commons.jexl3.parser.ASTDoWhileStatement; -import org.apache.commons.jexl3.parser.ASTEQNode; -import org.apache.commons.jexl3.parser.ASTEQSNode; -import org.apache.commons.jexl3.parser.ASTERNode; -import org.apache.commons.jexl3.parser.ASTEWNode; -import org.apache.commons.jexl3.parser.ASTEmptyFunction; -import org.apache.commons.jexl3.parser.ASTExtendedLiteral; -import org.apache.commons.jexl3.parser.ASTFalseNode; -import org.apache.commons.jexl3.parser.ASTForeachStatement; -import org.apache.commons.jexl3.parser.ASTFunctionNode; -import org.apache.commons.jexl3.parser.ASTGENode; -import org.apache.commons.jexl3.parser.ASTGTNode; -import org.apache.commons.jexl3.parser.ASTGetDecrementNode; -import org.apache.commons.jexl3.parser.ASTGetIncrementNode; -import org.apache.commons.jexl3.parser.ASTIdentifier; -import org.apache.commons.jexl3.parser.ASTIdentifierAccess; -import org.apache.commons.jexl3.parser.ASTIfStatement; -import org.apache.commons.jexl3.parser.ASTIncrementGetNode; -import org.apache.commons.jexl3.parser.ASTInstanceOf; -import org.apache.commons.jexl3.parser.ASTJexlLambda; -import org.apache.commons.jexl3.parser.ASTJexlScript; -import org.apache.commons.jexl3.parser.ASTJxltLiteral; -import org.apache.commons.jexl3.parser.ASTLENode; -import org.apache.commons.jexl3.parser.ASTLTNode; -import org.apache.commons.jexl3.parser.ASTMapEntry; -import org.apache.commons.jexl3.parser.ASTMapLiteral; -import org.apache.commons.jexl3.parser.ASTMethodNode; -import org.apache.commons.jexl3.parser.ASTModNode; -import org.apache.commons.jexl3.parser.ASTMulNode; -import org.apache.commons.jexl3.parser.ASTNENode; -import org.apache.commons.jexl3.parser.ASTNESNode; -import org.apache.commons.jexl3.parser.ASTNEWNode; -import org.apache.commons.jexl3.parser.ASTNRNode; -import org.apache.commons.jexl3.parser.ASTNSWNode; -import org.apache.commons.jexl3.parser.ASTNotInstanceOf; -import org.apache.commons.jexl3.parser.ASTNotNode; -import org.apache.commons.jexl3.parser.ASTNullLiteral; -import org.apache.commons.jexl3.parser.ASTNullpNode; -import org.apache.commons.jexl3.parser.ASTNumberLiteral; -import org.apache.commons.jexl3.parser.ASTOrNode; -import org.apache.commons.jexl3.parser.ASTQualifiedIdentifier; -import org.apache.commons.jexl3.parser.ASTRangeNode; -import org.apache.commons.jexl3.parser.ASTReference; -import org.apache.commons.jexl3.parser.ASTReferenceExpression; -import org.apache.commons.jexl3.parser.ASTRegexLiteral; -import org.apache.commons.jexl3.parser.ASTReturnStatement; -import org.apache.commons.jexl3.parser.ASTSWNode; -import org.apache.commons.jexl3.parser.ASTSetAddNode; -import org.apache.commons.jexl3.parser.ASTSetAndNode; -import org.apache.commons.jexl3.parser.ASTSetDivNode; -import org.apache.commons.jexl3.parser.ASTSetLiteral; -import org.apache.commons.jexl3.parser.ASTSetModNode; -import org.apache.commons.jexl3.parser.ASTSetMultNode; -import org.apache.commons.jexl3.parser.ASTSetOrNode; -import org.apache.commons.jexl3.parser.ASTSetShiftLeftNode; -import org.apache.commons.jexl3.parser.ASTSetShiftRightNode; -import org.apache.commons.jexl3.parser.ASTSetShiftRightUnsignedNode; -import org.apache.commons.jexl3.parser.ASTSetSubNode; -import org.apache.commons.jexl3.parser.ASTSetXorNode; -import org.apache.commons.jexl3.parser.ASTShiftLeftNode; -import org.apache.commons.jexl3.parser.ASTShiftRightNode; -import org.apache.commons.jexl3.parser.ASTShiftRightUnsignedNode; -import org.apache.commons.jexl3.parser.ASTSizeFunction; -import org.apache.commons.jexl3.parser.ASTStringLiteral; -import org.apache.commons.jexl3.parser.ASTSubNode; -import org.apache.commons.jexl3.parser.ASTTernaryNode; -import org.apache.commons.jexl3.parser.ASTThrowStatement; -import org.apache.commons.jexl3.parser.ASTTrueNode; -import org.apache.commons.jexl3.parser.ASTTryResources; -import org.apache.commons.jexl3.parser.ASTTryStatement; -import org.apache.commons.jexl3.parser.ASTUnaryMinusNode; -import org.apache.commons.jexl3.parser.ASTUnaryPlusNode; -import org.apache.commons.jexl3.parser.ASTVar; -import org.apache.commons.jexl3.parser.ASTWhileStatement; -import org.apache.commons.jexl3.parser.JexlNode; -import org.apache.commons.jexl3.parser.ParserVisitor; -import org.apache.commons.jexl3.parser.StringParser; +import org.apache.commons.jexl3.parser.*; /** * Helps pinpoint the cause of problems in expressions that fail during evaluation. @@ -145,6 +51,9 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail { * @return true if node is a statement */ private static boolean isStatement(final JexlNode child) { + if (child instanceof ASTCaseStatement) { + return isStatement(child.jjtGetChild(0)); + } return child instanceof ASTJexlScript || child instanceof ASTBlock || child instanceof ASTIfStatement @@ -153,7 +62,8 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail { || child instanceof ASTWhileStatement || child instanceof ASTDoWhileStatement || child instanceof ASTAnnotation - || child instanceof ASTThrowStatement; + || child instanceof ASTThrowStatement + || child instanceof ASTSwitchStatement; } /** * Tests whether a script or expression ends with a semicolumn. @@ -189,7 +99,7 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail { builder.append("#pragma "); builder.append(key); builder.append(' '); - builder.append(pragmaValue.toString()); + acceptValue(builder, pragmaValue, false); builder.append('\n'); } } @@ -260,7 +170,7 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail { */ protected Object acceptStatement(final JexlNode child, final Object data) { final JexlNode parent = child.jjtGetParent(); - if (indent > 0 && (parent instanceof ASTBlock || parent instanceof ASTJexlScript)) { + if (indent > 0 && (parent instanceof ASTBlock || parent instanceof ASTJexlScript || parent instanceof ASTSwitchStatement)) { for (int i = 0; i < indentLevel; ++i) { for(int s = 0; s < indent; ++s) { builder.append(' '); @@ -554,6 +464,24 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail { } return data; } + /** + * Accepts a (simple) value and appends its representation to the builder. + * @param builder where to append + * @param value the value to append + */ + static void acceptValue(StringBuilder builder, Object value, boolean quotedStrings) { + if (value == null) { + builder.append("null"); + } else if (value instanceof String) { + builder.append(quotedStrings? StringParser.escapeString(value.toString(), '\'') : value.toString()); + } else if (value instanceof Number) { + builder.append(new NumberParser((Number) value)); + } else if (value instanceof Boolean) { + builder.append((Boolean) value ? "true" : "false"); + } else { + builder.append(value.toString()); + } + } /** * Resets this debugger state. @@ -716,6 +644,10 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail { @Override protected Object visit(final ASTBlock node, final Object data) { + return acceptBlock(node, 0, data); + } + + private Object acceptBlock(final JexlNode node, int begin, final Object data) { builder.append('{'); if (indent > 0) { indentLevel += 1; @@ -724,7 +656,7 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail { builder.append(' '); } final int num = node.jjtGetNumChildren(); - for (int i = 0; i < num; ++i) { + for (int i = begin; i < num; ++i) { final JexlNode child = node.jjtGetChild(i); acceptStatement(child, data); } @@ -776,6 +708,62 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail { return data; } + @Override + protected Object visit(ASTSwitchStatement node, Object data) { + builder.append("switch ("); + accept(node.jjtGetChild(0), data); + builder.append(") "); + acceptBlock(node, 1, data); + return data; + } + + @Override + protected Object visit(ASTCaseStatement node, Object data) { + List<Object> values = node.getValues(); + if (values.isEmpty()) { + // default case + builder.append("default : "); + } else { + // regular case + for (Object value : values) { + builder.append("case "); + acceptValue(builder, value, true); + builder.append(" : "); + } + } + accept(node.jjtGetChild(0), data); + return data; + } + + @Override + protected Object visit(ASTSwitchExpression node, Object data) { + return visit((ASTSwitchStatement) node, data); + } + + @Override + protected Object visit(ASTCaseExpression node, Object data) { + List<Object> values = node.getValues(); + if (values.isEmpty()) { + // default case + builder.append("default -> "); + } else { + builder.append("case -> "); + // regular case + boolean first = true; + for (Object value : values) { + if (!first) { + builder.append(", "); + } else { + first = false; + } + acceptValue(builder, value, true); + } + builder.append(" : "); + } + accept(node.jjtGetChild(0), data); + return data; + } + @Override protected Object visit(final ASTContinue node, final Object data) { return check(node, "continue", data); diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java index 350803f9..dc097f99 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java @@ -35,101 +35,7 @@ import org.apache.commons.jexl3.JexlScript; import org.apache.commons.jexl3.JxltEngine; import org.apache.commons.jexl3.introspection.JexlMethod; import org.apache.commons.jexl3.introspection.JexlPropertyGet; -import org.apache.commons.jexl3.parser.ASTAddNode; -import org.apache.commons.jexl3.parser.ASTAndNode; -import org.apache.commons.jexl3.parser.ASTAnnotatedStatement; -import org.apache.commons.jexl3.parser.ASTAnnotation; -import org.apache.commons.jexl3.parser.ASTArguments; -import org.apache.commons.jexl3.parser.ASTArrayAccess; -import org.apache.commons.jexl3.parser.ASTArrayLiteral; -import org.apache.commons.jexl3.parser.ASTAssignment; -import org.apache.commons.jexl3.parser.ASTBitwiseAndNode; -import org.apache.commons.jexl3.parser.ASTBitwiseComplNode; -import org.apache.commons.jexl3.parser.ASTBitwiseOrNode; -import org.apache.commons.jexl3.parser.ASTBitwiseXorNode; -import org.apache.commons.jexl3.parser.ASTBlock; -import org.apache.commons.jexl3.parser.ASTBreak; -import org.apache.commons.jexl3.parser.ASTConstructorNode; -import org.apache.commons.jexl3.parser.ASTContinue; -import org.apache.commons.jexl3.parser.ASTDecrementGetNode; -import org.apache.commons.jexl3.parser.ASTDefineVars; -import org.apache.commons.jexl3.parser.ASTDivNode; -import org.apache.commons.jexl3.parser.ASTDoWhileStatement; -import org.apache.commons.jexl3.parser.ASTEQNode; -import org.apache.commons.jexl3.parser.ASTEQSNode; -import org.apache.commons.jexl3.parser.ASTERNode; -import org.apache.commons.jexl3.parser.ASTEWNode; -import org.apache.commons.jexl3.parser.ASTEmptyFunction; -import org.apache.commons.jexl3.parser.ASTExtendedLiteral; -import org.apache.commons.jexl3.parser.ASTFalseNode; -import org.apache.commons.jexl3.parser.ASTForeachStatement; -import org.apache.commons.jexl3.parser.ASTFunctionNode; -import org.apache.commons.jexl3.parser.ASTGENode; -import org.apache.commons.jexl3.parser.ASTGTNode; -import org.apache.commons.jexl3.parser.ASTGetDecrementNode; -import org.apache.commons.jexl3.parser.ASTGetIncrementNode; -import org.apache.commons.jexl3.parser.ASTIdentifier; -import org.apache.commons.jexl3.parser.ASTIdentifierAccess; -import org.apache.commons.jexl3.parser.ASTIdentifierAccessJxlt; -import org.apache.commons.jexl3.parser.ASTIfStatement; -import org.apache.commons.jexl3.parser.ASTIncrementGetNode; -import org.apache.commons.jexl3.parser.ASTInstanceOf; -import org.apache.commons.jexl3.parser.ASTJexlLambda; -import org.apache.commons.jexl3.parser.ASTJexlScript; -import org.apache.commons.jexl3.parser.ASTJxltLiteral; -import org.apache.commons.jexl3.parser.ASTLENode; -import org.apache.commons.jexl3.parser.ASTLTNode; -import org.apache.commons.jexl3.parser.ASTMapEntry; -import org.apache.commons.jexl3.parser.ASTMapLiteral; -import org.apache.commons.jexl3.parser.ASTMethodNode; -import org.apache.commons.jexl3.parser.ASTModNode; -import org.apache.commons.jexl3.parser.ASTMulNode; -import org.apache.commons.jexl3.parser.ASTNENode; -import org.apache.commons.jexl3.parser.ASTNESNode; -import org.apache.commons.jexl3.parser.ASTNEWNode; -import org.apache.commons.jexl3.parser.ASTNRNode; -import org.apache.commons.jexl3.parser.ASTNSWNode; -import org.apache.commons.jexl3.parser.ASTNotInstanceOf; -import org.apache.commons.jexl3.parser.ASTNotNode; -import org.apache.commons.jexl3.parser.ASTNullLiteral; -import org.apache.commons.jexl3.parser.ASTNullpNode; -import org.apache.commons.jexl3.parser.ASTNumberLiteral; -import org.apache.commons.jexl3.parser.ASTOrNode; -import org.apache.commons.jexl3.parser.ASTQualifiedIdentifier; -import org.apache.commons.jexl3.parser.ASTRangeNode; -import org.apache.commons.jexl3.parser.ASTReference; -import org.apache.commons.jexl3.parser.ASTReferenceExpression; -import org.apache.commons.jexl3.parser.ASTRegexLiteral; -import org.apache.commons.jexl3.parser.ASTReturnStatement; -import org.apache.commons.jexl3.parser.ASTSWNode; -import org.apache.commons.jexl3.parser.ASTSetAddNode; -import org.apache.commons.jexl3.parser.ASTSetAndNode; -import org.apache.commons.jexl3.parser.ASTSetDivNode; -import org.apache.commons.jexl3.parser.ASTSetLiteral; -import org.apache.commons.jexl3.parser.ASTSetModNode; -import org.apache.commons.jexl3.parser.ASTSetMultNode; -import org.apache.commons.jexl3.parser.ASTSetOrNode; -import org.apache.commons.jexl3.parser.ASTSetShiftLeftNode; -import org.apache.commons.jexl3.parser.ASTSetShiftRightNode; -import org.apache.commons.jexl3.parser.ASTSetShiftRightUnsignedNode; -import org.apache.commons.jexl3.parser.ASTSetSubNode; -import org.apache.commons.jexl3.parser.ASTSetXorNode; -import org.apache.commons.jexl3.parser.ASTShiftLeftNode; -import org.apache.commons.jexl3.parser.ASTShiftRightNode; -import org.apache.commons.jexl3.parser.ASTShiftRightUnsignedNode; -import org.apache.commons.jexl3.parser.ASTSizeFunction; -import org.apache.commons.jexl3.parser.ASTStringLiteral; -import org.apache.commons.jexl3.parser.ASTSubNode; -import org.apache.commons.jexl3.parser.ASTTernaryNode; -import org.apache.commons.jexl3.parser.ASTThrowStatement; -import org.apache.commons.jexl3.parser.ASTTrueNode; -import org.apache.commons.jexl3.parser.ASTTryResources; -import org.apache.commons.jexl3.parser.ASTTryStatement; -import org.apache.commons.jexl3.parser.ASTUnaryMinusNode; -import org.apache.commons.jexl3.parser.ASTUnaryPlusNode; -import org.apache.commons.jexl3.parser.ASTVar; -import org.apache.commons.jexl3.parser.ASTWhileStatement; -import org.apache.commons.jexl3.parser.JexlNode; +import org.apache.commons.jexl3.parser.*; /** * An interpreter of JEXL syntax. @@ -1324,6 +1230,26 @@ public class Interpreter extends InterpreterBase { } } + @Override + protected Object visit(ASTSwitchStatement node, Object data) { + return null; + } + + @Override + protected Object visit(ASTCaseStatement node, Object data) { + return null; + } + + @Override + protected Object visit(ASTSwitchExpression node, Object data) { + return null; + } + + @Override + protected Object visit(ASTCaseExpression node, Object data) { + return null; + } + @Override protected Object visit(final ASTContinue node, final Object data) { throw new JexlException.Continue(node); diff --git a/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java b/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java index c1f1cf1b..0068ea13 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java +++ b/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java @@ -18,100 +18,7 @@ package org.apache.commons.jexl3.internal; import org.apache.commons.jexl3.JexlExpression; import org.apache.commons.jexl3.JexlScript; -import org.apache.commons.jexl3.parser.ASTAddNode; -import org.apache.commons.jexl3.parser.ASTAndNode; -import org.apache.commons.jexl3.parser.ASTAnnotatedStatement; -import org.apache.commons.jexl3.parser.ASTAnnotation; -import org.apache.commons.jexl3.parser.ASTArguments; -import org.apache.commons.jexl3.parser.ASTArrayAccess; -import org.apache.commons.jexl3.parser.ASTArrayLiteral; -import org.apache.commons.jexl3.parser.ASTAssignment; -import org.apache.commons.jexl3.parser.ASTBitwiseAndNode; -import org.apache.commons.jexl3.parser.ASTBitwiseComplNode; -import org.apache.commons.jexl3.parser.ASTBitwiseOrNode; -import org.apache.commons.jexl3.parser.ASTBitwiseXorNode; -import org.apache.commons.jexl3.parser.ASTBlock; -import org.apache.commons.jexl3.parser.ASTBreak; -import org.apache.commons.jexl3.parser.ASTConstructorNode; -import org.apache.commons.jexl3.parser.ASTContinue; -import org.apache.commons.jexl3.parser.ASTDecrementGetNode; -import org.apache.commons.jexl3.parser.ASTDefineVars; -import org.apache.commons.jexl3.parser.ASTDivNode; -import org.apache.commons.jexl3.parser.ASTDoWhileStatement; -import org.apache.commons.jexl3.parser.ASTEQNode; -import org.apache.commons.jexl3.parser.ASTEQSNode; -import org.apache.commons.jexl3.parser.ASTERNode; -import org.apache.commons.jexl3.parser.ASTEWNode; -import org.apache.commons.jexl3.parser.ASTEmptyFunction; -import org.apache.commons.jexl3.parser.ASTExtendedLiteral; -import org.apache.commons.jexl3.parser.ASTFalseNode; -import org.apache.commons.jexl3.parser.ASTForeachStatement; -import org.apache.commons.jexl3.parser.ASTFunctionNode; -import org.apache.commons.jexl3.parser.ASTGENode; -import org.apache.commons.jexl3.parser.ASTGTNode; -import org.apache.commons.jexl3.parser.ASTGetDecrementNode; -import org.apache.commons.jexl3.parser.ASTGetIncrementNode; -import org.apache.commons.jexl3.parser.ASTIdentifier; -import org.apache.commons.jexl3.parser.ASTIdentifierAccess; -import org.apache.commons.jexl3.parser.ASTIfStatement; -import org.apache.commons.jexl3.parser.ASTIncrementGetNode; -import org.apache.commons.jexl3.parser.ASTInstanceOf; -import org.apache.commons.jexl3.parser.ASTJexlScript; -import org.apache.commons.jexl3.parser.ASTJxltLiteral; -import org.apache.commons.jexl3.parser.ASTLENode; -import org.apache.commons.jexl3.parser.ASTLTNode; -import org.apache.commons.jexl3.parser.ASTMapEntry; -import org.apache.commons.jexl3.parser.ASTMapLiteral; -import org.apache.commons.jexl3.parser.ASTMethodNode; -import org.apache.commons.jexl3.parser.ASTModNode; -import org.apache.commons.jexl3.parser.ASTMulNode; -import org.apache.commons.jexl3.parser.ASTNENode; -import org.apache.commons.jexl3.parser.ASTNESNode; -import org.apache.commons.jexl3.parser.ASTNEWNode; -import org.apache.commons.jexl3.parser.ASTNRNode; -import org.apache.commons.jexl3.parser.ASTNSWNode; -import org.apache.commons.jexl3.parser.ASTNotInstanceOf; -import org.apache.commons.jexl3.parser.ASTNotNode; -import org.apache.commons.jexl3.parser.ASTNullLiteral; -import org.apache.commons.jexl3.parser.ASTNullpNode; -import org.apache.commons.jexl3.parser.ASTNumberLiteral; -import org.apache.commons.jexl3.parser.ASTOrNode; -import org.apache.commons.jexl3.parser.ASTQualifiedIdentifier; -import org.apache.commons.jexl3.parser.ASTRangeNode; -import org.apache.commons.jexl3.parser.ASTReference; -import org.apache.commons.jexl3.parser.ASTReferenceExpression; -import org.apache.commons.jexl3.parser.ASTRegexLiteral; -import org.apache.commons.jexl3.parser.ASTReturnStatement; -import org.apache.commons.jexl3.parser.ASTSWNode; -import org.apache.commons.jexl3.parser.ASTSetAddNode; -import org.apache.commons.jexl3.parser.ASTSetAndNode; -import org.apache.commons.jexl3.parser.ASTSetDivNode; -import org.apache.commons.jexl3.parser.ASTSetLiteral; -import org.apache.commons.jexl3.parser.ASTSetModNode; -import org.apache.commons.jexl3.parser.ASTSetMultNode; -import org.apache.commons.jexl3.parser.ASTSetOrNode; -import org.apache.commons.jexl3.parser.ASTSetShiftLeftNode; -import org.apache.commons.jexl3.parser.ASTSetShiftRightNode; -import org.apache.commons.jexl3.parser.ASTSetShiftRightUnsignedNode; -import org.apache.commons.jexl3.parser.ASTSetSubNode; -import org.apache.commons.jexl3.parser.ASTSetXorNode; -import org.apache.commons.jexl3.parser.ASTShiftLeftNode; -import org.apache.commons.jexl3.parser.ASTShiftRightNode; -import org.apache.commons.jexl3.parser.ASTShiftRightUnsignedNode; -import org.apache.commons.jexl3.parser.ASTSizeFunction; -import org.apache.commons.jexl3.parser.ASTStringLiteral; -import org.apache.commons.jexl3.parser.ASTSubNode; -import org.apache.commons.jexl3.parser.ASTTernaryNode; -import org.apache.commons.jexl3.parser.ASTThrowStatement; -import org.apache.commons.jexl3.parser.ASTTrueNode; -import org.apache.commons.jexl3.parser.ASTTryResources; -import org.apache.commons.jexl3.parser.ASTTryStatement; -import org.apache.commons.jexl3.parser.ASTUnaryMinusNode; -import org.apache.commons.jexl3.parser.ASTUnaryPlusNode; -import org.apache.commons.jexl3.parser.ASTVar; -import org.apache.commons.jexl3.parser.ASTWhileStatement; -import org.apache.commons.jexl3.parser.JexlNode; -import org.apache.commons.jexl3.parser.ParserVisitor; +import org.apache.commons.jexl3.parser.*; /** * Concrete visitor base, used for feature and operator controllers. @@ -192,6 +99,26 @@ public class ScriptVisitor extends ParserVisitor { return visitNode(node, data); } + @Override + protected Object visit(final ASTSwitchStatement node, final Object data) { + return visitNode(node, data); + } + + @Override + protected Object visit(final ASTCaseStatement node, final Object data) { + return visitNode(node, data); + } + + @Override + protected Object visit(final ASTSwitchExpression node, final Object data) { + return visitNode(node, data); + } + + @Override + protected Object visit(final ASTCaseExpression node, final Object data) { + return visitNode(node, data); + } + @Override protected Object visit(final ASTContinue node, final Object data) { return visitNode(node, data); diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTCaseExpression.java b/src/main/java/org/apache/commons/jexl3/parser/ASTCaseExpression.java new file mode 100644 index 00000000..fdbd2ec5 --- /dev/null +++ b/src/main/java/org/apache/commons/jexl3/parser/ASTCaseExpression.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.jexl3.parser; + + +public class ASTCaseExpression extends ASTCaseStatement { + + public ASTCaseExpression(int id) { + super(id); + } + + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTCaseStatement.java b/src/main/java/org/apache/commons/jexl3/parser/ASTCaseStatement.java new file mode 100644 index 00000000..95e02eba --- /dev/null +++ b/src/main/java/org/apache/commons/jexl3/parser/ASTCaseStatement.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.jexl3.parser; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ASTCaseStatement extends JexlNode { + private transient List<Object> values = Collections.emptyList(); + + public ASTCaseStatement(int id) { + super(id); + } + + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void setValue(Object value) { + if (value == null) { + this.values = Collections.emptyList(); + } else { + this.values = Collections.singletonList(value); + } + } + + public void setValues(List<Object> values) { + if (values == null) { + this.values = Collections.emptyList(); + } else if (values.size() == 1) { + this.values = Collections.singletonList(values.get(0)); + } else { + this.values = new ArrayList<>(values); + } + } + + public List<Object> getValues() { + return Collections.unmodifiableList(values); + } +} diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchExpression.java b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchExpression.java new file mode 100644 index 00000000..7e53a32d --- /dev/null +++ b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchExpression.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.jexl3.parser; + + +public class ASTSwitchExpression extends ASTSwitchStatement { + public ASTSwitchExpression(int id) { + super(id); + } + + public ASTSwitchExpression(Parser p, int id) { + super(p, id); + } + + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java new file mode 100644 index 00000000..4690027e --- /dev/null +++ b/src/main/java/org/apache/commons/jexl3/parser/ASTSwitchStatement.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.jexl3.parser; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class ASTSwitchStatement extends JexlNode { + public ASTSwitchStatement(int id) { + super(id); + } + + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + /** + * The map of cases, where the key is the case value and the value is the switch index. + */ + protected transient Map<Object, Integer> cases = Collections.emptyMap(); + + /** + * Returns the array of cases values. + * <p>Meant only for verification during tests.</p> + * The list at each index contains the case values for that index. + * If the values-list is empty for an index, it is the default case. + * + * @return an array of case values + */ + public List<Object>[] getCasesList() { + @SuppressWarnings("unchecked") + List<Object>[] list = (List<Object>[]) new List[jjtGetNumChildren() -1]; + for (Map.Entry<Object, Integer> entry : cases.entrySet()) { + int index = entry.getValue(); + if (index < 0 || index >= list.length) { + throw new IndexOutOfBoundsException("switch index out of bounds: " + index); + } + List<Object> values = list[index]; + if (values == null) { + list[index] = values = new ArrayList<>(); + } + values.add(entry.getValue()); + } + return list; + } + + @SuppressWarnings("unchecked") + public void setCases(Map cases) { + this.cases = cases == null ? Collections.emptyMap() : (Map<Object, Integer>) cases; + } + + Map<Object, Integer> getCases() { + return cases; + } + + public int switchIndex(Object value) { + Object code = JexlParser.switchCode(value); + Integer index = cases.get(code); + if (index == null) { + index = cases.get(JexlParser.DFLT); + } + if (index != null && index >= 0 && index < jjtGetNumChildren()) { + return index; // index is 1-based, children are 0-based + } + return -1; + } + + public static class Helper { + private int nswitch = 0; + private boolean defaultDefined = false; + private final Map<Object, Integer> dispatch = new LinkedHashMap<>(); + + void defineCase(JexlParser.SwitchSet constants) throws ParseException { + if (constants.isEmpty()) { + if (defaultDefined) { + throw new ParseException("default clause is already defined"); + } else { + defaultDefined = true; + dispatch.put(JexlParser.DFLT, nswitch); + } + } else { + for (Object constant : constants) { + if (dispatch.put(constant == null ? JexlParser.NIL : constant, nswitch) != null) { + throw new ParseException("duplicate case in switch statement for value: " + constant); + } + } + constants.clear(); + } + nswitch += 1; + } + + void defineSwitch(ASTSwitchStatement statement) { + statement.cases = dispatch; + } + } + +} diff --git a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java index 3ed78de9..b971a096 100644 --- a/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java +++ b/src/main/java/org/apache/commons/jexl3/parser/JexlParser.java @@ -21,9 +21,11 @@ import java.io.IOException; import java.io.StringReader; import java.util.ArrayDeque; import java.util.Arrays; +import java.util.Collection; import java.util.Deque; import java.util.HashSet; import java.util.IdentityHashMap; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -221,9 +223,125 @@ public abstract class JexlParser extends StringParser implements JexlScriptParse */ protected final Map<LexicalUnit, Scope> blockScopes = new IdentityHashMap<>(); + /** + * The name of the null case constant. + */ + public static final Object NIL = new Object() { + @Override public String toString() { + return "null"; + }}; + /** + * The name of the default case constant. + */ + public static final Object DFLT = new Object() { + @Override public String toString() { + return "default"; + }}; + /** + * The name of the default NaN constant. + */ + public static final Object NAN = new Object() { + @Override public String toString() { + return "NaN"; + }}; + + /** + * Encode a value to a switch predicate. + * @param value the value + * @return the encoded value, which is either the value itself, or NAN (for NaN) or NIL (for null) + */ + static Object switchCode(Object value) { + if (value == null) { + return NIL; + } + if (value instanceof Double && ((Double) value).isNaN()) { + return NAN; + } + return value; + } + + /** + * Decodes a value of a switch predicate. + * @param value an encoded value, which is either a value or NAN (for NaN) or NIL (for null) + * @return the decoded value + */ + static Object switchDecode(Object value) { + if (value == NIL) { + return null; + } + if (value == NAN) { + return Double.NaN; + } + return value; + } + + /** + * Constructs a set of constants amenable to switch expression. + */ + protected SwitchSet switchSet() { + return new SwitchSet(); + } + protected class SwitchSet implements Iterable<Object> { + private final Set<Object> values = new LinkedHashSet<>(); + /** + * Adds a collection of values to the set. + * @param values the values to add + */ + void addAll(Collection<Object> values) { + for (Object value : values) { + add(value); + } + } + + /** + * Adds a value to the set. + * @param value the value to add + */ + void add(Object value) { + Object code = switchCode(value); + if (!values.add(code)) { + throw new JexlException.Parsing(info, "duplicate constant value: " + value); + } + } + + void clear() { + values.clear(); + } + + boolean isEmpty() { + return values.isEmpty(); + } + + int size() { + return values.size(); + } + + @Override + public Iterator<Object> iterator() { + return new Iterator<Object>() { + private final Iterator<Object> iter = values.iterator(); + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public Object next() { + return switchDecode(iter.next()); + } + + @Override + public void remove() { + iter.remove(); + } + }; + } + } + /** * Internal, for debug purpose only. - * @param registers whether register syntax is recognized by this parser + * @param registers sets whether this parser recognizes the register syntax */ public void allowRegisters(final boolean registers) { featureController.setFeatures(new JexlFeatures(featureController.getFeatures()).register(registers)); diff --git a/src/main/java/org/apache/commons/jexl3/parser/NumberParser.java b/src/main/java/org/apache/commons/jexl3/parser/NumberParser.java index aa5a9aa2..4ebc147b 100644 --- a/src/main/java/org/apache/commons/jexl3/parser/NumberParser.java +++ b/src/main/java/org/apache/commons/jexl3/parser/NumberParser.java @@ -43,6 +43,20 @@ public final class NumberParser implements Serializable { return new NumberParser().assignNatural(isNegative(negative), s.image).getLiteralValue(); } + public NumberParser() { + this(null); + } + + public NumberParser(Number number) { + if (number != null) { + this.literal = number; + this.clazz = number.getClass(); + } else { + this.literal = null; + this.clazz = null; + } + } + /** The type literal value. */ private Number literal; diff --git a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt index 0b32a451..eaaef134 100644 --- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt +++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt @@ -39,6 +39,7 @@ package org.apache.commons.jexl3.parser; import java.util.Collections; import java.util.List; +import java.util.ArrayList; import java.util.LinkedList; import org.apache.commons.jexl3.JexlInfo; @@ -71,7 +72,11 @@ public final class Parser extends JexlParser throw new JexlException.Tokenization(info, xtme).clean(); } catch (ParseException xparse) { Token errortok = errorToken(jj_lastpos, jj_scanpos, token.next, token); - throw new JexlException.Parsing(info.at(errortok.beginLine, errortok.beginColumn), errortok.image).clean(); + String msg = xparse.getMessage(); + if (msg.endsWith(";")) { + msg = errortok.image; + } + throw new JexlException.Parsing(info.at(errortok.beginLine, errortok.beginColumn), msg).clean(); } finally { token_source.defaultLexState = DEFAULT; cleanup(previous); @@ -238,6 +243,9 @@ TOKEN_MGR_DECLS : { | < NULL : "null" > | < TRUE : "true" > | < FALSE : "false" > + | < SWITCH : "switch" > + | < CASE : "case" > + | < CASE_DEFAULT : "default" > } @@ -413,10 +421,11 @@ void StatementNoVar() #void : {} | LOOKAHEAD(<BREAK>) Break() | LOOKAHEAD(<THROW>) ThrowStatement() | LOOKAHEAD(<TRY>) TryStatement() + | LOOKAHEAD(<SWITCH>) SwitchStatement() | LOOKAHEAD(LambdaLookahead()) Lambda() | LOOKAHEAD(Expression()) ExpressionStatement() | Block() - | LOOKAHEAD(<VAR>, {!getFeatures().isLexical()} ) Var() + | LOOKAHEAD(<VAR>, { !getFeatures().isLexical()} ) Var() } void Block() #Block : {} @@ -431,7 +440,7 @@ void FunctionStatement() #JexlLambda : {} void ExpressionStatement() #void : {} { - Expression() (LOOKAHEAD(Expression()) Expression() #Ambiguous(1))* (LOOKAHEAD(1) <SEMICOL>)? + Expression() (LOOKAHEAD(Expression()/*, { getFeatures().supportsAmbiguousStatement() }*/) Expression() #Ambiguous(1))* (LOOKAHEAD(1) <SEMICOL>)? } @@ -579,6 +588,99 @@ void DeclareFunction() #Var : t=<IDENTIFIER> { declareFunction(jjtThis, t); } } + +void SwitchStatement() #SwitchStatement : +{ + ASTSwitchStatement.Helper helper = new ASTSwitchStatement.Helper(); + SwitchSet cases = switchSet(); +} +{ + <SWITCH> <LPAREN> Expression() <RPAREN> <LCURLY> + ( SwitchStatementCase(cases) { helper.defineCase(cases); } )* + <RCURLY> +{ + helper.defineSwitch(jjtThis); +} +} + + +void SwitchStatementCase(SwitchSet cases) #CaseStatement : +{ + Object constant; + List<Object> constants; + loopCount += 1; +} +{ + ( LOOKAHEAD(3) + ( <CASE> constant=constLiteral() <COLON> )+ + { constants = Collections.singletonList(constant); jjtThis.setValues(constants); cases.addAll(constants); } + | + <CASE_DEFAULT> <COLON> + ) + ( LOOKAHEAD(1) Block() | StatementNoVar() ) +{ + loopCount -= 1; +} +} + +void SwitchExpression() #SwitchExpression : +{ + ASTSwitchStatement.Helper helper = new ASTSwitchStatement.Helper(); + SwitchSet cases = switchSet(); +} +{ + <SWITCH> <LPAREN> Expression() <RPAREN> <LCURLY> + ( SwitchExpressionCase(cases) { helper.defineCase(cases); } )* + <RCURLY> +{ + helper.defineSwitch(jjtThis); +} +} + +void SwitchExpressionCase(SwitchSet cases) #CaseExpression : +{ + List<Object> constants = new ArrayList(1); +} +{ + ( LOOKAHEAD(2) + <CASE> ConstLiterals(constants) <LAMBDA> { cases.addAll(constants); jjtThis.setValues(constants); } + | + <CASE_DEFAULT> <LAMBDA> + ) + ( LOOKAHEAD(3) Block() | Expression()) +} + +void ConstLiterals(List<Object> constants) #void : +{ + Object constant; +} +{ + constant=constLiteral() { constants.add(constant); } (LOOKAHEAD(2) <COMMA> constant=constLiteral() { constants.add(constant); })* +} + + +Object constLiteral() #void : +{ +Token s = null; +Token v; +Object result; +} +{ + ( + LOOKAHEAD(2) (s=<plus>|s=<minus>)? v=<INTEGER_LITERAL> { result = NumberParser.parseInteger(s, v); } + | LOOKAHEAD(2) (s=<plus>|s=<minus>)? v=<FLOAT_LITERAL> { result = NumberParser.parseDouble(s, v); } + | LOOKAHEAD(1) v=<STRING_LITERAL> { result = Parser.buildString(v.image, true); } + | LOOKAHEAD(1) <TRUE> { result = true; } + | LOOKAHEAD(1) <FALSE> { result = false; } + | LOOKAHEAD(1) <NULL> { result = null; } + | LOOKAHEAD(1) <NAN_LITERAL> { result = Double.NaN; } + ) + { + return result; + } +} + + void Pragma() #void : { LinkedList<String> lstr = new LinkedList<String>(); @@ -1054,7 +1156,6 @@ void LambdaLookahead() #void : {} void Lambda() #JexlLambda : { Token arrow; - Token name; } { <FUNCTION> (LOOKAHEAD(<IDENTIFIER>) DeclareFunction())? { @@ -1132,6 +1233,8 @@ void PrimaryExpression() #void : {} LOOKAHEAD( <LBRACKET> ) ArrayLiteral() | LOOKAHEAD( <NEW> ) Constructor() + | + LOOKAHEAD( <SWITCH> ) SwitchExpression() | LOOKAHEAD( FunctionCallLookahead() ) FunctionCall() | diff --git a/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java b/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java index 2d1ffbe1..7ba32e41 100644 --- a/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java +++ b/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java @@ -60,6 +60,14 @@ public abstract class ParserVisitor { protected abstract Object visit(ASTConstructorNode node, Object data); + protected abstract Object visit(ASTSwitchStatement node, final Object data); + + protected abstract Object visit(ASTCaseStatement node, final Object data); + + protected abstract Object visit(ASTSwitchExpression node, final Object data); + + protected abstract Object visit(ASTCaseExpression node, final Object data); + protected abstract Object visit(ASTContinue node, Object data); protected abstract Object visit(ASTDecrementGetNode node, final Object data); diff --git a/src/test/java/org/apache/commons/jexl3/FeaturesTest.java b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java index f7ed0d1c..161a3660 100644 --- a/src/test/java/org/apache/commons/jexl3/FeaturesTest.java +++ b/src/test/java/org/apache/commons/jexl3/FeaturesTest.java @@ -16,7 +16,7 @@ */ package org.apache.commons.jexl3; -import static org.apache.commons.jexl3.JexlFeatures.REF_CAPTURE; +import static org.apache.commons.jexl3.JexlFeatures.AMBIGUOUS_STATEMENT; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -71,8 +71,8 @@ public class FeaturesTest extends JexlTestCase { @Test void test410a() { final long x = JexlFeatures.createAll().getFlags(); - assertEquals(REF_CAPTURE + 1, Long.bitCount(x)); - assertTrue((x & 1L << REF_CAPTURE) != 0); + assertEquals(AMBIGUOUS_STATEMENT + 1, Long.bitCount(x)); + assertTrue((x & 1L << AMBIGUOUS_STATEMENT) != 0); final JexlFeatures all = JexlFeatures.createAll(); final JexlEngine jexl = new JexlBuilder().features(all).create(); diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java b/src/test/java/org/apache/commons/jexl3/Issues400Test.java index 0a62b5bb..93a530bb 100644 --- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java +++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java @@ -271,7 +271,7 @@ public class Issues400Test { Number result = (Number) script.execute(null, 99.0d, 7.82d); assertEquals(0d, result.doubleValue(), 8.e-15); // using BigdDecimal, more precise, still not zero - result = (Number) script.execute(null, new BigDecimal(99.0d), new BigDecimal(7.82d)); + result = (Number) script.execute(null, new BigDecimal("99.0"), new BigDecimal("7.82")); assertEquals(0d, result.doubleValue(), 3.e-32); } @@ -561,7 +561,6 @@ public class Issues400Test { assertNotNull(script); final Object result = script.execute(null); assertNull(result); - } public static class Arithmetic435 extends JexlArithmetic { @@ -727,4 +726,27 @@ public class Issues400Test { Assertions.assertTrue((boolean) sqle.createScript("a = 25", "a").execute(null, 25)); Assertions.assertFalse((boolean) sqle.createScript("a != 25", "a").execute(null, 25)); } + + @Test + void test440a() { + JexlFeatures f = JexlFeatures.createDefault(); + f.setAmbiguousStatement(true); + JexlEngine jexl = new JexlBuilder().features(f).safe(false).strict(true).create(); + final String src = +"let y = switch (x) { case 10,11 -> 3 case 20, 21 -> 4\ndefault -> { let z = 4; z + 6 } } y"; + final JexlScript script = jexl.createScript(src, "x"); + assertNotNull(script); + String dbgStr = script.getParsedText(); + assertNotNull(dbgStr); + } + @Test + void test440b() { + JexlEngine jexl = new JexlBuilder().safe(false).strict(true).create(); + final String src = +"switch (x) { case 10 : 3; case 20 : case 21 : 4; default : return 5; } "; + final JexlScript script = jexl.createScript(src, "x"); + assertNotNull(script); + String dbgStr = script.getParsedText(); + assertNotNull(dbgStr); + } } diff --git a/src/test/java/org/apache/commons/jexl3/LambdaTest.java b/src/test/java/org/apache/commons/jexl3/LambdaTest.java index 3938add5..bf8df85b 100644 --- a/src/test/java/org/apache/commons/jexl3/LambdaTest.java +++ b/src/test/java/org/apache/commons/jexl3/LambdaTest.java @@ -32,6 +32,8 @@ import org.apache.commons.jexl3.internal.Closure; import org.apache.commons.jexl3.internal.Script; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; /** * Tests function/lambda/closure features. @@ -213,18 +215,16 @@ class LambdaTest extends JexlTestCase { assertEquals(42, result); } - @Test void testFailParseFunc0() { - final String src = "if (false) function foo(x) { x + x }; var foo = 1"; + @ParameterizedTest + @CsvSource({ + "'if (false) function foo(x) { x + x }; var foo = 1', 'function'", + "'if (false) let foo = (x) -> { x + x }; var foo = 1', 'let'", + "'function foo(x) { x + x }; var foo = 42', 'foo'" + }) + void testFailParseFunc(String src, String expectedKeyword) { final JexlEngine jexl = createEngine(); final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createScript(src)); - assertTrue(xparse.getMessage().contains("function")); - } - - @Test void testFailParseFunc1() { - final String src = "if (false) let foo = (x) { x + x }; var foo = 1"; - final JexlEngine jexl = createEngine(); - final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createScript(src)); - assertTrue(xparse.getMessage().contains("let")); + assertTrue(xparse.getMessage().contains(expectedKeyword)); } @Test void testFatFact0() { @@ -408,12 +408,6 @@ class LambdaTest extends JexlTestCase { assertEquals(simpleWhitespace(src), parsed); } - @Test void testNamedFuncIsConst() { - final String src = "function foo(x) { x + x }; var foo ='nonononon'"; - final JexlEngine jexl = createEngine(); - final JexlException.Parsing xparse = assertThrows(JexlException.Parsing.class, () -> jexl.createScript(src)); - assertTrue(xparse.getMessage().contains("foo")); - } @Test void testNestLambada() throws Exception { final JexlEngine jexl = createEngine();