From 0463b34760cbc2200ee64a6a13137c9c92021717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Locker?= Date: Wed, 7 Feb 2024 08:38:40 +0100 Subject: [PATCH] Add DescrDumperTest and fix the expression parser grammar (#48) * Add DescrDumperTest * Fix it --- drools-drl/drools-drl10-parser/pom.xml | 12 + .../org/drools/parser/DRL6Expressions.g4 | 11 +- .../org/drools/parser/DescrDumperTest.java | 367 ++++++++++++++++++ 3 files changed, 384 insertions(+), 6 deletions(-) create mode 100644 drools-drl/drools-drl10-parser/src/test/java/org/drools/parser/DescrDumperTest.java diff --git a/drools-drl/drools-drl10-parser/pom.xml b/drools-drl/drools-drl10-parser/pom.xml index 49aee297f54..57ed8f85ca5 100644 --- a/drools-drl/drools-drl10-parser/pom.xml +++ b/drools-drl/drools-drl10-parser/pom.xml @@ -75,6 +75,18 @@ ${version.ch.qos.logback} test + + org.drools + drools-compiler + ${version.org.drools} + test + + + org.drools + drools-mvel + ${version.org.drools} + test + diff --git a/drools-drl/drools-drl10-parser/src/main/antlr4/org/drools/parser/DRL6Expressions.g4 b/drools-drl/drools-drl10-parser/src/main/antlr4/org/drools/parser/DRL6Expressions.g4 index 9b15991027c..ef6e0982672 100644 --- a/drools-drl/drools-drl10-parser/src/main/antlr4/org/drools/parser/DRL6Expressions.g4 +++ b/drools-drl/drools-drl10-parser/src/main/antlr4/org/drools/parser/DRL6Expressions.g4 @@ -99,6 +99,7 @@ catch (RecognitionException re) { // -------------------------------------------------------- literal : STRING_LITERAL { helper.emit($STRING_LITERAL, DroolsEditorType.STRING_CONST); } + | DRL_STRING_LITERAL { helper.emit($DRL_STRING_LITERAL, DroolsEditorType.STRING_CONST); } | DECIMAL_LITERAL { helper.emit($DECIMAL_LITERAL, DroolsEditorType.NUMERIC_CONST); } | HEX_LITERAL { helper.emit($HEX_LITERAL, DroolsEditorType.NUMERIC_CONST); } | FLOAT_LITERAL { helper.emit($FLOAT_LITERAL, DroolsEditorType.NUMERIC_CONST); } @@ -731,9 +732,6 @@ selector superSuffix : arguments - // TODO syntactic predicates in the form of `(x) => x` can be safely removed but - // there was originally `DOT ID ((LEFT_PAREN) => arguments)?`. - // Not sure if removing `(LEFT_PAREN) =>` is correct. | DOT IDENTIFIER (arguments)? ; @@ -832,15 +830,16 @@ new_key ; not_key - : {(helper.validateIdentifierKey(DroolsSoftKeywords.NOT))}? id=IDENTIFIER { helper.emit($id, DroolsEditorType.KEYWORD); } + : {(helper.validateIdentifierKey(DroolsSoftKeywords.NOT))}? id=DRL_NOT { helper.emit($id, DroolsEditorType.KEYWORD); } ; in_key - : {(helper.validateIdentifierKey(DroolsSoftKeywords.IN))}? id=IDENTIFIER { helper.emit($id, DroolsEditorType.KEYWORD); } + : {(helper.validateIdentifierKey(DroolsSoftKeywords.IN))}? id=DRL_IN { helper.emit($id, DroolsEditorType.KEYWORD); } ; operator_key - : {(helper.isPluggableEvaluator(false))}? id=IDENTIFIER { helper.emit($id, DroolsEditorType.KEYWORD); } + // TODO get rid of the DRL_MATCHES token or introduce DRL_CONTAINS etc. for consistency. + : {(helper.isPluggableEvaluator(false))}? id=(IDENTIFIER|DRL_MATCHES) { helper.emit($id, DroolsEditorType.KEYWORD); } ; neg_operator_key diff --git a/drools-drl/drools-drl10-parser/src/test/java/org/drools/parser/DescrDumperTest.java b/drools-drl/drools-drl10-parser/src/test/java/org/drools/parser/DescrDumperTest.java new file mode 100644 index 00000000000..d0a18797c1e --- /dev/null +++ b/drools-drl/drools-drl10-parser/src/test/java/org/drools/parser/DescrDumperTest.java @@ -0,0 +1,367 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.drools.parser; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.drools.compiler.lang.DescrDumper; +import org.drools.compiler.lang.DumperContext; +import org.drools.drl.ast.descr.AtomicExprDescr; +import org.drools.drl.ast.descr.BindingDescr; +import org.drools.drl.ast.descr.ConstraintConnectiveDescr; +import org.drools.mvel.evaluators.MatchesEvaluatorsDefinition; +import org.drools.mvel.evaluators.SetEvaluatorsDefinition; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.kie.internal.builder.conf.LanguageLevelOption; + +public class DescrDumperTest { + + private DescrDumper dumper; + + @BeforeEach + public void setUp() throws Exception { + // configure operators + new SetEvaluatorsDefinition(); + new MatchesEvaluatorsDefinition(); + + dumper = new DescrDumper(); + } + + @Test + public void testDump() throws Exception { + String input = "price > 10 && < 20 || == $val || == 30"; + String expected = "( price > 10 && price < 20 || price == $val || price == 30 )"; + + ConstraintConnectiveDescr descr = parse( input ); + String result = dumper.dump( descr ); + + assertThat(result).isEqualTo(expected); + } + + @Test + public void testDumpMatches() throws Exception { + String input = "type.toString matches \"something\\swith\\tsingle escapes\""; + String expected = "type.toString ~= \"something\\swith\\tsingle escapes\""; + + ConstraintConnectiveDescr descr = parse( input ); + String result = dumper.dump( descr ); + + assertThat(result).isEqualTo(expected); + } + + @Test + public void testDumpMatches2() throws Exception { + String input = "type.toString matches 'something\\swith\\tsingle escapes'"; + String expected = "type.toString ~= \"something\\swith\\tsingle escapes\""; + + ConstraintConnectiveDescr descr = parse( input ); + String result = dumper.dump( descr ); + + assertThat(result).isEqualTo(expected); + } + + @Test + public void testDumpMatches3() throws Exception { + String input = "this[\"content\"] matches \"hello ;=\""; + String expected = "this[\"content\"] ~= \"hello ;=\""; + + ConstraintConnectiveDescr descr = parse( input ); + String result = dumper.dump( descr ); + + assertThat(result).isEqualTo(expected); + } + + @Test + public void testDumpContains() throws Exception { + String input = "list contains \"b\""; + String expected = "list contains \"b\""; + + ConstraintConnectiveDescr descr = parse( input ); + String result = dumper.dump( descr ); + + assertThat(result).isEqualTo(expected); + } + + @Test + public void testDumpContains2() throws Exception { + String input = "list not contains \"b\""; + String expected = "!( list contains \"b\" )"; + + ConstraintConnectiveDescr descr = parse( input ); + String result = dumper.dump( descr ); + + assertThat(result).isEqualTo(expected); + } + + @Test + public void testDumpExcludes() throws Exception { + String input = "list excludes \"b\""; + String expected = "!( list contains \"b\" )"; + + ConstraintConnectiveDescr descr = parse( input ); + String result = dumper.dump( descr ); + + assertThat(result).isEqualTo(expected); + } + + @Test + public void testDumpExcludes2() throws Exception { + String input = "list not excludes \"b\""; + String expected = "list contains \"b\""; + + ConstraintConnectiveDescr descr = parse( input ); + String result = dumper.dump( descr ); + + assertThat(result).isEqualTo(expected); + } + + @Test @Disabled + public void testDumpWithDateAttr() throws Exception { + String input = "son.birthDate == \"01-jan-2000\""; + String expected = "son.birthDate == org.drools.util.DateUtils.parseDate( \"01-jan-2000\" )"; + + ConstraintConnectiveDescr descr = parse( input ); + String result = dumper.dump( descr ); + + assertThat(result).isEqualTo(expected); + } + + @Test + public void testDumpComplex() throws Exception { + String input = "a ( > 60 && < 70 ) || ( > 50 && < 55 ) && a3 == \"black\" || a == 40 && a3 == \"pink\" || a == 12 && a3 == \"yellow\" || a3 == \"blue\""; + String expected = "( ( a > 60 && a < 70 || a > 50 && a < 55 ) && a3 == \"black\" || a == 40 && a3 == \"pink\" || a == 12 && a3 == \"yellow\" || a3 == \"blue\" )"; + + ConstraintConnectiveDescr descr = parse( input ); + String result = dumper.dump( descr ); + + assertThat(result).isEqualTo(expected); + } + + @Test + public void testDumpBindings() throws Exception { + String input = "$x : property > value"; + String expected = "property > value"; + + ConstraintConnectiveDescr descr = parse( input ); + DumperContext ctx = new DumperContext(); + String result = dumper.dump( descr, + ctx ); + + assertThat(result).isEqualTo(expected); + assertThat(ctx.getBindings().size()).isEqualTo(1); + BindingDescr bind = ctx.getBindings().get( 0 ); + assertThat(bind.getVariable()).isEqualTo("$x"); + assertThat(bind.getExpression()).isEqualTo("property"); + } + + @Test + public void testDumpBindings2() throws Exception { + String input = "( $a : a > $b : b[10].prop || 10 != 20 ) && $x : someMethod(10) == 20"; + String expected = "( a > b[10].prop || 10 != 20 ) && someMethod(10) == 20"; + + ConstraintConnectiveDescr descr = parse( input ); + DumperContext ctx = new DumperContext(); + String result = dumper.dump( descr, + ctx ); + + assertThat(result).isEqualTo(expected); + assertThat(ctx.getBindings().size()).isEqualTo(3); + BindingDescr bind = ctx.getBindings().get( 0 ); + assertThat(bind.getVariable()).isEqualTo("$a"); + assertThat(bind.getExpression()).isEqualTo("a"); + bind = ctx.getBindings().get( 1 ); + assertThat(bind.getVariable()).isEqualTo("$b"); + assertThat(bind.getExpression()).isEqualTo("b[10].prop"); + bind = ctx.getBindings().get( 2 ); + assertThat(bind.getVariable()).isEqualTo("$x"); + assertThat(bind.getExpression()).isEqualTo("someMethod(10)"); + } + + @Test + public void testDumpBindings3() throws Exception { + String input = "( $a : a > $b : b[10].prop || 10 != 20 ) && $x : someMethod(10)"; + String expected = "( a > b[10].prop || 10 != 20 )"; + + ConstraintConnectiveDescr descr = parse( input ); + String result = dumper.dump( descr ); + + assertThat(result).isEqualTo(expected); + } + + @Test + public void testDumpBindings4() throws Exception { + String input = "( $a : a > $b : b[10].prop || $x : someMethod(10) ) && 10 != 20"; + String expected = "( a > b[10].prop ) && 10 != 20"; + + ConstraintConnectiveDescr descr = parse( input ); + String result = dumper.dump( descr ); + + assertThat(result).isEqualTo(expected); + } + + @Test + public void testDumpBindingsWithRestriction() throws Exception { + String input = "$x : age > 10 && < 20 || > 30"; + String expected = "( age > 10 && age < 20 || age > 30 )"; + + ConstraintConnectiveDescr descr = parse( input ); + DumperContext ctx = new DumperContext(); + String result = dumper.dump( descr, + ctx ); + + assertThat(result).isEqualTo(expected); + assertThat(ctx.getBindings().size()).isEqualTo(1); + BindingDescr bind = ctx.getBindings().get( 0 ); + assertThat(bind.getVariable()).isEqualTo("$x"); + assertThat(bind.getExpression()).isEqualTo("age"); + } + + @Test + public void testDumpBindingsComplexOp() throws Exception { + String input = "$x : age in (10, 20, $someVal)"; + String expected = "( age == 10 || age == 20 || age == $someVal )"; + + ConstraintConnectiveDescr descr = parse( input ); + DumperContext ctx = new DumperContext(); + String result = dumper.dump( descr, + ctx ); + + assertThat(result).isEqualTo(expected); + assertThat(ctx.getBindings().size()).isEqualTo(1); + BindingDescr bind = ctx.getBindings().get( 0 ); + assertThat(bind.getVariable()).isEqualTo("$x"); + assertThat(bind.getExpression()).isEqualTo("age"); + } + + @Test + public void testDumpBindingsComplexOp2() throws Exception { + String input = "$x : age not in (10, 20, $someVal)"; + String expected = "age != 10 && age != 20 && age != $someVal"; + + ConstraintConnectiveDescr descr = parse( input ); + DumperContext ctx = new DumperContext(); + String result = dumper.dump( descr, + ctx ); + + assertThat(result).isEqualTo(expected); + assertThat(ctx.getBindings().size()).isEqualTo(1); + BindingDescr bind = ctx.getBindings().get( 0 ); + assertThat(bind.getVariable()).isEqualTo("$x"); + assertThat(bind.getExpression()).isEqualTo("age"); + } + + @Test + public void testProcessInlineCast() throws Exception { + String expr = "field1#Class.field2"; + String expectedInstanceof = "field1 instanceof Class"; + String expectedcasted = "((Class)field1).field2"; + AtomicExprDescr atomicExpr = new AtomicExprDescr(expr); + ConstraintConnectiveDescr ccd = new ConstraintConnectiveDescr( ); + ccd.addDescr( atomicExpr ); + String[] instanceofAndCastedExpr = dumper.processImplicitConstraints(expr, atomicExpr, ccd, ccd.getDescrs().indexOf( atomicExpr ), null); + assertThat(ccd.getDescrs().size()).isEqualTo(2); + assertThat(ccd.getDescrs().get(0).toString()).isEqualTo(expectedInstanceof); + assertThat(atomicExpr.getRewrittenExpression()).isEqualTo(expectedcasted); + + expr = "field1#Class1.field2#Class2.field3"; + String expectedInstanceof1 = "field1 instanceof Class1"; + String expectedInstanceof2 = "((Class1)field1).field2 instanceof Class2"; + expectedcasted = "((Class2)((Class1)field1).field2).field3"; + atomicExpr = new AtomicExprDescr(expr); + ccd = new ConstraintConnectiveDescr( ); + instanceofAndCastedExpr = dumper.processImplicitConstraints(expr, atomicExpr, ccd, ccd.getDescrs().indexOf( atomicExpr ), null); + assertThat(ccd.getDescrs().get(0).toString()).isEqualTo(expectedInstanceof1); + assertThat(ccd.getDescrs().get(1).toString()).isEqualTo(expectedInstanceof2); + assertThat(instanceofAndCastedExpr[1]).isEqualTo(expectedcasted); + assertThat(atomicExpr.getRewrittenExpression()).isEqualTo(expectedcasted); + } + + @Test + public void testProcessNullSafeDereferencing() throws Exception { + String expr = "field1!.field2"; + String expectedNullCheck = "field1 != null"; + String expectedExpr = "field1.field2"; + AtomicExprDescr atomicExpr = new AtomicExprDescr(expr); + ConstraintConnectiveDescr ccd = new ConstraintConnectiveDescr( ); + String[] nullCheckAndExpr = dumper.processImplicitConstraints( expr, atomicExpr, ccd, ccd.getDescrs().indexOf( atomicExpr ), null ); + assertThat(ccd.getDescrs().get(0).toString()).isEqualTo(expectedNullCheck); + assertThat(nullCheckAndExpr[1]).isEqualTo(expectedExpr); + assertThat(atomicExpr.getRewrittenExpression()).isEqualTo(expectedExpr); + + expr = "field1!.field2!.field3"; + String expectedNullCheck1 = "field1 != null"; + String expectedNullCheck2 = "field1.field2 != null"; + expectedExpr = "field1.field2.field3"; + atomicExpr = new AtomicExprDescr(expr); + ccd = new ConstraintConnectiveDescr( ); + nullCheckAndExpr = dumper.processImplicitConstraints( expr, atomicExpr, ccd, ccd.getDescrs().indexOf( atomicExpr ), null ); + assertThat(ccd.getDescrs().get(0).toString()).isEqualTo(expectedNullCheck1); + assertThat(ccd.getDescrs().get(1).toString()).isEqualTo(expectedNullCheck2); + assertThat(nullCheckAndExpr[1]).isEqualTo(expectedExpr); + assertThat(atomicExpr.getRewrittenExpression()).isEqualTo(expectedExpr); + } + + @Test + public void testProcessImplicitConstraints() throws Exception { + String expr = "field1#Class!.field2"; + String expectedConstraints = "field1 instanceof Class"; + String expectedExpr = "((Class)field1).field2"; + AtomicExprDescr atomicExpr = new AtomicExprDescr(expr); + ConstraintConnectiveDescr ccd = new ConstraintConnectiveDescr( ); + String[] constraintsAndExpr = dumper.processImplicitConstraints( expr, atomicExpr, ccd, ccd.getDescrs().indexOf( atomicExpr ), null ); + assertThat(ccd.getDescrs().get(0).toString()).isEqualTo(expectedConstraints); + assertThat(constraintsAndExpr[1]).isEqualTo(expectedExpr); + assertThat(atomicExpr.getRewrittenExpression()).isEqualTo(expectedExpr); + + expr = "field1!.field2#Class.field3"; + String expectedConstraints1 = "field1 != null"; + String expectedConstraints2 = "field1.field2 instanceof Class"; + expectedExpr = "((Class)field1.field2).field3"; + atomicExpr = new AtomicExprDescr(expr); + ccd = new ConstraintConnectiveDescr( ); + constraintsAndExpr = dumper.processImplicitConstraints( expr, atomicExpr, ccd, ccd.getDescrs().indexOf( atomicExpr ), null ); + assertThat(ccd.getDescrs().get(0).toString()).isEqualTo(expectedConstraints1); + assertThat(ccd.getDescrs().get(1).toString()).isEqualTo(expectedConstraints2); + assertThat(constraintsAndExpr[1]).isEqualTo(expectedExpr); + assertThat(atomicExpr.getRewrittenExpression()).isEqualTo(expectedExpr); + + expr = "field1#Class.field2!.field3"; + expectedConstraints1 = "field1 instanceof Class"; + expectedConstraints2 = "((Class)field1).field2 != null"; + expectedExpr = "((Class)field1).field2.field3"; + atomicExpr = new AtomicExprDescr(expr); + ccd = new ConstraintConnectiveDescr( ); + constraintsAndExpr = dumper.processImplicitConstraints( expr, atomicExpr, ccd, ccd.getDescrs().indexOf( atomicExpr ), null ); + assertThat(ccd.getDescrs().get(0).toString()).isEqualTo(expectedConstraints1); + assertThat(ccd.getDescrs().get(1).toString()).isEqualTo(expectedConstraints2); + assertThat(constraintsAndExpr[1]).isEqualTo(expectedExpr); + assertThat(atomicExpr.getRewrittenExpression()).isEqualTo(expectedExpr); + } + + public ConstraintConnectiveDescr parse( final String constraint ) { + DrlExprParser parser = new DrlExprParser(LanguageLevelOption.DRL6); + ConstraintConnectiveDescr result = parser.parse( constraint ); + assertThat(parser.hasErrors()).as(parser.getErrors().toString()).isFalse(); + + return result; + } + +}