From 9f7ee1ce754dba006301f5fb9ddd90923032ffc4 Mon Sep 17 00:00:00 2001 From: Toshiya Kobayashi Date: Fri, 20 Oct 2023 15:49:51 +0900 Subject: [PATCH] [DROOLS-7306] Implement unification (#41) * [DROOLS-7306] Implement unification - Also [DROOLS-7307] Parse attribute agenda-group * [DROOLS-7308] Parse attribute without value - Also [DROOLS-7309] Parse attribute with parentheses --- .../main/antlr4/org/drools/parser/DRLLexer.g4 | 2 +- .../antlr4/org/drools/parser/DRLParser.g4 | 20 +++-- .../org/drools/parser/DRLVisitorImpl.java | 84 +++++++++++++++++-- .../org/drools/parser/MiscDRLParserTest.java | 8 -- 4 files changed, 88 insertions(+), 26 deletions(-) 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 cce083ab8a2..8aac81873cc 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 @@ -124,7 +124,7 @@ DRL_BIG_INTEGER_LITERAL ///////////////// HASH : '#'; -UNIFY : ':=' ; +DRL_UNIFY : ':=' ; NULL_SAFE_DOT : '!.' ; QUESTION_DIV : '?/' ; 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 30ba8454cc5..80774cd08ea 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 @@ -116,7 +116,7 @@ lhsUnary : ( | lhsPatternBind ) SEMI? ; -lhsPatternBind : label? ( LPAREN lhsPattern (DRL_OR lhsPattern)* RPAREN | lhsPattern ) ; +lhsPatternBind : (label|unif)? ( LPAREN lhsPattern (DRL_OR lhsPattern)* RPAREN | lhsPattern ) ; /* lhsPattern : xpathPrimary (OVER patternFilter)? | @@ -253,6 +253,7 @@ drlExpression | drlExpression bop=INSTANCEOF (typeType | pattern) | drlExpression bop=DRL_MATCHES drlExpression | drlExpression DRL_NOT? DRL_MEMBEROF drlExpression + | drlExpression bop=DRL_UNIFY drlExpression | drlExpression bop=(EQUAL | NOTEQUAL) drlExpression | drlExpression bop=BITAND drlExpression | drlExpression bop=CARET drlExpression @@ -438,12 +439,15 @@ drlElementValue ; attributes : attribute ( COMMA? attribute )* ; -attribute : ( 'salience' DECIMAL_LITERAL ) - | ( 'enabled' | 'no-loop' | 'auto-focus' | 'lock-on-active' | 'refract' | 'direct' ) BOOL_LITERAL? - | ( 'agenda-group' | 'activation-group' | 'ruleflow-group' | 'date-effective' | 'date-expires' | 'dialect' ) DRL_STRING_LITERAL - | 'calendars' DRL_STRING_LITERAL ( COMMA DRL_STRING_LITERAL )* - | 'timer' ( DECIMAL_LITERAL | TEXT ) - | 'duration' ( DECIMAL_LITERAL | TEXT ) ; +attribute : name=( 'salience' | 'enabled' ) conditionalOrExpression #expressionAttribute + | name=( 'no-loop' | 'auto-focus' | 'lock-on-active' | 'refract' | 'direct' ) BOOL_LITERAL? #booleanAttribute + | name=( 'agenda-group' | 'activation-group' | 'ruleflow-group' | 'date-effective' | 'date-expires' | 'dialect' ) DRL_STRING_LITERAL #stringAttribute + | name='calendars' DRL_STRING_LITERAL ( COMMA DRL_STRING_LITERAL )* #stringListAttribute + | name='timer' ( DECIMAL_LITERAL | chunk ) #intOrChunkAttribute + | name='duration' ( DECIMAL_LITERAL | TIME_INTERVAL | LPAREN TIME_INTERVAL RPAREN ) #durationAttribute + ; + +chunk : LPAREN .+? RPAREN; assignmentOperator : ASSIGN | ADD_ASSIGN @@ -457,7 +461,7 @@ assignmentOperator : ASSIGN | LT LT ASSIGN ; label : IDENTIFIER COLON ; -unif : IDENTIFIER UNIFY ; +unif : IDENTIFIER DRL_UNIFY ; /* extending JavaParser variableInitializer */ drlVariableInitializer 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 2a09cafd78e..3ca001611de 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 @@ -40,6 +40,7 @@ import org.drools.drl.ast.descr.TypeFieldDescr; import org.drools.drl.ast.descr.UnitDescr; import org.drools.drl.ast.descr.WindowDeclarationDescr; +import org.drools.util.StringUtils; import static org.drools.parser.DRLParserHelper.getTextWithoutErrorNode; import static org.drools.parser.ParserStringUtils.getTextPreservingWhitespace; @@ -95,10 +96,9 @@ private void applyChildrenDescrs(PackageDescr packageDescr, List desc packageDescr.addWindowDeclaration((WindowDeclarationDescr) descr); } else if (descr instanceof AttributeDescr) { packageDescr.addAttribute((AttributeDescr) descr); - } else if (descr instanceof RuleDescr) { + } else if (descr instanceof RuleDescr) { // QueryDescr extends RuleDescr packageDescr.addRule((RuleDescr) descr); - } else if (descr instanceof QueryDescr) { - packageDescr.addRule((QueryDescr) descr); + packageDescr.afterRuleAdded((RuleDescr) descr); } }); } @@ -301,12 +301,75 @@ private void visitDrlElementValuePairs(DRLParser.DrlElementValuePairsContext ctx } @Override - public AttributeDescr visitAttribute(DRLParser.AttributeContext ctx) { - AttributeDescr attributeDescr = new AttributeDescr(ctx.getChild(0).getText()); - if (ctx.getChildCount() > 1) { - // TODO : will likely split visitAttribute methods using labels (e.g. #stringAttribute) - String value = unescapeJava(safeStripStringDelimiters(ctx.getChild(1).getText())); - attributeDescr.setValue(value); + public AttributeDescr visitExpressionAttribute(DRLParser.ExpressionAttributeContext ctx) { + AttributeDescr attributeDescr = new AttributeDescr(ctx.name.getText()); + attributeDescr.setValue(getTextPreservingWhitespace(ctx.conditionalOrExpression())); + attributeDescr.setType(AttributeDescr.Type.EXPRESSION); + return attributeDescr; + } + + @Override + public AttributeDescr visitBooleanAttribute(DRLParser.BooleanAttributeContext ctx) { + AttributeDescr attributeDescr = new AttributeDescr(ctx.name.getText()); + attributeDescr.setValue(ctx.BOOL_LITERAL() != null ? ctx.BOOL_LITERAL().getText() : "true"); + attributeDescr.setType(AttributeDescr.Type.BOOLEAN); + return attributeDescr; + } + + @Override + public AttributeDescr visitStringAttribute(DRLParser.StringAttributeContext ctx) { + AttributeDescr attributeDescr = new AttributeDescr(ctx.name.getText()); + attributeDescr.setValue(unescapeJava(safeStripStringDelimiters(ctx.DRL_STRING_LITERAL().getText()))); + attributeDescr.setType(AttributeDescr.Type.STRING); + return attributeDescr; + } + + @Override + public AttributeDescr visitStringListAttribute(DRLParser.StringListAttributeContext ctx) { + AttributeDescr attributeDescr = new AttributeDescr(ctx.name.getText()); + List valueList = ctx.DRL_STRING_LITERAL().stream() + .map(ParseTree::getText) + .collect(Collectors.toList()); + attributeDescr.setValue(createStringList(valueList)); + attributeDescr.setType(AttributeDescr.Type.LIST); + return attributeDescr; + } + + private static String createStringList(List valueList) { + StringBuilder sb = new StringBuilder(); + sb.append("[ "); + for (int i = 0; i < valueList.size(); i++) { + sb.append(valueList.get(i)); + if (i < valueList.size() - 1) { + sb.append(", "); + } + } + sb.append(" ]"); + return sb.toString(); + } + + @Override + public AttributeDescr visitIntOrChunkAttribute(DRLParser.IntOrChunkAttributeContext ctx) { + AttributeDescr attributeDescr = new AttributeDescr(ctx.name.getText()); + if (ctx.DECIMAL_LITERAL() != null) { + attributeDescr.setValue(ctx.DECIMAL_LITERAL().getText()); + attributeDescr.setType(AttributeDescr.Type.NUMBER); + } else { + attributeDescr.setValue(getTextPreservingWhitespace(ctx.chunk())); + attributeDescr.setType(AttributeDescr.Type.EXPRESSION); + } + return attributeDescr; + } + + @Override + public AttributeDescr visitDurationAttribute(DRLParser.DurationAttributeContext ctx) { + AttributeDescr attributeDescr = new AttributeDescr(ctx.name.getText()); + if (ctx.DECIMAL_LITERAL() != null) { + attributeDescr.setValue(ctx.DECIMAL_LITERAL().getText()); + attributeDescr.setType(AttributeDescr.Type.NUMBER); + } else { + attributeDescr.setValue(unescapeJava(safeStripStringDelimiters(ctx.TIME_INTERVAL().getText()))); + attributeDescr.setType(AttributeDescr.Type.EXPRESSION); } return attributeDescr; } @@ -338,6 +401,9 @@ private PatternDescr getSinglePatternDescr(DRLParser.LhsPatternBindContext ctx) .orElseThrow(() -> new IllegalStateException("lhsPatternBind must have at least one lhsPattern : " + ctx.getText())); if (ctx.label() != null) { patternDescr.setIdentifier(ctx.label().IDENTIFIER().getText()); + } else if (ctx.unif() != null) { + patternDescr.setIdentifier(ctx.unif().IDENTIFIER().getText()); + patternDescr.setUnification(true); } return patternDescr; } 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 9e6a97673ea..50e7f82f7a1 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 @@ -1651,7 +1651,6 @@ public void parse_Comment() throws Exception { assertThat(pkg.getName()).isEqualTo("foo.bar"); } - @Disabled("Priority : High | Parse attribute without value => true") @Test public void parse_Attributes() throws Exception { final RuleDescr rule = parseAndGetFirstRuleDescrFromFile( @@ -1731,7 +1730,6 @@ public void parse_Attributes2() throws Exception { } - @Disabled("Priority : High | Parse attribute without value => true") @Test public void parse_AttributeRefract() throws Exception { final String source = "rule Test refract when Person() then end"; @@ -1751,7 +1749,6 @@ public void parse_AttributeRefract() throws Exception { } - @Disabled("Priority : High | Parse attribute with parentheses") @Test public void parse_EnabledExpression() throws Exception { final RuleDescr rule = parseAndGetFirstRuleDescrFromFile( @@ -1775,7 +1772,6 @@ public void parse_EnabledExpression() throws Exception { assertThat(at.getValue()).isEqualTo("true"); } - @Disabled("Priority : High | Parse attribute with parentheses") @Test public void parse_DurationExpression() throws Exception { final RuleDescr rule = parseAndGetFirstRuleDescrFromFile( @@ -1795,7 +1791,6 @@ public void parse_DurationExpression() throws Exception { assertThat(at.getValue()).isEqualTo("true"); } - @Disabled("Priority : Mid | Parse calendar attribute") @Test public void parse_Calendars() throws Exception { final RuleDescr rule = parseAndGetFirstRuleDescrFromFile( @@ -1815,7 +1810,6 @@ public void parse_Calendars() throws Exception { assertThat(at.getValue()).isEqualTo("true"); } - @Disabled("Priority : Mid | Parse calendar attribute") @Test public void parse_Calendars2() throws Exception { final RuleDescr rule = parseAndGetFirstRuleDescrFromFile( @@ -1905,7 +1899,6 @@ public void parse_SoundsLike() throws Exception { pat.getConstraint(); } - @Disabled("Priority : High | Parse attribute agenda-group") @Test public void parse_PackageAttributes() throws Exception { final PackageDescr pkg = parseAndGetPackageDescrFromFile( @@ -3132,7 +3125,6 @@ public void parse_PositionalsAndNamedConstraints() throws Exception { } - @Disabled("Priority : High | Implement unification") @Test public void parse_UnificationBinding() throws Exception { final String text = "rule X when $p := Person( $name := name, $loc : location ) then end";