From 40007891fc52bf269c7710ed02502d6980f4e674 Mon Sep 17 00:00:00 2001 From: Andrey G Date: Thu, 26 Mar 2026 22:49:54 +0100 Subject: [PATCH 1/7] Fix error handling, update tests --- .../antlr4/com/aerospike/dsl/Condition.g4 | 2 +- .../dsl/impl/DSLParserErrorListener.java | 8 + .../com/aerospike/dsl/impl/DSLParserImpl.java | 11 +- .../visitor/ExpressionConditionVisitor.java | 2 +- .../dsl/expression/BinExpressionsTests.java | 2 +- .../expression/ControlStructuresTests.java | 2 +- .../dsl/expression/ExplicitTypesTests.java | 8 +- .../dsl/expression/InNegativeTests.java | 2 +- .../expression/LogicalExpressionsTests.java | 2 +- .../dsl/expression/SyntaxErrorTests.java | 184 ++++++++++++++++++ .../dsl/filter/ExplicitTypesFiltersTests.java | 6 +- 11 files changed, 213 insertions(+), 16 deletions(-) create mode 100644 src/test/java/com/aerospike/dsl/expression/SyntaxErrorTests.java diff --git a/src/main/antlr4/com/aerospike/dsl/Condition.g4 b/src/main/antlr4/com/aerospike/dsl/Condition.g4 index f88d32c..fea3410 100644 --- a/src/main/antlr4/com/aerospike/dsl/Condition.g4 +++ b/src/main/antlr4/com/aerospike/dsl/Condition.g4 @@ -78,7 +78,7 @@ unaryExpression ; variableDefinition - : stringOperand '=' expression + : NAME_IDENTIFIER '=' expression ; expressionMapping diff --git a/src/main/java/com/aerospike/dsl/impl/DSLParserErrorListener.java b/src/main/java/com/aerospike/dsl/impl/DSLParserErrorListener.java index 499ff45..e462cbb 100644 --- a/src/main/java/com/aerospike/dsl/impl/DSLParserErrorListener.java +++ b/src/main/java/com/aerospike/dsl/impl/DSLParserErrorListener.java @@ -10,6 +10,8 @@ class DSLParserErrorListener extends BaseErrorListener { + private boolean hasErrors; + @Override public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, @@ -18,6 +20,7 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, // LEADING_DOT_FLOAT_HEX_OR_BINARY (.0xff/.0b101) is never accepted by the grammar; // LEADING_DOT_FLOAT is the offending token in cases like ".3.7" where ".7" is lexed // as a valid-looking token but appears where an operator was expected. + // These throw immediately to provide a specific error message. if (token.getType() == ConditionLexer.LEADING_DOT_FLOAT_HEX_OR_BINARY || token.getType() == ConditionLexer.LEADING_DOT_FLOAT) { throw new DslParseException("Invalid float literal: " + token.getText()); @@ -31,5 +34,10 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, throw new DslParseException("Invalid float literal: " + token.getText() + nextText); } } + hasErrors = true; + } + + boolean hasErrors() { + return hasErrors; } } diff --git a/src/main/java/com/aerospike/dsl/impl/DSLParserImpl.java b/src/main/java/com/aerospike/dsl/impl/DSLParserImpl.java index 0b4e3f9..d3165c3 100644 --- a/src/main/java/com/aerospike/dsl/impl/DSLParserImpl.java +++ b/src/main/java/com/aerospike/dsl/impl/DSLParserImpl.java @@ -49,8 +49,8 @@ public CTX[] parseCTX(String pathToCtx) { throw new DslParseException("Path must not be null or empty"); } - ParseTree parseTree = getParseTree(pathToCtx); try { + ParseTree parseTree = getParseTree(pathToCtx); return buildCtx(new ExpressionConditionVisitor().visit(parseTree)); } catch (Exception e) { throw new DslParseException("Could not parse the given DSL path input", e); @@ -61,8 +61,13 @@ private ParseTree getParseTree(String input) { ConditionLexer lexer = new ConditionLexer(CharStreams.fromString(input)); CommonTokenStream tokenStream = new CommonTokenStream(lexer); ConditionParser parser = new ConditionParser(tokenStream); - parser.addErrorListener(new DSLParserErrorListener()); - return parser.parse(); + DSLParserErrorListener errorListener = new DSLParserErrorListener(); + parser.addErrorListener(errorListener); + ParseTree tree = parser.parse(); + if (errorListener.hasErrors()) { + throw new DslParseException("Could not parse given DSL expression input"); + } + return tree; } private ParsedExpression getParsedExpression(ParseTree parseTree, PlaceholderValues placeholderValues, diff --git a/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java b/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java index 9835ec2..2a0ab16 100644 --- a/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java +++ b/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java @@ -54,7 +54,7 @@ public AbstractPart visitLetExpression(ConditionParser.LetExpressionContext ctx) // iterate through each definition for (ConditionParser.VariableDefinitionContext vdc : ctx.variableDefinition()) { AbstractPart part = visit(vdc.expression()); - LetOperand letOperand = new LetOperand(part, vdc.stringOperand().getText()); + LetOperand letOperand = new LetOperand(part, vdc.NAME_IDENTIFIER().getText()); expressions.add(letOperand); } // last expression is the action (described after "then") diff --git a/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java index 2d863de..2ec5475 100644 --- a/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java @@ -66,7 +66,7 @@ void binNotEquals() { void negativeStringBinEquals() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.strBin == yes"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Unexpected identifier"); + .hasMessageContaining("Could not parse given DSL expression input"); } // TODO: Will be handled in FMWK-486 diff --git a/src/test/java/com/aerospike/dsl/expression/ControlStructuresTests.java b/src/test/java/com/aerospike/dsl/expression/ControlStructuresTests.java index cb2fbbb..50bf1ca 100644 --- a/src/test/java/com/aerospike/dsl/expression/ControlStructuresTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ControlStructuresTests.java @@ -81,7 +81,7 @@ void withMultipleVariablesDefinitionAndUsage() { TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("let (x = 1, y = ${x} + 1) then (${x} + ${y})"), expected); - // different spacing style + // Different spacing style TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("let(x = 1, y = ${x} + 1) then(${x} + ${y})"), expected); } diff --git a/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java b/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java index 76b9e21..e5af647 100644 --- a/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java @@ -48,7 +48,7 @@ void stringComparisonNegativeTest() { TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.stringBin1.get(type: STRING) == yes"), Exp.eq(Exp.stringBin("stringBin1"), Exp.val("yes")))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Unexpected identifier"); + .hasMessageContaining("Could not parse given DSL expression input"); } @Test @@ -130,7 +130,7 @@ void listComparison_constantOnRightSide_NegativeTest() { Exp.eq(Exp.listBin("listBin1"), Exp.val(List.of("yes", "of course")))) ) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Unexpected identifier"); + .hasMessageContaining("Could not parse given DSL expression input"); } @Test @@ -243,7 +243,7 @@ void mapComparison_constantOnRightSide_NegativeTest() { Exp.eq(Exp.mapBin("mapBin1"), Exp.val(treeMapOf("yes", "of course")))) ) .isInstanceOf(DslParseException.class) - .hasMessage("Unable to parse map operand"); + .hasMessageContaining("Could not parse given DSL expression input"); assertThatThrownBy(() -> TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.get(type: MAP) == ['yes', 'of course']"), @@ -258,7 +258,7 @@ void mapComparison_constantOnRightSide_NegativeTest() { Exp.eq(Exp.mapBin("mapBin1"), Exp.val(List.of("yes", "of course")))) ) .isInstanceOf(DslParseException.class) - .hasMessage("Unable to parse map operand"); + .hasMessageContaining("Could not parse given DSL expression input"); } @Test diff --git a/src/test/java/com/aerospike/dsl/expression/InNegativeTests.java b/src/test/java/com/aerospike/dsl/expression/InNegativeTests.java index 9aa50c6..4166492 100644 --- a/src/test/java/com/aerospike/dsl/expression/InNegativeTests.java +++ b/src/test/java/com/aerospike/dsl/expression/InNegativeTests.java @@ -348,7 +348,7 @@ void negVarBoundToCountPath() { void negVarDefLetInScalarVar() { assertThatThrownBy(() -> parseFilterExp( ExpressionContext.of( - "let(x = 5, y = ($.bin.get(type: INT) in ${x})) then (y == true)"))) + "let(x = 5, y = ($.bin.get(type: INT) in ${x})) then (${y} == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("IN operation requires a List as the right operand") .hasMessageContaining("variable 'x'"); diff --git a/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java index 75aebd0..db035bf 100644 --- a/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java @@ -115,6 +115,6 @@ void negativeBinLogicalExclusiveWithOneParam() { Exp.exclusive( Exp.eq(Exp.stringBin("hand"), Exp.val("hook"))))) .isInstanceOf(DslParseException.class) - .hasMessage("Exclusive logical operator requires 2 or more expressions"); + .hasMessageContaining("Could not parse given DSL expression input"); } } diff --git a/src/test/java/com/aerospike/dsl/expression/SyntaxErrorTests.java b/src/test/java/com/aerospike/dsl/expression/SyntaxErrorTests.java new file mode 100644 index 0000000..4ab5921 --- /dev/null +++ b/src/test/java/com/aerospike/dsl/expression/SyntaxErrorTests.java @@ -0,0 +1,184 @@ +package com.aerospike.dsl.expression; + +import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.ExpressionContext; +import com.aerospike.dsl.client.exp.Exp; +import org.junit.jupiter.api.Test; + +import static com.aerospike.dsl.util.TestUtils.parseFilterExp; +import static com.aerospike.dsl.util.TestUtils.parseFilterExpressionAndCompare; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class SyntaxErrorTests { + + // --- General syntax errors --- + + @Test + void negNonsensePathFunction() { + assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("1.0 == $.f1.nonsense()"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + @Test + void negNonsensePathFunctionOnLeft() { + assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.f1.nonsense() == 1.0"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + @Test + void negTrailingGarbageTokens() { + assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.intBin1 > 100 garbage"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + // --- Positive syntax boundary --- + + @Test + void letWithAlphanumericVarName() { + Exp expected = Exp.let( + Exp.def("var_1", Exp.val(5)), + Exp.add(Exp.var("var_1"), Exp.val(1)) + ); + parseFilterExpressionAndCompare( + ExpressionContext.of("let(var_1 = 5) then (${var_1} + 1)"), expected); + } + + // --- Malformed variable references --- + + @Test + void negVarBareNameInThenBody() { + assertThatThrownBy(() -> parseFilterExp( + ExpressionContext.of( + "let(x = 5, y = ($.bin.get(type: INT) in ${x})) then (y == true)"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + @Test + void negVarDollarNoInThenBody() { + assertThatThrownBy(() -> parseFilterExp( + ExpressionContext.of( + "let(x = 5, y = ($.bin.get(type: INT) in ${x})) then ($y == true)"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + @Test + void negVarMissingCloseBrace() { + assertThatThrownBy(() -> parseFilterExp( + ExpressionContext.of( + "let(x = 5, y = ($.bin.get(type: INT) in ${x)) then (${y} == true)"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + @Test + void negVarMissingDollarAndOpen() { + assertThatThrownBy(() -> parseFilterExp( + ExpressionContext.of( + "let(x = 5, y = ($.bin.get(type: INT) in x})) then (${y} == true)"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + @Test + void negVarBareNameInLetExpr() { + assertThatThrownBy(() -> parseFilterExp( + ExpressionContext.of( + "let(x = 5, y = ($.bin.get(type: INT) in x)) then (${y} == true)"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + @Test + void negVarDoubleOpenBrace() { + assertThatThrownBy(() -> parseFilterExp( + ExpressionContext.of( + "let(x = 5, y = ($.bin.get(type: INT) in ${{x})) then (${y} == true)"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + @Test + void negVarDoubleCloseBrace() { + assertThatThrownBy(() -> parseFilterExp( + ExpressionContext.of( + "let(x = 5, y = ($.bin.get(type: INT) in ${x}})) then (${y} == true)"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + @Test + void negVarDoubleDollarSign() { + assertThatThrownBy(() -> parseFilterExp( + ExpressionContext.of( + "let(x = 5, y = ($.bin.get(type: INT) in $${x})) then (${y} == true)"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + // --- Mismatched delimiters --- + + @Test + void negOrphanedParentheses() { + assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.intBin1 > ()"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + @Test + void negGetFuncMissingCloseParen() { + assertThatThrownBy(() -> parseFilterExp( + ExpressionContext.of( + "let(x = 5, y = ($.bin.get(type: INT in ${x})) then (${y} == true)"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + // --- Malformed let structure --- + + @Test + void negLetMissingThen() { + assertThatThrownBy(() -> parseFilterExp( + ExpressionContext.of("let(x = 5) (${x} + 1)"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + @Test + void negLetEmptyDefs() { + assertThatThrownBy(() -> parseFilterExp( + ExpressionContext.of("let() then (1 + 2)"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + @Test + void negLetMissingEquals() { + assertThatThrownBy(() -> parseFilterExp( + ExpressionContext.of("let(x 5) then (${x} + 1)"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + // --- Malformed when structure --- + + @Test + void negWhenMissingDefault() { + assertThatThrownBy(() -> parseFilterExp( + ExpressionContext.of("when($.x == 1 => \"a\")"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } + + @Test + void negWhenMissingArrow() { + assertThatThrownBy(() -> parseFilterExp( + ExpressionContext.of("when($.x == 1 \"a\", default => \"b\")"))) + .isInstanceOf(DslParseException.class) + .hasMessageContaining("Could not parse given DSL expression input"); + } +} diff --git a/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java b/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java index 19610f4..77c02c1 100644 --- a/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java +++ b/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java @@ -58,7 +58,7 @@ void stringComparisonNegativeTest() { // A String constant must be quoted assertThatThrownBy(() -> parseFilter(ExpressionContext.of("$.stringBin1.get(type: STRING) == yes"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Unexpected identifier"); + .hasMessageContaining("Could not parse given DSL expression input"); } @Test @@ -104,7 +104,7 @@ void listComparison_constantOnRightSide() { void listComparison_constantOnRightSide_NegativeTest() { assertThatThrownBy(() -> parseFilter(ExpressionContext.of("$.listBin1.get(type: LIST) == [yes, of course]"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Unexpected identifier"); + .hasMessageContaining("Could not parse given DSL expression input"); } @Test @@ -128,7 +128,7 @@ void mapComparison_constantOnRightSide() { void mapComparison_constantOnRightSide_NegativeTest() { assertThatThrownBy(() -> parseFilter(ExpressionContext.of("$.mapBin1.get(type: MAP) == {yes, of course}"))) .isInstanceOf(DslParseException.class) - .hasMessage("Unable to parse map operand"); + .hasMessageContaining("Could not parse given DSL expression input"); } @Test From f52d6af9f3dd3361ea3e2a19d9eb078d8c000dd0 Mon Sep 17 00:00:00 2001 From: Andrey G Date: Thu, 26 Mar 2026 23:51:36 +0100 Subject: [PATCH 2/7] Move control structures from basicExpression to operand to enable composability with comparison operators --- .../antlr4/com/aerospike/dsl/Condition.g4 | 22 ++++++---- .../visitor/ExpressionConditionVisitor.java | 14 ++----- .../expression/ControlStructuresTests.java | 42 +++++++++++++++++++ 3 files changed, 58 insertions(+), 20 deletions(-) diff --git a/src/main/antlr4/com/aerospike/dsl/Condition.g4 b/src/main/antlr4/com/aerospike/dsl/Condition.g4 index fea3410..8e75638 100644 --- a/src/main/antlr4/com/aerospike/dsl/Condition.g4 +++ b/src/main/antlr4/com/aerospike/dsl/Condition.g4 @@ -15,15 +15,7 @@ logicalOrExpression ; logicalAndExpression - : basicExpression ('and' basicExpression)* # AndExpression - ; - -basicExpression - : 'not' '(' expression ')' # NotExpression - | 'exclusive' '(' expression (',' expression)+ ')' # ExclusiveExpression - | 'let' '(' variableDefinition (',' variableDefinition)* ')' 'then' '(' expression ')' # LetExpression - | 'when' '(' expressionMapping (',' expressionMapping)* ',' 'default' '=>' expression ')' # WhenExpression - | comparisonExpression # ComparisonExpressionWrapper + : comparisonExpression ('and' comparisonExpression)* # AndExpression ; comparisonExpression @@ -97,8 +89,20 @@ operand | placeholder | '$.' pathOrMetadata | '(' expression ')' + | notExpression + | exclusiveExpression + | letExpression + | whenExpression ; +notExpression: 'not' '(' expression ')'; + +exclusiveExpression: 'exclusive' '(' expression (',' expression)+ ')'; + +letExpression: 'let' '(' variableDefinition (',' variableDefinition)* ')' 'then' '(' expression ')'; + +whenExpression: 'when' '(' expressionMapping (',' expressionMapping)* ',' 'default' '=>' expression ')'; + functionCall : NAME_IDENTIFIER '(' expression (',' expression)* ')' ; diff --git a/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java b/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java index 2a0ab16..18fa267 100644 --- a/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java +++ b/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java @@ -86,14 +86,12 @@ public AbstractPart visitWhenExpression(ConditionParser.WhenExpressionContext ct @Override public AbstractPart visitAndExpression(ConditionParser.AndExpressionContext ctx) { - // If there's only one basicExpression and no 'and' operators, just pass through - if (ctx.basicExpression().size() == 1) { - return visit(ctx.basicExpression(0)); + if (ctx.comparisonExpression().size() == 1) { + return visit(ctx.comparisonExpression(0)); } List expressions = new ArrayList<>(); - // iterate through each sub-expression - for (ConditionParser.BasicExpressionContext ec : ctx.basicExpression()) { + for (ConditionParser.ComparisonExpressionContext ec : ctx.comparisonExpression()) { ExpressionContainer expr = (ExpressionContainer) visit(ec); if (expr == null) return null; @@ -146,12 +144,6 @@ public AbstractPart visitExclusiveExpression(ConditionParser.ExclusiveExpression ExpressionContainer.ExprPartsOperation.EXCLUSIVE_STRUCTURE); } - @Override - public AbstractPart visitComparisonExpressionWrapper(ConditionParser.ComparisonExpressionWrapperContext ctx) { - // Pass through the wrapper - return visit(ctx.comparisonExpression()); - } - @Override public AbstractPart visitBitwiseExpressionWrapper(ConditionParser.BitwiseExpressionWrapperContext ctx) { // Pass through the wrapper diff --git a/src/test/java/com/aerospike/dsl/expression/ControlStructuresTests.java b/src/test/java/com/aerospike/dsl/expression/ControlStructuresTests.java index 50bf1ca..bf0ca66 100644 --- a/src/test/java/com/aerospike/dsl/expression/ControlStructuresTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ControlStructuresTests.java @@ -67,6 +67,48 @@ void whenWithMultipleDeclarations() { expected); } + @Test + void whenWithArithmeticBranchesComparedToValue() { + Exp expected = Exp.eq( + Exp.cond( + Exp.eq(Exp.intBin("A"), Exp.val(0)), Exp.add(Exp.intBin("D"), Exp.intBin("E")), + Exp.eq(Exp.intBin("A"), Exp.val(1)), Exp.sub(Exp.intBin("D"), Exp.intBin("E")), + Exp.eq(Exp.intBin("A"), Exp.val(2)), Exp.mul(Exp.intBin("D"), Exp.intBin("E")), + Exp.val(-1) + ), + Exp.val(2) + ); + + TestUtils.parseFilterExpressionAndCompare( + ExpressionContext.of("when($.A == 0 => $.D + $.E, " + + "$.A == 1 => $.D - $.E, " + + "$.A == 2 => $.D * $.E, " + + "default => -1) == 2"), + expected); + } + + @Test + void notWhenWithArithmeticBranches() { + Exp expected = Exp.not( + Exp.eq( + Exp.cond( + Exp.eq(Exp.intBin("A"), Exp.val(0)), Exp.add(Exp.intBin("D"), Exp.intBin("E")), + Exp.eq(Exp.intBin("A"), Exp.val(1)), Exp.sub(Exp.intBin("D"), Exp.intBin("E")), + Exp.eq(Exp.intBin("A"), Exp.val(2)), Exp.mul(Exp.intBin("D"), Exp.intBin("E")), + Exp.val(-1) + ), + Exp.val(2) + ) + ); + + TestUtils.parseFilterExpressionAndCompare( + ExpressionContext.of("not(when($.A == 0 => $.D + $.E, " + + "$.A == 1 => $.D - $.E, " + + "$.A == 2 => $.D * $.E, " + + "default => -1) == 2)"), + expected); + } + @Test void withMultipleVariablesDefinitionAndUsage() { Exp expected = Exp.let( From f36e60eab9f626f95c2643babfe968a97c4598c8 Mon Sep 17 00:00:00 2001 From: Andrey G Date: Fri, 27 Mar 2026 00:10:14 +0100 Subject: [PATCH 3/7] Propagate ANTLR error message --- .../dsl/impl/DSLParserErrorListener.java | 10 ++-- .../com/aerospike/dsl/impl/DSLParserImpl.java | 5 +- .../dsl/expression/BinExpressionsTests.java | 3 +- .../dsl/expression/ExplicitTypesTests.java | 21 +++++--- .../dsl/expression/ListExpressionsTests.java | 3 +- .../expression/LogicalExpressionsTests.java | 15 ++++-- .../dsl/expression/NumericLiteralsTests.java | 9 ++-- .../dsl/expression/SyntaxErrorTests.java | 54 ++++++++++++------- .../dsl/filter/ExplicitTypesFiltersTests.java | 14 +++-- 9 files changed, 88 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/aerospike/dsl/impl/DSLParserErrorListener.java b/src/main/java/com/aerospike/dsl/impl/DSLParserErrorListener.java index e462cbb..a3024f2 100644 --- a/src/main/java/com/aerospike/dsl/impl/DSLParserErrorListener.java +++ b/src/main/java/com/aerospike/dsl/impl/DSLParserErrorListener.java @@ -10,7 +10,7 @@ class DSLParserErrorListener extends BaseErrorListener { - private boolean hasErrors; + private String firstErrorMessage; @Override public void syntaxError(Recognizer recognizer, Object offendingSymbol, @@ -34,10 +34,12 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, throw new DslParseException("Invalid float literal: " + token.getText() + nextText); } } - hasErrors = true; + if (firstErrorMessage == null) { + firstErrorMessage = msg; + } } - boolean hasErrors() { - return hasErrors; + String getFirstErrorMessage() { + return firstErrorMessage; } } diff --git a/src/main/java/com/aerospike/dsl/impl/DSLParserImpl.java b/src/main/java/com/aerospike/dsl/impl/DSLParserImpl.java index d3165c3..2d348a8 100644 --- a/src/main/java/com/aerospike/dsl/impl/DSLParserImpl.java +++ b/src/main/java/com/aerospike/dsl/impl/DSLParserImpl.java @@ -64,8 +64,9 @@ private ParseTree getParseTree(String input) { DSLParserErrorListener errorListener = new DSLParserErrorListener(); parser.addErrorListener(errorListener); ParseTree tree = parser.parse(); - if (errorListener.hasErrors()) { - throw new DslParseException("Could not parse given DSL expression input"); + String errorMessage = errorListener.getFirstErrorMessage(); + if (errorMessage != null) { + throw new DslParseException("Could not parse given DSL expression input: " + errorMessage); } return tree; } diff --git a/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java index 2ec5475..448e5d2 100644 --- a/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java @@ -66,7 +66,8 @@ void binNotEquals() { void negativeStringBinEquals() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.strBin == yes"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("mismatched input ''"); } // TODO: Will be handled in FMWK-486 diff --git a/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java b/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java index e5af647..37d8868 100644 --- a/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java @@ -48,7 +48,8 @@ void stringComparisonNegativeTest() { TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.stringBin1.get(type: STRING) == yes"), Exp.eq(Exp.stringBin("stringBin1"), Exp.val("yes")))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("mismatched input ''"); } @Test @@ -130,7 +131,8 @@ void listComparison_constantOnRightSide_NegativeTest() { Exp.eq(Exp.listBin("listBin1"), Exp.val(List.of("yes", "of course")))) ) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("mismatched input ','"); } @Test @@ -173,7 +175,8 @@ void listComparison_constantOnLeftSide_NegativeTest() { Exp.eq(Exp.val(List.of("yes", "of course")), Exp.listBin("listBin1"))) ) .isInstanceOf(DslParseException.class) - .hasMessage("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @SuppressWarnings("unchecked") @@ -243,7 +246,8 @@ void mapComparison_constantOnRightSide_NegativeTest() { Exp.eq(Exp.mapBin("mapBin1"), Exp.val(treeMapOf("yes", "of course")))) ) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("extraneous input 'yes'"); assertThatThrownBy(() -> TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.get(type: MAP) == ['yes', 'of course']"), @@ -258,7 +262,8 @@ void mapComparison_constantOnRightSide_NegativeTest() { Exp.eq(Exp.mapBin("mapBin1"), Exp.val(List.of("yes", "of course")))) ) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("extraneous input '['"); } @Test @@ -312,7 +317,8 @@ void mapComparison_constantOnLeftSide_NegativeTest() { Exp.eq(Exp.mapBin("mapBin1"), Exp.val(treeMapOf("of course", "yes")))) ) .isInstanceOf(DslParseException.class) - .hasMessage("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); assertThatThrownBy(() -> TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("['yes', 'of course'] == $.mapBin1.get(type: MAP)"), // incorrect: must be {} @@ -327,7 +333,8 @@ void mapComparison_constantOnLeftSide_NegativeTest() { Exp.eq(Exp.val(List.of("yes", "of course")), Exp.mapBin("mapBin1"))) ) .isInstanceOf(DslParseException.class) - .hasMessage("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @Test diff --git a/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java index c13e068..b8df927 100644 --- a/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java @@ -306,7 +306,8 @@ void listBinElementCount() { void negativeSyntaxList() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.listBin1.[stringValue] == 100"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } //@Test diff --git a/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java index db035bf..f766381 100644 --- a/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java @@ -94,19 +94,23 @@ void flatHierarchyAnd() { void negativeSyntaxLogicalOperators() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("($.intBin1 > 100 and ($.intBin2 > 100) or"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("and ($.intBin1 > 100 and ($.intBin2 > 100)"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("extraneous input 'and'"); assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("($.intBin1 > 100 and ($.intBin2 > 100) not"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("($.intBin1 > 100 and ($.intBin2 > 100) exclusive"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @Test @@ -115,6 +119,7 @@ void negativeBinLogicalExclusiveWithOneParam() { Exp.exclusive( Exp.eq(Exp.stringBin("hand"), Exp.val("hook"))))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } } diff --git a/src/test/java/com/aerospike/dsl/expression/NumericLiteralsTests.java b/src/test/java/com/aerospike/dsl/expression/NumericLiteralsTests.java index fd8d777..dbd26ad 100644 --- a/src/test/java/com/aerospike/dsl/expression/NumericLiteralsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/NumericLiteralsTests.java @@ -248,7 +248,8 @@ void invalidHexDigits() { // 0xGG is not valid hex -- lexer produces INT(0) + NAME_IDENTIFIER(xGG) assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("0xGG == 0"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @Test @@ -256,7 +257,8 @@ void invalidBinaryDigits() { // 0b2 is not valid binary -- lexer produces INT(0) + NAME_IDENTIFIER(b2) assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("0b2 == 0"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @Test @@ -264,7 +266,8 @@ void trailingDotFloat() { // "10." is not valid FLOAT -- requires digits after dot assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("10. == 10.0"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @Test diff --git a/src/test/java/com/aerospike/dsl/expression/SyntaxErrorTests.java b/src/test/java/com/aerospike/dsl/expression/SyntaxErrorTests.java index 4ab5921..b17df67 100644 --- a/src/test/java/com/aerospike/dsl/expression/SyntaxErrorTests.java +++ b/src/test/java/com/aerospike/dsl/expression/SyntaxErrorTests.java @@ -17,21 +17,24 @@ class SyntaxErrorTests { void negNonsensePathFunction() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("1.0 == $.f1.nonsense()"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("extraneous input '()'"); } @Test void negNonsensePathFunctionOnLeft() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.f1.nonsense() == 1.0"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("mismatched input '()'"); } @Test void negTrailingGarbageTokens() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.intBin1 > 100 garbage"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("extraneous input 'garbage'"); } // --- Positive syntax boundary --- @@ -54,7 +57,8 @@ void negVarBareNameInThenBody() { ExpressionContext.of( "let(x = 5, y = ($.bin.get(type: INT) in ${x})) then (y == true)"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @Test @@ -63,7 +67,8 @@ void negVarDollarNoInThenBody() { ExpressionContext.of( "let(x = 5, y = ($.bin.get(type: INT) in ${x})) then ($y == true)"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @Test @@ -72,7 +77,8 @@ void negVarMissingCloseBrace() { ExpressionContext.of( "let(x = 5, y = ($.bin.get(type: INT) in ${x)) then (${y} == true)"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @Test @@ -81,7 +87,8 @@ void negVarMissingDollarAndOpen() { ExpressionContext.of( "let(x = 5, y = ($.bin.get(type: INT) in x})) then (${y} == true)"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @Test @@ -90,7 +97,8 @@ void negVarBareNameInLetExpr() { ExpressionContext.of( "let(x = 5, y = ($.bin.get(type: INT) in x)) then (${y} == true)"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @Test @@ -99,7 +107,8 @@ void negVarDoubleOpenBrace() { ExpressionContext.of( "let(x = 5, y = ($.bin.get(type: INT) in ${{x})) then (${y} == true)"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @Test @@ -108,7 +117,8 @@ void negVarDoubleCloseBrace() { ExpressionContext.of( "let(x = 5, y = ($.bin.get(type: INT) in ${x}})) then (${y} == true)"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @Test @@ -117,7 +127,8 @@ void negVarDoubleDollarSign() { ExpressionContext.of( "let(x = 5, y = ($.bin.get(type: INT) in $${x})) then (${y} == true)"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } // --- Mismatched delimiters --- @@ -126,7 +137,8 @@ void negVarDoubleDollarSign() { void negOrphanedParentheses() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.intBin1 > ()"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("mismatched input '()'"); } @Test @@ -135,7 +147,8 @@ void negGetFuncMissingCloseParen() { ExpressionContext.of( "let(x = 5, y = ($.bin.get(type: INT in ${x})) then (${y} == true)"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } // --- Malformed let structure --- @@ -145,7 +158,8 @@ void negLetMissingThen() { assertThatThrownBy(() -> parseFilterExp( ExpressionContext.of("let(x = 5) (${x} + 1)"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @Test @@ -153,7 +167,8 @@ void negLetEmptyDefs() { assertThatThrownBy(() -> parseFilterExp( ExpressionContext.of("let() then (1 + 2)"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @Test @@ -161,7 +176,8 @@ void negLetMissingEquals() { assertThatThrownBy(() -> parseFilterExp( ExpressionContext.of("let(x 5) then (${x} + 1)"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } // --- Malformed when structure --- @@ -171,7 +187,8 @@ void negWhenMissingDefault() { assertThatThrownBy(() -> parseFilterExp( ExpressionContext.of("when($.x == 1 => \"a\")"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @Test @@ -179,6 +196,7 @@ void negWhenMissingArrow() { assertThatThrownBy(() -> parseFilterExp( ExpressionContext.of("when($.x == 1 \"a\", default => \"b\")"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } } diff --git a/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java b/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java index 77c02c1..db303d2 100644 --- a/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java +++ b/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java @@ -58,7 +58,8 @@ void stringComparisonNegativeTest() { // A String constant must be quoted assertThatThrownBy(() -> parseFilter(ExpressionContext.of("$.stringBin1.get(type: STRING) == yes"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("mismatched input ''"); } @Test @@ -104,7 +105,8 @@ void listComparison_constantOnRightSide() { void listComparison_constantOnRightSide_NegativeTest() { assertThatThrownBy(() -> parseFilter(ExpressionContext.of("$.listBin1.get(type: LIST) == [yes, of course]"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("mismatched input ','"); } @Test @@ -116,7 +118,7 @@ void listComparison_constantOnLeftSide() { void listComparison_constantOnLeftSide_NegativeTest() { assertThatThrownBy(() -> parseFilter(ExpressionContext.of("[yes, of course] == $.listBin1.get(type: LIST)"))) .isInstanceOf(DslParseException.class) - .hasMessage("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input"); } @Test @@ -128,7 +130,8 @@ void mapComparison_constantOnRightSide() { void mapComparison_constantOnRightSide_NegativeTest() { assertThatThrownBy(() -> parseFilter(ExpressionContext.of("$.mapBin1.get(type: MAP) == {yes, of course}"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("extraneous input 'yes'"); } @Test @@ -140,7 +143,8 @@ void mapComparison_constantOnLeftSide() { void mapComparison_constantOnLeftSide_NegativeTest() { assertThatThrownBy(() -> parseFilter(ExpressionContext.of("{yes, of course} == $.mapBin1.get(type: MAP)"))) .isInstanceOf(DslParseException.class) - .hasMessage("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("no viable alternative at input"); } @Test From 7bcf85a26869c8f2cce6d5e6b647d7ec4b89c7fc Mon Sep 17 00:00:00 2001 From: Andrey G Date: Fri, 27 Mar 2026 00:28:29 +0100 Subject: [PATCH 4/7] Propagate ANTLR error message from lexer and parser combined --- .../dsl/impl/DSLParserErrorListener.java | 22 +++++++--- .../com/aerospike/dsl/impl/DSLParserImpl.java | 15 +++++-- .../dsl/expression/BinExpressionsTests.java | 2 +- .../dsl/expression/ExplicitTypesTests.java | 14 +++---- .../dsl/expression/ListExpressionsTests.java | 2 +- .../expression/LogicalExpressionsTests.java | 10 ++--- .../dsl/expression/NumericLiteralsTests.java | 6 +-- .../dsl/expression/SyntaxErrorTests.java | 40 ++++++++++--------- .../dsl/filter/ExplicitTypesFiltersTests.java | 11 ++--- 9 files changed, 74 insertions(+), 48 deletions(-) diff --git a/src/main/java/com/aerospike/dsl/impl/DSLParserErrorListener.java b/src/main/java/com/aerospike/dsl/impl/DSLParserErrorListener.java index a3024f2..5170343 100644 --- a/src/main/java/com/aerospike/dsl/impl/DSLParserErrorListener.java +++ b/src/main/java/com/aerospike/dsl/impl/DSLParserErrorListener.java @@ -10,7 +10,8 @@ class DSLParserErrorListener extends BaseErrorListener { - private String firstErrorMessage; + private String firstLexerError; + private String firstParserError; @Override public void syntaxError(Recognizer recognizer, Object offendingSymbol, @@ -34,12 +35,23 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, throw new DslParseException("Invalid float literal: " + token.getText() + nextText); } } - if (firstErrorMessage == null) { - firstErrorMessage = msg; + if (recognizer instanceof Parser) { + if (firstParserError == null) { + firstParserError = msg; + } + } else { + if (firstLexerError == null) { + firstLexerError = msg; + } } } - String getFirstErrorMessage() { - return firstErrorMessage; + String getErrorMessage() { + if (firstLexerError != null && firstParserError != null) { + return "[Lexer] " + firstLexerError + "; [Parser] " + firstParserError; + } + if (firstLexerError != null) return "[Lexer] " + firstLexerError; + if (firstParserError != null) return "[Parser] " + firstParserError; + return null; } } diff --git a/src/main/java/com/aerospike/dsl/impl/DSLParserImpl.java b/src/main/java/com/aerospike/dsl/impl/DSLParserImpl.java index 2d348a8..74b21ee 100644 --- a/src/main/java/com/aerospike/dsl/impl/DSLParserImpl.java +++ b/src/main/java/com/aerospike/dsl/impl/DSLParserImpl.java @@ -57,14 +57,23 @@ public CTX[] parseCTX(String pathToCtx) { } } - private ParseTree getParseTree(String input) { + private ConditionParser createParser(String input, DSLParserErrorListener errorListener) { ConditionLexer lexer = new ConditionLexer(CharStreams.fromString(input)); + lexer.removeErrorListeners(); + lexer.addErrorListener(errorListener); CommonTokenStream tokenStream = new CommonTokenStream(lexer); ConditionParser parser = new ConditionParser(tokenStream); - DSLParserErrorListener errorListener = new DSLParserErrorListener(); + parser.removeErrorListeners(); parser.addErrorListener(errorListener); + return parser; + } + + private ParseTree getParseTree(String input) { + DSLParserErrorListener errorListener = new DSLParserErrorListener(); + ConditionParser parser = createParser(input, errorListener); ParseTree tree = parser.parse(); - String errorMessage = errorListener.getFirstErrorMessage(); + + String errorMessage = errorListener.getErrorMessage(); if (errorMessage != null) { throw new DslParseException("Could not parse given DSL expression input: " + errorMessage); } diff --git a/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java index 448e5d2..bde8d73 100644 --- a/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java @@ -67,7 +67,7 @@ void negativeStringBinEquals() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.strBin == yes"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("mismatched input ''"); + .hasMessageContaining("[Parser] mismatched input ''"); } // TODO: Will be handled in FMWK-486 diff --git a/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java b/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java index 37d8868..a9a36a4 100644 --- a/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java @@ -49,7 +49,7 @@ void stringComparisonNegativeTest() { Exp.eq(Exp.stringBin("stringBin1"), Exp.val("yes")))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("mismatched input ''"); + .hasMessageContaining("[Parser] mismatched input ''"); } @Test @@ -132,7 +132,7 @@ void listComparison_constantOnRightSide_NegativeTest() { ) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("mismatched input ','"); + .hasMessageContaining("[Parser] mismatched input ','"); } @Test @@ -176,7 +176,7 @@ void listComparison_constantOnLeftSide_NegativeTest() { ) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } @SuppressWarnings("unchecked") @@ -247,7 +247,7 @@ void mapComparison_constantOnRightSide_NegativeTest() { ) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("extraneous input 'yes'"); + .hasMessageContaining("[Parser] extraneous input 'yes'"); assertThatThrownBy(() -> TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.get(type: MAP) == ['yes', 'of course']"), @@ -263,7 +263,7 @@ void mapComparison_constantOnRightSide_NegativeTest() { ) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("extraneous input '['"); + .hasMessageContaining("[Parser] extraneous input '['"); } @Test @@ -318,7 +318,7 @@ void mapComparison_constantOnLeftSide_NegativeTest() { ) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); assertThatThrownBy(() -> TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("['yes', 'of course'] == $.mapBin1.get(type: MAP)"), // incorrect: must be {} @@ -334,7 +334,7 @@ void mapComparison_constantOnLeftSide_NegativeTest() { ) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test diff --git a/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java index b8df927..f889247 100644 --- a/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java @@ -307,7 +307,7 @@ void negativeSyntaxList() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.listBin1.[stringValue] == 100"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } //@Test diff --git a/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java index f766381..d18005c 100644 --- a/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java @@ -95,22 +95,22 @@ void negativeSyntaxLogicalOperators() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("($.intBin1 > 100 and ($.intBin2 > 100) or"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("and ($.intBin1 > 100 and ($.intBin2 > 100)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("extraneous input 'and'"); + .hasMessageContaining("[Parser] extraneous input 'and'"); assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("($.intBin1 > 100 and ($.intBin2 > 100) not"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("($.intBin1 > 100 and ($.intBin2 > 100) exclusive"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test @@ -120,6 +120,6 @@ void negativeBinLogicalExclusiveWithOneParam() { Exp.eq(Exp.stringBin("hand"), Exp.val("hook"))))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } } diff --git a/src/test/java/com/aerospike/dsl/expression/NumericLiteralsTests.java b/src/test/java/com/aerospike/dsl/expression/NumericLiteralsTests.java index dbd26ad..846efce 100644 --- a/src/test/java/com/aerospike/dsl/expression/NumericLiteralsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/NumericLiteralsTests.java @@ -249,7 +249,7 @@ void invalidHexDigits() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("0xGG == 0"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test @@ -258,7 +258,7 @@ void invalidBinaryDigits() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("0b2 == 0"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test @@ -267,7 +267,7 @@ void trailingDotFloat() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("10. == 10.0"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test diff --git a/src/test/java/com/aerospike/dsl/expression/SyntaxErrorTests.java b/src/test/java/com/aerospike/dsl/expression/SyntaxErrorTests.java index b17df67..ceb35fa 100644 --- a/src/test/java/com/aerospike/dsl/expression/SyntaxErrorTests.java +++ b/src/test/java/com/aerospike/dsl/expression/SyntaxErrorTests.java @@ -18,7 +18,7 @@ void negNonsensePathFunction() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("1.0 == $.f1.nonsense()"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("extraneous input '()'"); + .hasMessageContaining("[Parser] extraneous input '()'"); } @Test @@ -26,7 +26,7 @@ void negNonsensePathFunctionOnLeft() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.f1.nonsense() == 1.0"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("mismatched input '()'"); + .hasMessageContaining("[Parser] mismatched input '()'"); } @Test @@ -34,7 +34,7 @@ void negTrailingGarbageTokens() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.intBin1 > 100 garbage"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("extraneous input 'garbage'"); + .hasMessageContaining("[Parser] extraneous input 'garbage'"); } // --- Positive syntax boundary --- @@ -58,7 +58,7 @@ void negVarBareNameInThenBody() { "let(x = 5, y = ($.bin.get(type: INT) in ${x})) then (y == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test @@ -68,7 +68,8 @@ void negVarDollarNoInThenBody() { "let(x = 5, y = ($.bin.get(type: INT) in ${x})) then ($y == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Lexer] token recognition error at:") + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test @@ -78,7 +79,8 @@ void negVarMissingCloseBrace() { "let(x = 5, y = ($.bin.get(type: INT) in ${x)) then (${y} == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Lexer] token recognition error at:") + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test @@ -88,7 +90,7 @@ void negVarMissingDollarAndOpen() { "let(x = 5, y = ($.bin.get(type: INT) in x})) then (${y} == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test @@ -98,7 +100,7 @@ void negVarBareNameInLetExpr() { "let(x = 5, y = ($.bin.get(type: INT) in x)) then (${y} == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test @@ -108,7 +110,8 @@ void negVarDoubleOpenBrace() { "let(x = 5, y = ($.bin.get(type: INT) in ${{x})) then (${y} == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Lexer] token recognition error at:") + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test @@ -118,7 +121,7 @@ void negVarDoubleCloseBrace() { "let(x = 5, y = ($.bin.get(type: INT) in ${x}})) then (${y} == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test @@ -128,7 +131,8 @@ void negVarDoubleDollarSign() { "let(x = 5, y = ($.bin.get(type: INT) in $${x})) then (${y} == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Lexer] token recognition error at:") + .hasMessageContaining("[Parser] no viable alternative at input"); } // --- Mismatched delimiters --- @@ -138,7 +142,7 @@ void negOrphanedParentheses() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.intBin1 > ()"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("mismatched input '()'"); + .hasMessageContaining("[Parser] mismatched input '()'"); } @Test @@ -148,7 +152,7 @@ void negGetFuncMissingCloseParen() { "let(x = 5, y = ($.bin.get(type: INT in ${x})) then (${y} == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } // --- Malformed let structure --- @@ -159,7 +163,7 @@ void negLetMissingThen() { ExpressionContext.of("let(x = 5) (${x} + 1)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test @@ -168,7 +172,7 @@ void negLetEmptyDefs() { ExpressionContext.of("let() then (1 + 2)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test @@ -177,7 +181,7 @@ void negLetMissingEquals() { ExpressionContext.of("let(x 5) then (${x} + 1)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } // --- Malformed when structure --- @@ -188,7 +192,7 @@ void negWhenMissingDefault() { ExpressionContext.of("when($.x == 1 => \"a\")"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test @@ -197,6 +201,6 @@ void negWhenMissingArrow() { ExpressionContext.of("when($.x == 1 \"a\", default => \"b\")"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } } diff --git a/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java b/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java index db303d2..fef3446 100644 --- a/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java +++ b/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java @@ -59,7 +59,7 @@ void stringComparisonNegativeTest() { assertThatThrownBy(() -> parseFilter(ExpressionContext.of("$.stringBin1.get(type: STRING) == yes"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("mismatched input ''"); + .hasMessageContaining("[Parser] mismatched input ''"); } @Test @@ -106,7 +106,7 @@ void listComparison_constantOnRightSide_NegativeTest() { assertThatThrownBy(() -> parseFilter(ExpressionContext.of("$.listBin1.get(type: LIST) == [yes, of course]"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("mismatched input ','"); + .hasMessageContaining("[Parser] mismatched input ','"); } @Test @@ -118,7 +118,8 @@ void listComparison_constantOnLeftSide() { void listComparison_constantOnLeftSide_NegativeTest() { assertThatThrownBy(() -> parseFilter(ExpressionContext.of("[yes, of course] == $.listBin1.get(type: LIST)"))) .isInstanceOf(DslParseException.class) - .hasMessageContaining("Could not parse given DSL expression input"); + .hasMessageContaining("Could not parse given DSL expression input") + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test @@ -131,7 +132,7 @@ void mapComparison_constantOnRightSide_NegativeTest() { assertThatThrownBy(() -> parseFilter(ExpressionContext.of("$.mapBin1.get(type: MAP) == {yes, of course}"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("extraneous input 'yes'"); + .hasMessageContaining("[Parser] extraneous input 'yes'"); } @Test @@ -144,7 +145,7 @@ void mapComparison_constantOnLeftSide_NegativeTest() { assertThatThrownBy(() -> parseFilter(ExpressionContext.of("{yes, of course} == $.mapBin1.get(type: MAP)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input"); } @Test From e18601a6bad1237b07ef72b3d978b57aef7efbf2 Mon Sep 17 00:00:00 2001 From: Andrey G Date: Fri, 27 Mar 2026 01:20:30 +0100 Subject: [PATCH 5/7] Propagate char position parameter from ANTLR error message --- .../dsl/impl/DSLParserErrorListener.java | 4 +- .../dsl/expression/BinExpressionsTests.java | 3 +- .../dsl/expression/ExplicitTypesTests.java | 21 ++++-- .../dsl/expression/ListExpressionsTests.java | 3 +- .../expression/LogicalExpressionsTests.java | 15 +++-- .../dsl/expression/NumericLiteralsTests.java | 9 ++- .../dsl/expression/SyntaxErrorTests.java | 66 ++++++++++++------- .../dsl/filter/ExplicitTypesFiltersTests.java | 15 +++-- 8 files changed, 90 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/aerospike/dsl/impl/DSLParserErrorListener.java b/src/main/java/com/aerospike/dsl/impl/DSLParserErrorListener.java index 5170343..bf9fc1a 100644 --- a/src/main/java/com/aerospike/dsl/impl/DSLParserErrorListener.java +++ b/src/main/java/com/aerospike/dsl/impl/DSLParserErrorListener.java @@ -37,11 +37,11 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, } if (recognizer instanceof Parser) { if (firstParserError == null) { - firstParserError = msg; + firstParserError = msg + " at character " + charPositionInLine; } } else { if (firstLexerError == null) { - firstLexerError = msg; + firstLexerError = msg + " at character " + charPositionInLine; } } } diff --git a/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java index bde8d73..fd5e8f2 100644 --- a/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/BinExpressionsTests.java @@ -67,7 +67,8 @@ void negativeStringBinEquals() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.strBin == yes"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] mismatched input ''"); + .hasMessageContaining("[Parser] mismatched input ''") + .hasMessageContaining("at character 15"); } // TODO: Will be handled in FMWK-486 diff --git a/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java b/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java index a9a36a4..6b2da13 100644 --- a/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ExplicitTypesTests.java @@ -49,7 +49,8 @@ void stringComparisonNegativeTest() { Exp.eq(Exp.stringBin("stringBin1"), Exp.val("yes")))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] mismatched input ''"); + .hasMessageContaining("[Parser] mismatched input ''") + .hasMessageContaining("at character 37"); } @Test @@ -132,7 +133,8 @@ void listComparison_constantOnRightSide_NegativeTest() { ) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] mismatched input ','"); + .hasMessageContaining("[Parser] mismatched input ','") + .hasMessageContaining("at character 34"); } @Test @@ -176,7 +178,8 @@ void listComparison_constantOnLeftSide_NegativeTest() { ) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 4"); } @SuppressWarnings("unchecked") @@ -247,7 +250,8 @@ void mapComparison_constantOnRightSide_NegativeTest() { ) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] extraneous input 'yes'"); + .hasMessageContaining("[Parser] extraneous input 'yes'") + .hasMessageContaining("at character 29"); assertThatThrownBy(() -> TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.get(type: MAP) == ['yes', 'of course']"), @@ -263,7 +267,8 @@ void mapComparison_constantOnRightSide_NegativeTest() { ) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] extraneous input '['"); + .hasMessageContaining("[Parser] extraneous input '['") + .hasMessageContaining("at character 29"); } @Test @@ -318,7 +323,8 @@ void mapComparison_constantOnLeftSide_NegativeTest() { ) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 1"); assertThatThrownBy(() -> TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("['yes', 'of course'] == $.mapBin1.get(type: MAP)"), // incorrect: must be {} @@ -334,7 +340,8 @@ void mapComparison_constantOnLeftSide_NegativeTest() { ) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 1"); } @Test diff --git a/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java index f889247..14fc23c 100644 --- a/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java @@ -307,7 +307,8 @@ void negativeSyntaxList() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.listBin1.[stringValue] == 100"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 12"); } //@Test diff --git a/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java index d18005c..08b6af4 100644 --- a/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/LogicalExpressionsTests.java @@ -95,22 +95,26 @@ void negativeSyntaxLogicalOperators() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("($.intBin1 > 100 and ($.intBin2 > 100) or"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 41"); assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("and ($.intBin1 > 100 and ($.intBin2 > 100)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] extraneous input 'and'"); + .hasMessageContaining("[Parser] extraneous input 'and'") + .hasMessageContaining("at character 0"); assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("($.intBin1 > 100 and ($.intBin2 > 100) not"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 39"); assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("($.intBin1 > 100 and ($.intBin2 > 100) exclusive"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 39"); } @Test @@ -120,6 +124,7 @@ void negativeBinLogicalExclusiveWithOneParam() { Exp.eq(Exp.stringBin("hand"), Exp.val("hook"))))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 26"); } } diff --git a/src/test/java/com/aerospike/dsl/expression/NumericLiteralsTests.java b/src/test/java/com/aerospike/dsl/expression/NumericLiteralsTests.java index 846efce..781734b 100644 --- a/src/test/java/com/aerospike/dsl/expression/NumericLiteralsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/NumericLiteralsTests.java @@ -249,7 +249,8 @@ void invalidHexDigits() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("0xGG == 0"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 5"); } @Test @@ -258,7 +259,8 @@ void invalidBinaryDigits() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("0b2 == 0"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 4"); } @Test @@ -267,7 +269,8 @@ void trailingDotFloat() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("10. == 10.0"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 4"); } @Test diff --git a/src/test/java/com/aerospike/dsl/expression/SyntaxErrorTests.java b/src/test/java/com/aerospike/dsl/expression/SyntaxErrorTests.java index ceb35fa..f78217e 100644 --- a/src/test/java/com/aerospike/dsl/expression/SyntaxErrorTests.java +++ b/src/test/java/com/aerospike/dsl/expression/SyntaxErrorTests.java @@ -18,7 +18,8 @@ void negNonsensePathFunction() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("1.0 == $.f1.nonsense()"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] extraneous input '()'"); + .hasMessageContaining("[Parser] extraneous input '()'") + .hasMessageContaining("at character 20"); } @Test @@ -26,7 +27,8 @@ void negNonsensePathFunctionOnLeft() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.f1.nonsense() == 1.0"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] mismatched input '()'"); + .hasMessageContaining("[Parser] mismatched input '()'") + .hasMessageContaining("at character 13"); } @Test @@ -34,7 +36,8 @@ void negTrailingGarbageTokens() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.intBin1 > 100 garbage"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] extraneous input 'garbage'"); + .hasMessageContaining("[Parser] extraneous input 'garbage'") + .hasMessageContaining("at character 16"); } // --- Positive syntax boundary --- @@ -58,7 +61,8 @@ void negVarBareNameInThenBody() { "let(x = 5, y = ($.bin.get(type: INT) in ${x})) then (y == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 55"); } @Test @@ -68,8 +72,10 @@ void negVarDollarNoInThenBody() { "let(x = 5, y = ($.bin.get(type: INT) in ${x})) then ($y == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Lexer] token recognition error at:") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Lexer] token recognition error at: '$y'") + .hasMessageContaining("at character 53") + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 56"); } @Test @@ -79,8 +85,10 @@ void negVarMissingCloseBrace() { "let(x = 5, y = ($.bin.get(type: INT) in ${x)) then (${y} == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Lexer] token recognition error at:") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Lexer] token recognition error at: '${x)'") + .hasMessageContaining("at character 40") + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 44"); } @Test @@ -90,7 +98,8 @@ void negVarMissingDollarAndOpen() { "let(x = 5, y = ($.bin.get(type: INT) in x})) then (${y} == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 41"); } @Test @@ -100,7 +109,8 @@ void negVarBareNameInLetExpr() { "let(x = 5, y = ($.bin.get(type: INT) in x)) then (${y} == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 41"); } @Test @@ -110,8 +120,10 @@ void negVarDoubleOpenBrace() { "let(x = 5, y = ($.bin.get(type: INT) in ${{x})) then (${y} == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Lexer] token recognition error at:") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Lexer] token recognition error at: '${{'") + .hasMessageContaining("at character 40") + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 44"); } @Test @@ -121,7 +133,8 @@ void negVarDoubleCloseBrace() { "let(x = 5, y = ($.bin.get(type: INT) in ${x}})) then (${y} == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 44"); } @Test @@ -131,8 +144,10 @@ void negVarDoubleDollarSign() { "let(x = 5, y = ($.bin.get(type: INT) in $${x})) then (${y} == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Lexer] token recognition error at:") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Lexer] token recognition error at: '$$'") + .hasMessageContaining("at character 40") + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 43"); } // --- Mismatched delimiters --- @@ -142,7 +157,8 @@ void negOrphanedParentheses() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.intBin1 > ()"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] mismatched input '()'"); + .hasMessageContaining("[Parser] mismatched input '()'") + .hasMessageContaining("at character 12"); } @Test @@ -152,7 +168,8 @@ void negGetFuncMissingCloseParen() { "let(x = 5, y = ($.bin.get(type: INT in ${x})) then (${y} == true)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 36"); } // --- Malformed let structure --- @@ -163,7 +180,8 @@ void negLetMissingThen() { ExpressionContext.of("let(x = 5) (${x} + 1)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 11"); } @Test @@ -172,7 +190,8 @@ void negLetEmptyDefs() { ExpressionContext.of("let() then (1 + 2)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 3"); } @Test @@ -181,7 +200,8 @@ void negLetMissingEquals() { ExpressionContext.of("let(x 5) then (${x} + 1)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 6"); } // --- Malformed when structure --- @@ -192,7 +212,8 @@ void negWhenMissingDefault() { ExpressionContext.of("when($.x == 1 => \"a\")"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 20"); } @Test @@ -201,6 +222,7 @@ void negWhenMissingArrow() { ExpressionContext.of("when($.x == 1 \"a\", default => \"b\")"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 14"); } } diff --git a/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java b/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java index fef3446..296c5e2 100644 --- a/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java +++ b/src/test/java/com/aerospike/dsl/filter/ExplicitTypesFiltersTests.java @@ -59,7 +59,8 @@ void stringComparisonNegativeTest() { assertThatThrownBy(() -> parseFilter(ExpressionContext.of("$.stringBin1.get(type: STRING) == yes"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] mismatched input ''"); + .hasMessageContaining("[Parser] mismatched input ''") + .hasMessageContaining("at character 37"); } @Test @@ -106,7 +107,8 @@ void listComparison_constantOnRightSide_NegativeTest() { assertThatThrownBy(() -> parseFilter(ExpressionContext.of("$.listBin1.get(type: LIST) == [yes, of course]"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] mismatched input ','"); + .hasMessageContaining("[Parser] mismatched input ','") + .hasMessageContaining("at character 34"); } @Test @@ -119,7 +121,8 @@ void listComparison_constantOnLeftSide_NegativeTest() { assertThatThrownBy(() -> parseFilter(ExpressionContext.of("[yes, of course] == $.listBin1.get(type: LIST)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 4"); } @Test @@ -132,7 +135,8 @@ void mapComparison_constantOnRightSide_NegativeTest() { assertThatThrownBy(() -> parseFilter(ExpressionContext.of("$.mapBin1.get(type: MAP) == {yes, of course}"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] extraneous input 'yes'"); + .hasMessageContaining("[Parser] extraneous input 'yes'") + .hasMessageContaining("at character 29"); } @Test @@ -145,7 +149,8 @@ void mapComparison_constantOnLeftSide_NegativeTest() { assertThatThrownBy(() -> parseFilter(ExpressionContext.of("{yes, of course} == $.mapBin1.get(type: MAP)"))) .isInstanceOf(DslParseException.class) .hasMessageContaining("Could not parse given DSL expression input") - .hasMessageContaining("[Parser] no viable alternative at input"); + .hasMessageContaining("[Parser] no viable alternative at input") + .hasMessageContaining("at character 1"); } @Test From 01697f4aa32cbfaf145e9937b49620ef09bed2b2 Mon Sep 17 00:00:00 2001 From: Andrey G Date: Fri, 27 Mar 2026 17:48:38 +0100 Subject: [PATCH 6/7] Change asInt() and asFloat() to always generate casting Exp --- .../dsl/parts/cdt/list/ListValue.java | 15 +- .../aerospike/dsl/parts/cdt/map/MapValue.java | 16 +- .../com/aerospike/dsl/parts/path/Path.java | 9 +- .../dsl/parts/path/PathFunction.java | 15 ++ .../aerospike/dsl/util/PathOperandUtils.java | 25 ++- .../visitor/ExpressionConditionVisitor.java | 47 +++-- .../aerospike/dsl/visitor/VisitorUtils.java | 11 +- .../dsl/expression/CastingTests.java | 182 ++++++++++++++++-- .../dsl/expression/InExplicitTypeTests.java | 8 +- .../dsl/expression/ListExpressionsTests.java | 3 - .../dsl/expression/MapExpressionsTests.java | 5 - 11 files changed, 269 insertions(+), 67 deletions(-) diff --git a/src/main/java/com/aerospike/dsl/parts/cdt/list/ListValue.java b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListValue.java index ed5064b..fca8d33 100644 --- a/src/main/java/com/aerospike/dsl/parts/cdt/list/ListValue.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListValue.java @@ -23,16 +23,17 @@ public static ListValue from(ConditionParser.ListValueContext ctx) { @Override public Exp constructExp(BasePath basePath, Exp.Type valueType, int cdtReturnType, CTX[] context) { - Exp valueExp = switch (valueType) { - case BOOL -> Exp.val((Boolean) value); - case STRING -> Exp.val((String) value); - case FLOAT -> Exp.val((Float) value); - default -> Exp.val((Integer) value); // for getByValue the default is INT - }; - return ListExp.getByValue(cdtReturnType, valueExp, Exp.bin(basePath.getBinPart().getBinName(), + return ListExp.getByValue(cdtReturnType, valueToExp(), Exp.bin(basePath.getBinPart().getBinName(), basePath.getBinType()), context); } + private Exp valueToExp() { + if (value instanceof Boolean b) return Exp.val(b); + if (value instanceof String s) return Exp.val(s); + if (value instanceof Float f) return Exp.val(f); + return Exp.val((Integer) value); + } + @Override public CTX getContext() { return CTX.listValue(Value.get(value)); diff --git a/src/main/java/com/aerospike/dsl/parts/cdt/map/MapValue.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapValue.java index 2cf1bdc..9351dcb 100644 --- a/src/main/java/com/aerospike/dsl/parts/cdt/map/MapValue.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapValue.java @@ -23,17 +23,17 @@ public static MapValue from(ConditionParser.MapValueContext ctx) { @Override public Exp constructExp(BasePath basePath, Exp.Type valueType, int cdtReturnType, CTX[] context) { - Exp valueExp = switch (valueType) { - case BOOL -> Exp.val((Boolean) value); - case STRING -> Exp.val((String) value); - case FLOAT -> Exp.val((Float) value); - default -> Exp.val((Integer) value); // for getByValue the default is INT - }; - - return MapExp.getByValue(cdtReturnType, valueExp, Exp.bin(basePath.getBinPart().getBinName(), + return MapExp.getByValue(cdtReturnType, valueToExp(), Exp.bin(basePath.getBinPart().getBinName(), basePath.getBinType()), context); } + private Exp valueToExp() { + if (value instanceof Boolean b) return Exp.val(b); + if (value instanceof String s) return Exp.val(s); + if (value instanceof Float f) return Exp.val(f); + return Exp.val((Integer) value); + } + @Override public CTX getContext() { return CTX.mapValue(Value.get(value)); diff --git a/src/main/java/com/aerospike/dsl/parts/path/Path.java b/src/main/java/com/aerospike/dsl/parts/path/Path.java index 744c2e3..94f5722 100644 --- a/src/main/java/com/aerospike/dsl/parts/path/Path.java +++ b/src/main/java/com/aerospike/dsl/parts/path/Path.java @@ -35,12 +35,15 @@ public Exp processPath(BasePath basePath, PathFunction pathFunction) { cdtReturnType = ((CdtPart) lastPathPart).getReturnType(pathFunction.getReturnParam()); } - if (lastPathPart != null) { // only if there are other parts except a bin - return switch (pathFunction.getPathFunctionType()) { - // CAST is the same as GET with a different type + if (lastPathPart != null) { + Exp exp = switch (pathFunction.getPathFunctionType()) { case GET, COUNT, CAST -> processGet(basePath, lastPathPart, valueType, cdtReturnType); case SIZE -> processSize(basePath, lastPathPart, valueType, cdtReturnType); }; + if (pathFunction.getPathFunctionType() == PathFunction.PathFunctionType.CAST && exp != null) { + exp = pathFunction.getBinType() == Exp.Type.FLOAT ? Exp.toFloat(exp) : Exp.toInt(exp); + } + return exp; } return null; } diff --git a/src/main/java/com/aerospike/dsl/parts/path/PathFunction.java b/src/main/java/com/aerospike/dsl/parts/path/PathFunction.java index 47e34d8..3563063 100644 --- a/src/main/java/com/aerospike/dsl/parts/path/PathFunction.java +++ b/src/main/java/com/aerospike/dsl/parts/path/PathFunction.java @@ -2,6 +2,7 @@ import com.aerospike.dsl.client.exp.Exp; import com.aerospike.dsl.parts.AbstractPart; +import com.aerospike.dsl.parts.ExpressionContainer.ExprPartsOperation; import lombok.Getter; @Getter @@ -25,6 +26,20 @@ public static Exp.Type castTypeToExpType(CastType castType) { }; } + public static Exp.Type castSourceExpType(CastType castType) { + return switch (castType) { + case INT -> Exp.Type.FLOAT; + case FLOAT -> Exp.Type.INT; + }; + } + + public static ExprPartsOperation castTypeToOperation(CastType castType) { + return switch (castType) { + case INT -> ExprPartsOperation.TO_INT; + case FLOAT -> ExprPartsOperation.TO_FLOAT; + }; + } + public enum ReturnParam { VALUE, INDEX, diff --git a/src/main/java/com/aerospike/dsl/util/PathOperandUtils.java b/src/main/java/com/aerospike/dsl/util/PathOperandUtils.java index a6179f8..8cac60c 100644 --- a/src/main/java/com/aerospike/dsl/util/PathOperandUtils.java +++ b/src/main/java/com/aerospike/dsl/util/PathOperandUtils.java @@ -39,7 +39,6 @@ public class PathOperandUtils { * @return The determined {@link Exp.Type} for the value */ public static Exp.Type processValueType(AbstractPart lastPathPart, PathFunction pathFunction) { - // there is always a path function with non-null function type and return param if (pathFunction.getBinType() == null) { if (isListTypeDesignator(lastPathPart)) { return Exp.Type.LIST; @@ -49,6 +48,16 @@ public static Exp.Type processValueType(AbstractPart lastPathPart, PathFunction return findValueType(lastPathPart, pathFunction.getPathFunctionType()); } } + if (pathFunction.getPathFunctionType() == PathFunction.PathFunctionType.CAST) { + // Value selectors (getByValue/getByValueList/getByValueRange) have no return-type + // parameter; their constructExp ignores valueType. The cast wrapping is applied + // afterward in Path.processPath(), so the source-type logic does not apply here. + if (isValueSelector(lastPathPart)) { + return TypeUtils.getDefaultType(lastPathPart); + } + PathFunction.CastType castType = PathFunction.CastType.valueOf(pathFunction.getBinType().toString()); + return PathFunction.castSourceExpType(castType); + } return Exp.Type.valueOf(pathFunction.getBinType().toString()); } @@ -226,6 +235,20 @@ private static boolean isMapTypeDesignator(AbstractPart cdtPart) { return cdtPart.getPartType() == MAP_PART && ((MapPart) cdtPart).getMapPartType().equals(MAP_TYPE_DESIGNATOR); } + private static boolean isValueSelector(AbstractPart cdtPart) { + if (cdtPart.getPartType() == LIST_PART) { + ListPart.ListPartType type = ((ListPart) cdtPart).getListPartType(); + return type == ListPart.ListPartType.VALUE || type == ListPart.ListPartType.VALUE_LIST + || type == ListPart.ListPartType.VALUE_RANGE; + } + if (cdtPart.getPartType() == MAP_PART) { + MapPart.MapPartType type = ((MapPart) cdtPart).getMapPartType(); + return type == MapPart.MapPartType.VALUE || type == MapPart.MapPartType.VALUE_LIST + || type == MapPart.MapPartType.VALUE_RANGE; + } + return false; + } + /** * Constructs an array of {@link CTX} (context) objects from a list of {@link AbstractPart} objects. * This is used to build the context for nested CDT operations. diff --git a/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java b/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java index 18fa267..9dde00a 100644 --- a/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java +++ b/src/main/java/com/aerospike/dsl/visitor/ExpressionConditionVisitor.java @@ -1013,30 +1013,39 @@ public AbstractPart visitVariable(ConditionParser.VariableContext ctx) { @Override public AbstractPart visitPath(ConditionParser.PathContext ctx) { BasePath basePath = (BasePath) visit(ctx.basePath()); - List cdtParts = basePath.getCdtParts(); - overrideWithPathFunction(basePath.getBinPart(), ctx); - - // if there are other parts except bin, get a corresponding Exp - if (!cdtParts.isEmpty() || ctx.pathFunction() != null && ctx.pathFunction().pathFunctionCount() != null) { - return new Path(basePath, ctx.pathFunction() == null - ? null - : (PathFunction) visit(ctx.pathFunction())); + PathFunction pathFunction = visitPathFunctionIfPresent(ctx); + + if (!basePath.getCdtParts().isEmpty() + || pathFunction != null && pathFunction.getPathFunctionType() == PathFunction.PathFunctionType.COUNT) { + return new Path(basePath, pathFunction); + } + + if (pathFunction != null && pathFunction.getPathFunctionType() == PathFunction.PathFunctionType.CAST) { + return buildBinCast(basePath.getBinPart(), pathFunction); } + + overrideBinWithPathFunction(basePath.getBinPart(), pathFunction); return basePath.getBinPart(); } - private void overrideWithPathFunction(BinPart binPart, ConditionParser.PathContext ctx) { - ConditionParser.PathFunctionContext pathFunctionContext = ctx.pathFunction(); + private PathFunction visitPathFunctionIfPresent(ConditionParser.PathContext ctx) { + return ctx.pathFunction() != null ? (PathFunction) visit(ctx.pathFunction()) : null; + } - // Override with path function (explicit get or cast) - if (pathFunctionContext != null) { - PathFunction pathFunction = (PathFunction) visit(pathFunctionContext); - if (pathFunction != null) { - Exp.Type type = pathFunction.getBinType(); - if (type != null) { - binPart.updateExp(type); - binPart.setTypeExplicitlySet(true); - } + private AbstractPart buildBinCast(BinPart binPart, PathFunction pathFunction) { + String castText = pathFunction.getBinType().toString(); + PathFunction.CastType castType = PathFunction.CastType.valueOf(castText); + binPart.updateExp(PathFunction.castSourceExpType(castType)); + binPart.setTypeExplicitlySet(true); + return new ExpressionContainer(binPart, PathFunction.castTypeToOperation(castType)); + } + + private void overrideBinWithPathFunction(BinPart binPart, PathFunction pathFunction) { + if (pathFunction != null) { + Exp.Type type = pathFunction.getBinType(); + if (type != null) { + binPart.updateExp(type); + binPart.setTypeExplicitlySet(true); } } } diff --git a/src/main/java/com/aerospike/dsl/visitor/VisitorUtils.java b/src/main/java/com/aerospike/dsl/visitor/VisitorUtils.java index 07ab640..fe56f5b 100644 --- a/src/main/java/com/aerospike/dsl/visitor/VisitorUtils.java +++ b/src/main/java/com/aerospike/dsl/visitor/VisitorUtils.java @@ -353,9 +353,14 @@ private static Exp getExpBinComparison(BinPart binPart, AbstractPart anotherPart binExp = Exp.bin(binPart.getBinName(), binType); yield anotherPart.getExp(); } - case EXPRESSION_CONTAINER, PATH_OPERAND, VARIABLE_OPERAND -> - // Can't validate with expression container - anotherPart.getExp(); + case EXPRESSION_CONTAINER -> { + Exp.Type resolvedType = resolveExpType(anotherPart); + if (resolvedType != null) { + validateComparableTypes(binPart.getExpType(), resolvedType); + } + yield anotherPart.getExp(); + } + case PATH_OPERAND, VARIABLE_OPERAND -> anotherPart.getExp(); case BIN_PART -> { // Both are bin parts validateComparableTypes(binPart.getExpType(), anotherPart.getExpType()); diff --git a/src/test/java/com/aerospike/dsl/expression/CastingTests.java b/src/test/java/com/aerospike/dsl/expression/CastingTests.java index e0a9789..19fe9fb 100644 --- a/src/test/java/com/aerospike/dsl/expression/CastingTests.java +++ b/src/test/java/com/aerospike/dsl/expression/CastingTests.java @@ -2,8 +2,14 @@ import com.aerospike.dsl.DslParseException; import com.aerospike.dsl.ExpressionContext; +import com.aerospike.dsl.client.cdt.ListReturnType; +import com.aerospike.dsl.client.cdt.MapReturnType; import com.aerospike.dsl.client.exp.Exp; +import com.aerospike.dsl.client.exp.ListExp; +import com.aerospike.dsl.client.exp.MapExp; import com.aerospike.dsl.util.TestUtils; +import com.aerospike.dsl.client.cdt.CTX; +import com.aerospike.dsl.client.Value; import org.junit.jupiter.api.Test; import static com.aerospike.dsl.util.TestUtils.parseFilterExp; @@ -11,20 +17,6 @@ public class CastingTests { - @Test - void floatToIntComparison() { - Exp expectedExp = Exp.gt(Exp.intBin("intBin1"), Exp.intBin("floatBin1")); - // Int is default - TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.intBin1 > $.floatBin1.asInt()"), expectedExp); - TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.intBin1.get(type: INT) > $.floatBin1.asInt()"), expectedExp); - } - - @Test - void intToFloatComparison() { - TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.intBin1.get(type: INT) > $.intBin2.asFloat()"), - Exp.gt(Exp.intBin("intBin1"), Exp.floatBin("intBin2"))); - } - @Test void negativeInvalidTypesComparison() { assertThatThrownBy(() -> parseFilterExp(ExpressionContext.of("$.stringBin1.get(type: STRING) > $.intBin2.asFloat()"))) @@ -99,4 +91,166 @@ void castToIntComparedToStringThrows() { .isInstanceOf(DslParseException.class) .hasMessageContaining("Cannot compare"); } + + // --- Explicit bin casting (asFloat/asInt produce Exp.toFloat/toInt wrappers) --- + + @Test + void binAsFloat() { + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.bin.asFloat() == 1.0"), + Exp.eq(Exp.toFloat(Exp.intBin("bin")), Exp.val(1.0))); + } + + @Test + void binAsInt() { + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.bin.asInt() == 1"), + Exp.eq(Exp.toInt(Exp.floatBin("bin")), Exp.val(1))); + } + + @Test + void binAsIntInComparison() { + Exp expectedExp = Exp.gt(Exp.intBin("intBin1"), Exp.toInt(Exp.floatBin("floatBin1"))); + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.intBin1 > $.floatBin1.asInt()"), expectedExp); + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.intBin1.get(type: INT) > $.floatBin1.asInt()"), expectedExp); + } + + @Test + void binAsFloatInComparison() { + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.intBin1.get(type: INT) > $.intBin2.asFloat()"), + Exp.gt(Exp.intBin("intBin1"), Exp.toFloat(Exp.intBin("intBin2")))); + } + + @Test + void binAsFloatInAddition() { + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.binA.asFloat() + 4.0 == 5.0"), + Exp.eq(Exp.add(Exp.toFloat(Exp.intBin("binA")), Exp.val(4.0)), Exp.val(5.0))); + } + + @Test + void binAsIntInSubtraction() { + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.binA.asInt() - 3 == 0"), + Exp.eq(Exp.sub(Exp.toInt(Exp.floatBin("binA")), Exp.val(3)), Exp.val(0))); + } + + // --- CDT list casting --- + + @Test + void listIndexAsInt() { + Exp expected = Exp.eq( + Exp.toInt(ListExp.getByIndex(ListReturnType.VALUE, Exp.Type.FLOAT, + Exp.val(0), Exp.listBin("listBin1"))), + Exp.val(100)); + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.listBin1.[0].asInt() == 100"), expected); + } + + @Test + void listIndexAsFloat() { + Exp expected = Exp.eq( + Exp.toFloat(ListExp.getByIndex(ListReturnType.VALUE, Exp.Type.INT, + Exp.val(0), Exp.listBin("listBin1"))), + Exp.val(100.0)); + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.listBin1.[0].asFloat() == 100.0"), expected); + } + + @Test + void listValueAsInt() { + Exp expected = Exp.eq( + Exp.toInt(ListExp.getByValue(ListReturnType.VALUE, + Exp.val(100), Exp.listBin("listBin1"))), + Exp.val(100)); + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.listBin1.[=100].asInt() == 100"), expected); + } + + @Test + void listRankAsInt() { + Exp expected = Exp.eq( + Exp.toInt(ListExp.getByRank(ListReturnType.VALUE, Exp.Type.FLOAT, + Exp.val(-1), Exp.listBin("listBin1"))), + Exp.val(100)); + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.listBin1.[#-1].asInt() == 100"), expected); + } + + @Test + void listValueAsFloat() { + Exp expected = Exp.eq( + Exp.toFloat(ListExp.getByValue(ListReturnType.VALUE, + Exp.val(100), Exp.listBin("listBin1"))), + Exp.val(100.0)); + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.listBin1.[=100].asFloat() == 100.0"), expected); + } + + @Test + void listRankAsFloat() { + Exp expected = Exp.eq( + Exp.toFloat(ListExp.getByRank(ListReturnType.VALUE, Exp.Type.INT, + Exp.val(-1), Exp.listBin("listBin1"))), + Exp.val(100.0)); + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.listBin1.[#-1].asFloat() == 100.0"), expected); + } + + // --- CDT map casting --- + + @Test + void mapKeyAsInt() { + Exp expected = Exp.eq( + Exp.toInt(MapExp.getByKey(MapReturnType.VALUE, Exp.Type.FLOAT, + Exp.val("a"), Exp.mapBin("mapBin1"))), + Exp.val(200)); + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.a.asInt() == 200"), expected); + } + + @Test + void mapKeyAsFloat() { + Exp expected = Exp.eq( + Exp.toFloat(MapExp.getByKey(MapReturnType.VALUE, Exp.Type.INT, + Exp.val("a"), Exp.mapBin("mapBin1"))), + Exp.val(200.0)); + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.a.asFloat() == 200.0"), expected); + } + + @Test + void mapIndexAsInt() { + Exp expected = Exp.eq( + Exp.toInt(MapExp.getByIndex(MapReturnType.VALUE, Exp.Type.FLOAT, + Exp.val(0), Exp.mapBin("mapBin1"))), + Exp.val(100)); + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.{0}.asInt() == 100"), expected); + } + + @Test + void mapIndexAsFloat() { + Exp expected = Exp.eq( + Exp.toFloat(MapExp.getByIndex(MapReturnType.VALUE, Exp.Type.INT, + Exp.val(0), Exp.mapBin("mapBin1"))), + Exp.val(100.0)); + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.{0}.asFloat() == 100.0"), expected); + } + + @Test + void mapValueAsInt() { + Exp expected = Exp.eq( + Exp.toInt(MapExp.getByValue(MapReturnType.VALUE, + Exp.val(100), Exp.mapBin("mapBin1"))), + Exp.val(100)); + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.{=100}.asInt() == 100"), expected); + } + + @Test + void mapRankAsInt() { + Exp expected = Exp.eq( + Exp.toInt(MapExp.getByRank(MapReturnType.VALUE, Exp.Type.FLOAT, + Exp.val(-1), Exp.mapBin("mapBin1"))), + Exp.val(100)); + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.{#-1}.asInt() == 100"), expected); + } + + @Test + void nestedMapRankAsInt() { + Exp expected = Exp.eq( + Exp.toInt(MapExp.getByRank(MapReturnType.VALUE, Exp.Type.FLOAT, + Exp.val(-1), Exp.mapBin("mapBin1"), + CTX.mapKey(Value.get("a")))), + Exp.val(100)); + TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.a.{#-1}.asInt() == 100"), expected); + } + } diff --git a/src/test/java/com/aerospike/dsl/expression/InExplicitTypeTests.java b/src/test/java/com/aerospike/dsl/expression/InExplicitTypeTests.java index 49ddf34..081d315 100644 --- a/src/test/java/com/aerospike/dsl/expression/InExplicitTypeTests.java +++ b/src/test/java/com/aerospike/dsl/expression/InExplicitTypeTests.java @@ -191,7 +191,7 @@ void explicitMapBinInPath() { @Test void castIntBinInBin() { Exp expected = ListExp.getByValue(ListReturnType.EXISTS, - Exp.intBin("val"), Exp.listBin("list")); + Exp.toInt(Exp.floatBin("val")), Exp.listBin("list")); parseFilterExpressionAndCompare( ExpressionContext.of("$.val.asInt() in $.list"), expected); } @@ -199,7 +199,7 @@ void castIntBinInBin() { @Test void castFloatBinInBin() { Exp expected = ListExp.getByValue(ListReturnType.EXISTS, - Exp.floatBin("val"), Exp.listBin("list")); + Exp.toFloat(Exp.intBin("val")), Exp.listBin("list")); parseFilterExpressionAndCompare( ExpressionContext.of("$.val.asFloat() in $.list"), expected); } @@ -207,7 +207,7 @@ void castFloatBinInBin() { @Test void castIntBinInPath() { Exp expected = ListExp.getByValue(ListReturnType.EXISTS, - Exp.intBin("val"), + Exp.toInt(Exp.floatBin("val")), MapExp.getByKey(MapReturnType.VALUE, Exp.Type.STRING, Exp.val("tags"), Exp.mapBin("items"))); parseFilterExpressionAndCompare( @@ -217,7 +217,7 @@ void castIntBinInPath() { @Test void castFloatBinInPath() { Exp expected = ListExp.getByValue(ListReturnType.EXISTS, - Exp.floatBin("val"), + Exp.toFloat(Exp.intBin("val")), MapExp.getByKey(MapReturnType.VALUE, Exp.Type.STRING, Exp.val("tags"), Exp.mapBin("items"))); parseFilterExpressionAndCompare( diff --git a/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java index 14fc23c..a10498c 100644 --- a/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/ListExpressionsTests.java @@ -31,7 +31,6 @@ void listByIndexInteger() { TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.listBin1.[0].get(type: INT) == 100"), expected); TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.listBin1.[0].get(type: INT, return: VALUE) == 100"), expected); - TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.listBin1.[0].asInt() == 100"), expected); } @Test @@ -79,7 +78,6 @@ void listByValue() { TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.listBin1.[=100].get(type: INT) == 100"), expected); TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.listBin1.[=100].get(type: INT, return: VALUE) == 100"), expected); - TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.listBin1.[=100].asInt() == 100"), expected); } @Test @@ -136,7 +134,6 @@ void listByRank() { TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.listBin1.[#-1].get(type: INT) == 100"), expected); TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.listBin1.[#-1].get(type: INT, return: VALUE) == 100"), expected); - TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.listBin1.[#-1].asInt() == 100"), expected); } @Test diff --git a/src/test/java/com/aerospike/dsl/expression/MapExpressionsTests.java b/src/test/java/com/aerospike/dsl/expression/MapExpressionsTests.java index 5853d1e..6479088 100644 --- a/src/test/java/com/aerospike/dsl/expression/MapExpressionsTests.java +++ b/src/test/java/com/aerospike/dsl/expression/MapExpressionsTests.java @@ -35,7 +35,6 @@ void mapOneLevelExpressions() { TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.a.get(type: INT) == 200"), expected); TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.a.get(type: INT, return: VALUE) == 200"), expected); - TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.a.asInt() == 200"), expected); // String expected = Exp.eq( @@ -218,7 +217,6 @@ void mapByIndex() { TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.{0}.get(type: INT) == 100"), expected); TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.{0}.get(type: INT, return: VALUE) == 100"), expected); - TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.{0}.asInt() == 100"), expected); Exp expected2 = Exp.eq( MapExp.getByIndex( @@ -247,7 +245,6 @@ void mapByValue() { TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.{=100}.get(type: INT) == 100"), expected); TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.{=100}.get(type: INT, return: VALUE) == 100"), expected); - TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.{=100}.asInt() == 100"), expected); } @Test @@ -304,7 +301,6 @@ void mapByRank() { TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.{#-1}.get(type: INT) == 100"), expected); TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.{#-1}.get(type: INT, return: VALUE) == 100"), expected); - TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.{#-1}.asInt() == 100"), expected); } @Test @@ -351,7 +347,6 @@ void mapByRankWithNesting() { TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.a.{#-1}.get(type: INT) == 100"), expected); TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.a.{#-1}.get(type: INT, return: VALUE) == 100"), expected); - TestUtils.parseFilterExpressionAndCompare(ExpressionContext.of("$.mapBin1.a.{#-1}.asInt() == 100"), expected); } @Test From a7777acd35a64692b2b9bcc3085a07f61a0cfa7f Mon Sep 17 00:00:00 2001 From: Andrey G Date: Fri, 27 Mar 2026 19:37:51 +0100 Subject: [PATCH 7/7] Code format --- .../aerospike/dsl/parts/cdt/list/ListValue.java | 10 ++-------- .../aerospike/dsl/parts/cdt/map/MapValue.java | 10 ++-------- .../aerospike/dsl/parts/path/PathFunction.java | 4 ++++ .../com/aerospike/dsl/util/ParsingUtils.java | 16 ++++++++++++++++ 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/aerospike/dsl/parts/cdt/list/ListValue.java b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListValue.java index fca8d33..488a788 100644 --- a/src/main/java/com/aerospike/dsl/parts/cdt/list/ListValue.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/list/ListValue.java @@ -7,6 +7,7 @@ import com.aerospike.dsl.client.exp.ListExp; import com.aerospike.dsl.parts.path.BasePath; +import static com.aerospike.dsl.util.ParsingUtils.objectToExp; import static com.aerospike.dsl.util.ParsingUtils.parseValueIdentifier; public class ListValue extends ListPart { @@ -23,17 +24,10 @@ public static ListValue from(ConditionParser.ListValueContext ctx) { @Override public Exp constructExp(BasePath basePath, Exp.Type valueType, int cdtReturnType, CTX[] context) { - return ListExp.getByValue(cdtReturnType, valueToExp(), Exp.bin(basePath.getBinPart().getBinName(), + return ListExp.getByValue(cdtReturnType, objectToExp(value), Exp.bin(basePath.getBinPart().getBinName(), basePath.getBinType()), context); } - private Exp valueToExp() { - if (value instanceof Boolean b) return Exp.val(b); - if (value instanceof String s) return Exp.val(s); - if (value instanceof Float f) return Exp.val(f); - return Exp.val((Integer) value); - } - @Override public CTX getContext() { return CTX.listValue(Value.get(value)); diff --git a/src/main/java/com/aerospike/dsl/parts/cdt/map/MapValue.java b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapValue.java index 9351dcb..4d78fbc 100644 --- a/src/main/java/com/aerospike/dsl/parts/cdt/map/MapValue.java +++ b/src/main/java/com/aerospike/dsl/parts/cdt/map/MapValue.java @@ -7,6 +7,7 @@ import com.aerospike.dsl.client.exp.MapExp; import com.aerospike.dsl.parts.path.BasePath; +import static com.aerospike.dsl.util.ParsingUtils.objectToExp; import static com.aerospike.dsl.util.ParsingUtils.parseValueIdentifier; public class MapValue extends MapPart { @@ -23,17 +24,10 @@ public static MapValue from(ConditionParser.MapValueContext ctx) { @Override public Exp constructExp(BasePath basePath, Exp.Type valueType, int cdtReturnType, CTX[] context) { - return MapExp.getByValue(cdtReturnType, valueToExp(), Exp.bin(basePath.getBinPart().getBinName(), + return MapExp.getByValue(cdtReturnType, objectToExp(value), Exp.bin(basePath.getBinPart().getBinName(), basePath.getBinType()), context); } - private Exp valueToExp() { - if (value instanceof Boolean b) return Exp.val(b); - if (value instanceof String s) return Exp.val(s); - if (value instanceof Float f) return Exp.val(f); - return Exp.val((Integer) value); - } - @Override public CTX getContext() { return CTX.mapValue(Value.get(value)); diff --git a/src/main/java/com/aerospike/dsl/parts/path/PathFunction.java b/src/main/java/com/aerospike/dsl/parts/path/PathFunction.java index 3563063..2775095 100644 --- a/src/main/java/com/aerospike/dsl/parts/path/PathFunction.java +++ b/src/main/java/com/aerospike/dsl/parts/path/PathFunction.java @@ -26,6 +26,10 @@ public static Exp.Type castTypeToExpType(CastType castType) { }; } + /** + * Returns the source {@link Exp.Type} for a cast — the type the value must + * already have before the cast is applied (e.g., casting to INT requires a FLOAT source). + */ public static Exp.Type castSourceExpType(CastType castType) { return switch (castType) { case INT -> Exp.Type.FLOAT; diff --git a/src/main/java/com/aerospike/dsl/util/ParsingUtils.java b/src/main/java/com/aerospike/dsl/util/ParsingUtils.java index ef75653..c7a0234 100644 --- a/src/main/java/com/aerospike/dsl/util/ParsingUtils.java +++ b/src/main/java/com/aerospike/dsl/util/ParsingUtils.java @@ -2,6 +2,7 @@ import com.aerospike.dsl.ConditionParser; import com.aerospike.dsl.DslParseException; +import com.aerospike.dsl.client.exp.Exp; import lombok.NonNull; import lombok.experimental.UtilityClass; import org.antlr.v4.runtime.ParserRuleContext; @@ -166,6 +167,21 @@ public static Integer requireIntValueIdentifier(ConditionParser.ValueIdentifierC "Value range requires integer operands, got: %s".formatted(ctx.getText())); } + /** + * Converts a parsed value object to an {@link Exp} value expression. + * Supports the types produced by {@link #parseValueIdentifier}: {@link String} and {@link Integer}. + * + * @param value The parsed value object + * @return The corresponding {@link Exp} value expression + * @throws DslParseException if the value type is not supported + */ + public static Exp objectToExp(Object value) { + if (value instanceof String s) return Exp.val(s); + if (value instanceof Integer i) return Exp.val(i); + throw new DslParseException( + "Unsupported value type for Exp conversion: " + value.getClass().getSimpleName()); + } + /** * Get the string inside the quotes. *