diff --git a/drools-drl/drools-drl10-parser/src/main/antlr4/org/drools/parser/DRLLexer.g4 b/drools-drl/drools-drl10-parser/src/main/antlr4/org/drools/parser/DRLLexer.g4 index f92c120d0a6..60e282c49fb 100644 --- a/drools-drl/drools-drl10-parser/src/main/antlr4/org/drools/parser/DRLLexer.g4 +++ b/drools-drl/drools-drl10-parser/src/main/antlr4/org/drools/parser/DRLLexer.g4 @@ -18,16 +18,20 @@ import JavaLexer; // KEYWORDS ///////////////// -DRL_PACKAGE : 'package'; +// These keywords are already declared in JavaLexer. They should not be overriden with different names, or else Vocabulary's literalName will be null. +// So no need to declare by DRLLexer +// PACKAGE : 'package'; +// IMPORT : 'import'; +// STATIC : 'static'; +// EXTENDS : 'extends'; +// SUPER : 'super'; + +// DRL keywords DRL_UNIT : 'unit'; -DRL_IMPORT : 'import'; DRL_FUNCTION : 'function'; -DRL_STATIC : 'static'; DRL_GLOBAL : 'global'; DRL_RULE : 'rule'; DRL_QUERY : 'query'; -DRL_EXTENDS : 'extends'; -DRL_SUPER : 'super'; DRL_WHEN : 'when'; DRL_THEN : 'then'; DRL_END : 'end'; diff --git a/drools-drl/drools-drl10-parser/src/main/antlr4/org/drools/parser/DRLParser.g4 b/drools-drl/drools-drl10-parser/src/main/antlr4/org/drools/parser/DRLParser.g4 index 690828a176e..00c61cbc1e5 100644 --- a/drools-drl/drools-drl10-parser/src/main/antlr4/org/drools/parser/DRLParser.g4 +++ b/drools-drl/drools-drl10-parser/src/main/antlr4/org/drools/parser/DRLParser.g4 @@ -24,20 +24,20 @@ drlStatementdef | ruledef ; -packagedef : DRL_PACKAGE name=drlQualifiedName SEMI? ; +packagedef : PACKAGE name=drlQualifiedName SEMI? ; unitdef : DRL_UNIT name=drlQualifiedName SEMI? ; -importdef : DRL_IMPORT (DRL_FUNCTION|DRL_STATIC)? drlQualifiedName (DOT MUL)? SEMI? ; +importdef : IMPORT (DRL_FUNCTION|STATIC)? drlQualifiedName (DOT MUL)? SEMI? ; globaldef : DRL_GLOBAL type drlIdentifier SEMI? ; // rule := RULE stringId (EXTENDS stringId)? annotation* attributes? lhs? rhs END -ruledef : DRL_RULE name=stringId (DRL_EXTENDS stringId)? drlAnnotation* attributes? DRL_WHEN lhs DRL_THEN rhs DRL_END ; +ruledef : DRL_RULE name=stringId (EXTENDS stringId)? drlAnnotation* attributes? DRL_WHEN lhs DRL_THEN rhs DRL_END ; lhs : lhsExpression? ; -lhsExpression : lhsOr* ; +lhsExpression : lhsOr+ ; lhsOr : LPAREN DRL_OR lhsAnd+ RPAREN | lhsAnd (DRL_OR lhsAnd)* ; lhsAnd : LPAREN DRL_AND lhsUnary+ RPAREN | lhsUnary (DRL_AND lhsUnary)* ; @@ -111,16 +111,11 @@ drlIdentifier ; drlKeywords - : DRL_PACKAGE - | DRL_UNIT - | DRL_IMPORT + : DRL_UNIT | DRL_FUNCTION - | DRL_STATIC | DRL_GLOBAL | DRL_RULE | DRL_QUERY - | DRL_EXTENDS - | DRL_SUPER | DRL_WHEN | DRL_THEN | DRL_END @@ -154,7 +149,7 @@ drlExpression : drlPrimary | drlExpression bop=DOT ( - identifier + drlIdentifier | methodCall | THIS | NEW nonWildcardTypeArguments? innerCreator @@ -163,7 +158,7 @@ drlExpression ) | drlExpression LBRACK drlExpression RBRACK | methodCall - | NEW creator + | NEW drlCreator | LPAREN annotation* typeType (BITAND typeType)* RPAREN drlExpression | drlExpression postfix=(INC | DEC) | prefix=(ADD|SUB|INC|DEC) drlExpression @@ -188,8 +183,8 @@ drlExpression | switchExpression // Java17 // Java 8 methodReference - | drlExpression COLONCOLON typeArguments? identifier - | typeType COLONCOLON (typeArguments? identifier | NEW) + | drlExpression COLONCOLON typeArguments? drlIdentifier + | typeType COLONCOLON (typeArguments? drlIdentifier | NEW) | classType COLONCOLON typeArguments? NEW ; @@ -290,11 +285,38 @@ unif : IDENTIFIER UNIFY ; /* extending JavaParser blockStatement */ drlRhsBlockStatement - : localVariableDeclaration SEMI + : drlLocalVariableDeclaration SEMI | drlRhsStatement | localTypeDeclaration ; +/* extending JavaParser localVariableDeclaration */ +drlLocalVariableDeclaration + : variableModifier* (typeType drlVariableDeclarators | VAR drlIdentifier ASSIGN drlExpression) + ; + +/* extending JavaParser variableDeclarators */ +drlVariableDeclarators + : drlVariableDeclarator (COMMA drlVariableDeclarator)* + ; + +/* extending JavaParser variableDeclarator */ +drlVariableDeclarator + : drlVariableDeclaratorId (ASSIGN drlVariableInitializer)? + ; + +/* extending JavaParser variableDeclaratorId */ +drlVariableDeclaratorId + : drlIdentifier (LBRACK RBRACK)* + ; + +/* extending JavaParser variableInitializer */ +drlVariableInitializer + : arrayInitializer + | drlExpression + ; + + /* extending JavaParser statement */ drlRhsStatement : blockLabel=block @@ -309,13 +331,13 @@ drlRhsStatement | SYNCHRONIZED parExpression block | RETURN drlRhsExpression? SEMI | THROW drlRhsExpression SEMI - | BREAK identifier? SEMI - | CONTINUE identifier? SEMI + | BREAK drlIdentifier? SEMI + | CONTINUE drlIdentifier? SEMI | YIELD drlRhsExpression SEMI // Java17 | SEMI | statementExpression=drlRhsExpression SEMI | switchExpression SEMI? // Java17 - | identifierLabel=identifier COLON drlRhsStatement + | identifierLabel=drlIdentifier COLON drlRhsStatement ; /* extending JavaParser expression */ @@ -323,7 +345,7 @@ drlRhsExpression : drlPrimary | drlRhsExpression bop=DOT ( - identifier + drlIdentifier | methodCall | THIS | NEW nonWildcardTypeArguments? innerCreator @@ -357,7 +379,20 @@ drlRhsExpression | switchExpression // Java17 // Java 8 methodReference - | drlRhsExpression COLONCOLON typeArguments? identifier - | typeType COLONCOLON (typeArguments? identifier | NEW) + | drlRhsExpression COLONCOLON typeArguments? drlIdentifier + | typeType COLONCOLON (typeArguments? drlIdentifier | NEW) | classType COLONCOLON typeArguments? NEW - ; \ No newline at end of file + ; + + drlCreator + : nonWildcardTypeArguments createdName classCreatorRest + | createdName (drlArrayCreatorRest | classCreatorRest) + ; + + drlArrayCreatorRest + : LBRACK (RBRACK (LBRACK RBRACK)* drlArrayInitializer | expression RBRACK (LBRACK expression RBRACK)* (LBRACK RBRACK)*) + ; + + drlArrayInitializer + : LBRACE (drlVariableInitializer (COMMA drlVariableInitializer)* (COMMA)? )? RBRACE + ; diff --git a/drools-drl/drools-drl10-parser/src/main/java/org/drools/parser/DRLVisitorImpl.java b/drools-drl/drools-drl10-parser/src/main/java/org/drools/parser/DRLVisitorImpl.java index d45028d82a8..28775cfa8d9 100644 --- a/drools-drl/drools-drl10-parser/src/main/java/org/drools/parser/DRLVisitorImpl.java +++ b/drools-drl/drools-drl10-parser/src/main/java/org/drools/parser/DRLVisitorImpl.java @@ -16,6 +16,7 @@ import org.drools.drl.ast.descr.ExistsDescr; import org.drools.drl.ast.descr.ExprConstraintDescr; import org.drools.drl.ast.descr.FromDescr; +import org.drools.drl.ast.descr.FunctionDescr; import org.drools.drl.ast.descr.FunctionImportDescr; import org.drools.drl.ast.descr.GlobalDescr; import org.drools.drl.ast.descr.ImportDescr; @@ -68,7 +69,7 @@ public Object visitGlobaldef(DRLParser.GlobaldefContext ctx) { @Override public Object visitImportdef(DRLParser.ImportdefContext ctx) { String target = ctx.drlQualifiedName().getText() + (ctx.MUL() != null ? ".*" : ""); - if (ctx.DRL_FUNCTION() != null || ctx.DRL_STATIC() != null) { + if (ctx.DRL_FUNCTION() != null || ctx.STATIC() != null) { FunctionImportDescr functionImportDescr = new FunctionImportDescr(); functionImportDescr.setTarget(target); populateStartEnd(functionImportDescr, ctx); @@ -82,10 +83,39 @@ public Object visitImportdef(DRLParser.ImportdefContext ctx) { return super.visitImportdef(ctx); } + @Override + public Object visitFunctiondef(DRLParser.FunctiondefContext ctx) { + FunctionDescr functionDescr = new FunctionDescr(); + functionDescr.setNamespace(packageDescr.getNamespace()); + AttributeDescr dialect = packageDescr.getAttribute("dialect"); + if (dialect != null) { + functionDescr.setDialect(dialect.getValue()); + } + if (ctx.typeTypeOrVoid() != null) { + functionDescr.setReturnType(ctx.typeTypeOrVoid().getText()); + } else { + functionDescr.setReturnType("void"); + } + functionDescr.setName(ctx.IDENTIFIER().getText()); + DRLParser.FormalParametersContext formalParametersContext = ctx.formalParameters(); + DRLParser.FormalParameterListContext formalParameterListContext = formalParametersContext.formalParameterList(); + if (formalParameterListContext != null) { + List formalParameterContexts = formalParameterListContext.formalParameter(); + formalParameterContexts.stream().forEach(formalParameterContext -> { + DRLParser.TypeTypeContext typeTypeContext = formalParameterContext.typeType(); + DRLParser.VariableDeclaratorIdContext variableDeclaratorIdContext = formalParameterContext.variableDeclaratorId(); + functionDescr.addParameter(typeTypeContext.getText(), variableDeclaratorIdContext.getText()); + }); + } + functionDescr.setBody(ParserStringUtils.getTextPreservingWhitespace(ctx.block())); + packageDescr.addFunction(functionDescr); + return super.visitFunctiondef(ctx); + } + @Override public Object visitRuledef(DRLParser.RuledefContext ctx) { currentRule = new RuleDescr(safeStripStringDelimiters(ctx.name.getText())); - currentRule.setConsequence(ctx.rhs().getText()); + currentRule.setConsequence(ParserStringUtils.getTextPreservingWhitespace(ctx.rhs())); packageDescr.addRule(currentRule); Object result = super.visitRuledef(ctx); diff --git a/drools-drl/drools-drl10-parser/src/main/java/org/drools/parser/ParserStringUtils.java b/drools-drl/drools-drl10-parser/src/main/java/org/drools/parser/ParserStringUtils.java index 966a68b90c7..58410e9ea36 100644 --- a/drools-drl/drools-drl10-parser/src/main/java/org/drools/parser/ParserStringUtils.java +++ b/drools-drl/drools-drl10-parser/src/main/java/org/drools/parser/ParserStringUtils.java @@ -1,5 +1,8 @@ package org.drools.parser; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.misc.Interval; + /** * will be merged in drools-util */ @@ -17,4 +20,15 @@ public static String safeStripStringDelimiters(String value) { } return value; } + + public static String getTextPreservingWhitespace(ParserRuleContext ctx) { + int startIndex = ctx.start.getStartIndex(); + int stopIndex = ctx.stop.getStopIndex(); + if (startIndex > stopIndex) { + // no text + return ""; + } + Interval interval = new Interval(startIndex, stopIndex); + return ctx.start.getTokenSource().getInputStream().getText(interval); + } } diff --git a/drools-drl/drools-drl10-parser/src/test/java/org/drools/parser/DRLParserTest.java b/drools-drl/drools-drl10-parser/src/test/java/org/drools/parser/DRLParserTest.java index 4e8abc50b0c..1330e7ed509 100644 --- a/drools-drl/drools-drl10-parser/src/test/java/org/drools/parser/DRLParserTest.java +++ b/drools-drl/drools-drl10-parser/src/test/java/org/drools/parser/DRLParserTest.java @@ -5,6 +5,7 @@ import org.drools.drl.ast.descr.*; import org.junit.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.drools.parser.DRLParserHelper.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -60,7 +61,7 @@ public void testParse() { ExprConstraintDescr expr = (ExprConstraintDescr) constraints.get(0); assertEquals("age >= 18", expr.getExpression()); - assertEquals("inta=4;System.out.println($p.getName());", ruleDescr.getConsequence()); + assertThat(ruleDescr.getConsequence().toString()).isEqualToIgnoringWhitespace("int a = 4; System.out.println($p.getName());"); } @Test diff --git a/drools-drl/drools-drl10-parser/src/test/java/org/drools/parser/MiscDRLParserTest.java b/drools-drl/drools-drl10-parser/src/test/java/org/drools/parser/MiscDRLParserTest.java index 079b3ccb9ae..48d3460f62a 100644 --- a/drools-drl/drools-drl10-parser/src/test/java/org/drools/parser/MiscDRLParserTest.java +++ b/drools-drl/drools-drl10-parser/src/test/java/org/drools/parser/MiscDRLParserTest.java @@ -16,6 +16,7 @@ import org.drools.drl.ast.descr.BaseDescr; import org.drools.drl.ast.descr.ExprConstraintDescr; import org.drools.drl.ast.descr.FromDescr; +import org.drools.drl.ast.descr.FunctionDescr; import org.drools.drl.ast.descr.FunctionImportDescr; import org.drools.drl.ast.descr.GlobalDescr; import org.drools.drl.ast.descr.ImportDescr; @@ -498,6 +499,10 @@ public void testEmptyRuleWithoutWhen() throws Exception { @Test public void testKeywordCollisions() throws Exception { String source = readResource("eol_funny_business.drl"); // keywords everywhere + + // Note: eol_funny_business.drl is modified from the one under drools-test-coverage to be more realistic. + // e.g. "package" is not allowed in a package value in Java, so it doesn't make sense to test. (Right to raise a parser error) + PackageDescr pkg = parser.parse(source); assertFalse( parser.getErrors().toString(), @@ -506,4 +511,174 @@ public void testKeywordCollisions() throws Exception { assertEquals( 1, pkg.getRules().size() ); } + + @Test + public void testTernaryExpression() throws Exception { + String source = readResource("ternary_expression.drl"); + PackageDescr pkg = parser.parse(source); + + final RuleDescr rule = (RuleDescr) pkg.getRules().get( 0 ); + assertEquals( 1, + pkg.getRules().size() ); + + assertThat((String) rule.getConsequence()).isEqualToIgnoringWhitespace("if (speed > speedLimit ? true : false;) pullEmOver();"); + } + + @Test + public void testFunctionWithArrays() throws Exception { + String source = readResource("function_arrays.drl"); + + // Note: function_arrays.drl is modified from the one under drools-test-coverage to be more realistic. + // new String[3] {"a","b","c"} is invalid in Java (Cannot define dimension expressions when an array initializer is provided) + // , so it doesn't make sense to test. (Right to raise a parser error) + + PackageDescr pkg = parser.parse(source); + + assertEquals( "foo", + pkg.getName() ); + assertEquals( 1, + pkg.getRules().size() ); + + final RuleDescr rule = (RuleDescr) pkg.getRules().get( 0 ); + + assertThat((String) rule.getConsequence()).isEqualToIgnoringWhitespace("yourFunction(new String[] {\"a\",\"b\",\"c\"});"); + + final FunctionDescr func = (FunctionDescr) pkg.getFunctions().get(0 ); + + assertEquals( "String[]", + func.getReturnType() ); + assertEquals( "args[]", + func.getParameterNames().get( 0 ) ); + assertEquals( "String", + func.getParameterTypes().get( 0 ) ); + } + + @Test + public void testAlmostEmptyRule() throws Exception { + String source = readResource("almost_empty_rule.drl"); + PackageDescr pkg = parser.parse(source); + + assertFalse( parser.getErrors().toString(), + parser.hasErrors() ); + assertNotNull( pkg ); + + RuleDescr rule = pkg.getRules().get( 0 ); + + assertEquals( "almost_empty", + rule.getName() ); + assertNotNull( rule.getLhs() ); + assertEquals( "", + ((String) rule.getConsequence()).trim() ); + } + + @Test + public void testQuotedStringNameRule() throws Exception { + String source = readResource("quoted_string_name_rule.drl"); + PackageDescr pkg = parser.parse(source); + + assertFalse( parser.getErrors().toString(), + parser.hasErrors() ); + + RuleDescr rule = pkg.getRules().get(0); + assertNotNull( rule ); + + assertEquals( "quoted string name", + rule.getName() ); + assertNotNull( rule.getLhs() ); + assertEquals( "", + ((String) rule.getConsequence()).trim() ); + } + + @Test + public void testNoLoop() throws Exception { + String source = readResource("no-loop.drl"); + PackageDescr pkg = parser.parse(source); + + assertFalse( parser.getErrors().toString(), + parser.hasErrors() ); + + RuleDescr rule = pkg.getRules().get(0); + assertNotNull( rule ); + + assertEquals( "rule1", + rule.getName() ); + final AttributeDescr att = (AttributeDescr) rule.getAttributes().get( "no-loop" ); + assertEquals( "false", + att.getValue() ); + assertEquals( "no-loop", + att.getName() ); + } + + @Test + public void testAutofocus() throws Exception { + String source = readResource("autofocus.drl"); + PackageDescr pkg = parser.parse(source); + + assertFalse( parser.getErrors().toString(), + parser.hasErrors() ); + + RuleDescr rule = pkg.getRules().get(0); + assertNotNull( rule ); + + assertEquals( "rule1", + rule.getName() ); + final AttributeDescr att = (AttributeDescr) rule.getAttributes().get( "auto-focus" ); + assertEquals( "true", + att.getValue() ); + assertEquals( "auto-focus", + att.getName() ); + } + + @Test + public void testRuleFlowGroup() throws Exception { + String source = readResource("ruleflowgroup.drl"); + PackageDescr pkg = parser.parse(source); + + assertFalse( parser.getErrors().toString(), + parser.hasErrors() ); + + RuleDescr rule = pkg.getRules().get(0); + assertNotNull( rule ); + + assertEquals( "rule1", + rule.getName() ); + final AttributeDescr att = (AttributeDescr) rule.getAttributes().get( "ruleflow-group" ); + assertEquals( "a group", + att.getValue() ); + assertEquals( "ruleflow-group", + att.getName() ); + } + + @Test + public void testConsequenceWithDeclaration() throws Exception { + String source = readResource("declaration-in-consequence.drl"); + PackageDescr pkg = parser.parse(source); + + // Note : Removed "i\i;" from the original declaration-in-consequence.drl under drools-test-coverage + // because it's not a valid java expression and doesn't make sense to test. (Right to raise a parser error) + + assertFalse( parser.getErrors().toString(), + parser.hasErrors() ); + + RuleDescr rule = pkg.getRules().get(0); + assertNotNull( rule ); + + assertEquals( "myrule", + rule.getName() ); + + final String expected = "int i = 0; i = 1; i / 1; i == 1; i(i); i = 'i'; i.i.i; ii; i=\"i\"; ++i;" + + "i++; --i; i--; i += i; i -= i; i *= i; i /= i;" + + "int i = 5;" + "for(int j; j 0 ); + assertTrue( ((String) rule.getConsequence()).indexOf( "--" ) > 0 ); + assertTrue( ((String) rule.getConsequence()).indexOf( "+=" ) > 0 ); + assertTrue( ((String) rule.getConsequence()).indexOf( "==" ) > 0 ); + assertTrue( ((String) rule.getConsequence()).indexOf( "i++" ) > 0 ); + // note, need to assert that "i++" is preserved as is, no extra spaces. + } } diff --git a/drools-drl/drools-drl10-parser/src/test/resources/org/drools/parser/almost_empty_rule.drl b/drools-drl/drools-drl10-parser/src/test/resources/org/drools/parser/almost_empty_rule.drl new file mode 100644 index 00000000000..149bd5b49a7 --- /dev/null +++ b/drools-drl/drools-drl10-parser/src/test/resources/org/drools/parser/almost_empty_rule.drl @@ -0,0 +1,20 @@ +/* + * Copyright 2015 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * 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. +*/ + + +rule almost_empty + when + then +end \ No newline at end of file diff --git a/drools-drl/drools-drl10-parser/src/test/resources/org/drools/parser/autofocus.drl b/drools-drl/drools-drl10-parser/src/test/resources/org/drools/parser/autofocus.drl new file mode 100644 index 00000000000..d968e700c7f --- /dev/null +++ b/drools-drl/drools-drl10-parser/src/test/resources/org/drools/parser/autofocus.drl @@ -0,0 +1,23 @@ +/* + * Copyright 2015 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * 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. +*/ + + +rule rule1 + auto-focus true + when + not Cheese(type == "stilton") + then + funky(); +end diff --git a/drools-drl/drools-drl10-parser/src/test/resources/org/drools/parser/declaration-in-consequence.drl b/drools-drl/drools-drl10-parser/src/test/resources/org/drools/parser/declaration-in-consequence.drl new file mode 100644 index 00000000000..f21bba25dfe --- /dev/null +++ b/drools-drl/drools-drl10-parser/src/test/resources/org/drools/parser/declaration-in-consequence.drl @@ -0,0 +1,46 @@ +/* + * Copyright 2015 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * 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. +*/ + + + +rule myrule + when + then + int i = 0; + i = 1; + i / 1; + i == 1; + i(i); + i = 'i'; + i.i.i; + ii; + i="i"; + ++i; + i++; + --i; + i--; + i += i; + i -= i; + i *= i; + i /= i; + int i = 5; + for(int j; j speedLimit ? true : false;) + pullEmOver(); +end