From 383137c190cab040d2609f8295ef822c3917b88d Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sat, 19 Nov 2022 14:00:12 +0100 Subject: feat(language): numeric expressions --- .../java/tools/refinery/language/Problem.xtext | 137 +++++++++++----- .../refinery/language/ProblemRuntimeModule.java | 7 + .../language/formatting2/ProblemFormatter.java | 20 +-- .../parser/antlr/IdentifierTokenProvider.java | 89 ++++++++++ .../language/parser/antlr/ProblemTokenSource.java | 125 ++++++++++++++ .../antlr/TokenSourceInjectingProblemParser.java | 18 ++ .../language/resource/DerivedVariableComputer.java | 181 +++++++-------------- .../language/resource/ImplicitVariableScope.java | 138 ++++++++++++++++ .../resource/ProblemXmiResourceFactory.java | 16 -- .../language/scoping/ProblemScopeProvider.java | 36 ++-- .../tools/refinery/language/utils/ProblemUtil.java | 2 +- .../language/validation/ProblemValidator.java | 30 ++-- .../tools/refinery/language/builtin.problem | 2 +- .../language/tests/ProblemParsingTest.java | 12 +- .../tests/formatting2/ProblemFormatterTest.java | 18 +- .../parser/antlr/IdentifierTokenProviderTest.java | 39 +++++ .../tests/parser/antlr/ProblemTokenSourceTest.java | 134 +++++++++++++++ .../parser/antlr/TransitiveClosureParserTest.java | 49 ++++++ .../language/tests/scoping/NodeScopingTest.java | 23 +-- .../tests/serializer/ProblemSerializerTest.java | 39 ++--- .../language/tests/utils/SymbolCollectorTest.java | 4 +- .../model/tests/utils/WrappedArgument.java | 8 +- .../language/model/tests/utils/WrappedLiteral.java | 16 +- .../tests/utils/WrappedParametricDefinition.java | 10 +- .../tests/utils/WrappedPredicateDefinition.java | 4 + .../model/tests/utils/WrappedRuleDefinition.java | 6 +- 26 files changed, 846 insertions(+), 317 deletions(-) create mode 100644 subprojects/language/src/main/java/tools/refinery/language/parser/antlr/IdentifierTokenProvider.java create mode 100644 subprojects/language/src/main/java/tools/refinery/language/parser/antlr/ProblemTokenSource.java create mode 100644 subprojects/language/src/main/java/tools/refinery/language/parser/antlr/TokenSourceInjectingProblemParser.java create mode 100644 subprojects/language/src/main/java/tools/refinery/language/resource/ImplicitVariableScope.java delete mode 100644 subprojects/language/src/main/java/tools/refinery/language/resource/ProblemXmiResourceFactory.java create mode 100644 subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/IdentifierTokenProviderTest.java create mode 100644 subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/ProblemTokenSourceTest.java create mode 100644 subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/TransitiveClosureParserTest.java (limited to 'subprojects/language/src') diff --git a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext index 2a8429a3..bc1ee465 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext +++ b/subprojects/language/src/main/java/tools/refinery/language/Problem.xtext @@ -8,9 +8,10 @@ Problem: statements+=Statement*; Statement: - ClassDeclaration | EnumDeclaration | PredicateDefinition | /* RuleDefinition | */ Assertion | NodeValueAssertion | - ScopeDeclaration | - IndividualDeclaration; + ClassDeclaration | EnumDeclaration | + PredicateDefinition | FunctionDefinition | /* RuleDefinition | */ + Assertion | NodeValueAssertion | + ScopeDeclaration | IndividualDeclaration; ClassDeclaration: abstract?="abstract"? "class" @@ -30,53 +31,51 @@ enum ReferenceKind: REFERENCE="refers" | CONTAINMENT="contains" | CONTAINER="container"; ReferenceDeclaration: - ( - kind=ReferenceKind referenceType=[Relation|QualifiedName] | - referenceType=[Relation|NonRelationKindQualifiedName] - ) + (kind=ReferenceKind referenceType=[Relation|QualifiedName] | + referenceType=[Relation|NonRelationKindQualifiedName]) ("[" multiplicity=Multiplicity "]")? name=Identifier ("opposite" opposite=[ReferenceDeclaration|QualifiedName])?; +enum ErrorKind returns PredicateKind: + ERROR="error"; + enum PredicateKind: ERROR="error" | CONTAINED="contained" | CONTAINMENT="containment"; PredicateDefinition: - (kind=PredicateKind "pred"? | "pred") + (kind=ErrorKind | kind=PredicateKind? "pred") name=Identifier "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")" ("<->" bodies+=Conjunction (";" bodies+=Conjunction)*)? "."; +Conjunction: + literals+=Expr ("," literals+=Expr)*; + +FunctionDefinition: + functionType=[Relation|QualifiedName] name=Identifier + "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")" + ("=" cases+=Case (";" cases+=Case)*)? + "."; + +Case: + Conjunction ({Match.condition=current} "->" value=Expr)?; + //RuleDefinition: // "rule" // name=Identifier // "(" (parameters+=Parameter ("," parameters+=Parameter)*)? ")" -// (":" bodies+=Conjunction (";" bodies+=Conjunction)* +// (":" preconditions+=Conjunction (";" preconditions+=Conjunction)*)? // "==>" consequents+=Consequent (";" consequents+=Consequent)*)? // "."; Parameter: (modality=Modality? parameterType=[Relation|QualifiedName])? name=Identifier; -Conjunction: - literals+=Literal ("," literals+=Literal)*; - //Consequent: // actions+=Action ("," actions+=Action)*; - -Literal: - Atom | NegativeLiteral | CountLiteral; - -NegativeLiteral: - modality=Modality? "!" atom=Atom; - -enum ComparisonOp: - LESS="<" | LESS_EQ="<=" | GREATER=">" | GREATER_EQ=">=" | EQ="==" | NOT_EQ="!="; - -CountLiteral: - modality=Modality? "count" atom=Atom op=ComparisonOp threshold=INT; - +// //Action: // AssertionAction | DeleteAction | NewAction; // @@ -93,22 +92,74 @@ CountLiteral: //NewVariable: // name=Identifier; +Expr: + ComparisonExpr; + +enum ComparisonOp: + LESS="<" | LESS_EQ="<=" | GREATER=">" | GREATER_EQ=">=" | EQ="==" | NOT_EQ="!="; + +ComparisonExpr returns Expr: + AdditiveExpr ({ComparisonExpr.left=current} + op=ComparisonOp right=AdditiveExpr)*; + +enum AdditiveOp returns BinaryOp: + ADD="+" | SUB="-"; + +AdditiveExpr returns Expr: + MultiplicativeExpr ({ArithmeticBinaryExpr.left=current} + op=AdditiveOp right=MultiplicativeExpr)*; + +enum MultiplicativeOp returns BinaryOp: + MUL="*" | DIV="/"; + +MultiplicativeExpr returns Expr: + ExponentialExpr ({ArithmeticBinaryExpr.left=current} + op=MultiplicativeOp right=ExponentialExpr)*; + +enum ExponentialOp returns BinaryOp: + POW="**"; + +ExponentialExpr returns Expr: + UnaryExpr ({ArithmeticBinaryExpr.left=current} + op=ExponentialOp right=ExponentialExpr)?; + +UnaryExpr returns Expr: + ArithmeticUnaryExpr | ModalExpr | NegationExpr | CountExpr | AggregationExpr | + Atom | VariableOrNodeExpr | ConstantExpr | "(" Expr ")"; + +enum UnaryOp: + PLUS="+" | MINUS="-"; + +ArithmeticUnaryExpr: + op=UnaryOp body=UnaryExpr; + enum Modality: MAY="may" | MUST="must" | CURRENT="current"; +ModalExpr: + modality=Modality body=UnaryExpr; + +NegationExpr: + "!" body=UnaryExpr; + +CountExpr: + "#" body=UnaryExpr; + +enum AggregationOp: + SUM="sum" | PROD="prod" | MIN="min" | MAX="max"; + +AggregationExpr: + op=AggregationOp "{" value=Expr "|" condition=Expr "}"; + Atom: - modality=Modality? relation=[Relation|QualifiedName] - transitiveClosure?="+"? - "(" (arguments+=Argument ("," arguments+=Argument)*)? ")"; - -Argument: - VariableOrNodeArgument | ConstantArgument; + transitiveClosure?=TRANSITIVE_CLOSURE? + "(" (arguments+=Expr ("," arguments+=Expr)*)? ")"; -VariableOrNodeArgument: +VariableOrNodeExpr: variableOrNode=[VariableOrNode|QualifiedName]; -ConstantArgument: +ConstantExpr: constant=Constant; Assertion: @@ -131,7 +182,7 @@ WildcardAssertionArgument: {WildcardAssertionArgument} "*"; ConstantAssertionArgument: - constant=Constant; + negative?="-"? constant=Constant; enum LogicValue: TRUE="true" | FALSE="false" | UNKNOWN="unknown" | ERROR="error"; @@ -146,7 +197,7 @@ Constant: RealConstant | IntConstant | StringConstant; IntConstant: - intValue=Integer; + intValue=INT; RealConstant: realValue=Real; @@ -178,7 +229,7 @@ ExactMultiplicity: exactValue=INT; IndividualDeclaration: - "indiv" nodes+=EnumLiteral ("," nodes+=EnumLiteral)* "."; + "individual" nodes+=EnumLiteral ("," nodes+=EnumLiteral)* "."; UpperBound returns ecore::EInt: INT | "*"; @@ -190,18 +241,16 @@ QualifiedName hidden(): Identifier ("::" Identifier)*; NonRelationKindIdentifier: - ID | "true" | "false" | "unknown" | "error" | "class" | "abstract" | "extends" | "enum" | - "pred" | "indiv" | "problem" | /* "new" | "delete" | "rule" | */ "may" | "must" | "current" | - "count" | "default" | "scope" | "contained" | "containment"; + ID | "true" | "false" | "contained" | "sum" | "prod" | "min" | "max"; Identifier: - NonRelationKindIdentifier | "refers" | "contains" | "container"; - -Integer returns ecore::EInt hidden(): - "-"? INT; + NonRelationKindIdentifier | "contains"; Real returns ecore::EDouble: - "-"? (EXPONENTIAL | INT "." (INT | EXPONENTIAL)); + EXPONENTIAL | INT "." (INT | EXPONENTIAL); + +terminal TRANSITIVE_CLOSURE: + "synthetic:TRANSITIVE_CLOSURE"; @Override terminal ID: diff --git a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java index dd7731b4..c0777038 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java +++ b/subprojects/language/src/main/java/tools/refinery/language/ProblemRuntimeModule.java @@ -5,6 +5,7 @@ package tools.refinery.language; import org.eclipse.xtext.conversion.IValueConverterService; import org.eclipse.xtext.naming.IQualifiedNameConverter; +import org.eclipse.xtext.parser.IParser; import org.eclipse.xtext.resource.DerivedStateAwareResource; import org.eclipse.xtext.resource.DerivedStateAwareResourceDescriptionManager; import org.eclipse.xtext.resource.IDefaultResourceDescriptionStrategy; @@ -23,6 +24,7 @@ import com.google.inject.name.Names; import tools.refinery.language.conversion.ProblemValueConverterService; import tools.refinery.language.naming.ProblemQualifiedNameConverter; +import tools.refinery.language.parser.antlr.TokenSourceInjectingProblemParser; import tools.refinery.language.resource.ProblemDerivedStateComputer; import tools.refinery.language.resource.ProblemLocationInFileProvider; import tools.refinery.language.resource.ProblemResourceDescriptionStrategy; @@ -34,6 +36,11 @@ import tools.refinery.language.scoping.ProblemLocalScopeProvider; * Equinox extension registry. */ public class ProblemRuntimeModule extends AbstractProblemRuntimeModule { + @Override + public Class bindIParser() { + return TokenSourceInjectingProblemParser.class; + } + public Class bindIQualifiedNameConverter() { return ProblemQualifiedNameConverter.class; } diff --git a/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java b/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java index df5d2090..a65e0750 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java +++ b/subprojects/language/src/main/java/tools/refinery/language/formatting2/ProblemFormatter.java @@ -11,17 +11,9 @@ import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionsFinder; import org.eclipse.xtext.formatting2.regionaccess.ISequentialRegion; import org.eclipse.xtext.xbase.lib.Procedures.Procedure1; -import tools.refinery.language.model.problem.Assertion; -import tools.refinery.language.model.problem.Atom; -import tools.refinery.language.model.problem.ClassDeclaration; -import tools.refinery.language.model.problem.Conjunction; -import tools.refinery.language.model.problem.IndividualDeclaration; -import tools.refinery.language.model.problem.NegativeLiteral; -import tools.refinery.language.model.problem.Parameter; -import tools.refinery.language.model.problem.PredicateDefinition; -import tools.refinery.language.model.problem.Problem; -import tools.refinery.language.model.problem.ProblemPackage; +import tools.refinery.language.model.problem.*; +@SuppressWarnings("UnstableApiUsage") public class ProblemFormatter extends AbstractJavaFormatter { protected void format(Problem problem, IFormattableDocument doc) { @@ -95,16 +87,14 @@ public class ProblemFormatter extends AbstractJavaFormatter { } } - protected void format(NegativeLiteral literal, IFormattableDocument doc) { + protected void format(NegationExpr literal, IFormattableDocument doc) { var region = regionFor(literal); - doc.append(region.feature(ProblemPackage.Literals.LITERAL__MODALITY), this::oneSpace); doc.append(region.keyword("!"), this::noSpace); - doc.format(literal.getAtom()); + doc.format(literal.getBody()); } protected void format(Atom atom, IFormattableDocument doc) { var region = regionFor(atom); - doc.append(region.feature(ProblemPackage.Literals.LITERAL__MODALITY), this::oneSpace); doc.append(region.feature(ProblemPackage.Literals.ATOM__RELATION), this::noSpace); doc.append(region.feature(ProblemPackage.Literals.ATOM__TRANSITIVE_CLOSURE), this::noSpace); formatParenthesizedList(region, doc); @@ -116,7 +106,7 @@ public class ProblemFormatter extends AbstractJavaFormatter { protected void format(IndividualDeclaration individualDeclaration, IFormattableDocument doc) { surroundNewLines(doc, individualDeclaration, this::singleNewLine); var region = regionFor(individualDeclaration); - doc.append(region.keyword("indiv"), this::oneSpace); + doc.append(region.keyword("individual"), this::oneSpace); formatList(region, ",", doc); doc.prepend(region.keyword("."), this::noSpace); } diff --git a/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/IdentifierTokenProvider.java b/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/IdentifierTokenProvider.java new file mode 100644 index 00000000..ab133a90 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/IdentifierTokenProvider.java @@ -0,0 +1,89 @@ +package tools.refinery.language.parser.antlr; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import org.eclipse.xtext.*; +import org.eclipse.xtext.parser.antlr.ITokenDefProvider; +import tools.refinery.language.services.ProblemGrammarAccess; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +@Singleton +public class IdentifierTokenProvider { + private final int[] identifierTokensArray; + + @Inject + private IdentifierTokenProvider(Initializer initializer) { + this.identifierTokensArray = initializer.getIdentifierTokesArray(); + } + + public boolean isIdentifierToken(int tokenId) { + for (int identifierTokenId : identifierTokensArray) { + if (identifierTokenId == tokenId) { + return true; + } + } + return false; + } + + private static class Initializer { + @Inject + private ITokenDefProvider tokenDefProvider; + + @Inject + private ProblemGrammarAccess problemGrammarAccess; + + private HashMap valueToTokenIdMap; + + private Set identifierTokens; + + public int[] getIdentifierTokesArray() { + createValueToTokenIdMap(); + identifierTokens = new HashSet<>(); + collectIdentifierTokensFromRule(problemGrammarAccess.getIdentifierRule()); + var identifierTokensArray = new int[identifierTokens.size()]; + int i = 0; + for (var tokenId : identifierTokens) { + identifierTokensArray[i] = tokenId; + i++; + } + return identifierTokensArray; + } + + private void createValueToTokenIdMap() { + var tokenIdToValueMap = tokenDefProvider.getTokenDefMap(); + valueToTokenIdMap = new HashMap<>(tokenIdToValueMap.size()); + for (var entry : tokenIdToValueMap.entrySet()) { + valueToTokenIdMap.put(entry.getValue(), entry.getKey()); + } + } + + private void collectIdentifierTokensFromRule(AbstractRule rule) { + if (rule instanceof TerminalRule) { + collectToken("RULE_" + rule.getName()); + return; + } + collectIdentifierTokensFromElement(rule.getAlternatives()); + } + + private void collectIdentifierTokensFromElement(AbstractElement element) { + if (element instanceof Alternatives alternatives) { + for (var alternative : alternatives.getElements()) { + collectIdentifierTokensFromElement(alternative); + } + } else if (element instanceof RuleCall ruleCall) { + collectIdentifierTokensFromRule(ruleCall.getRule()); + } else if (element instanceof Keyword keyword) { + collectToken("'" + keyword.getValue() + "'"); + } else { + throw new IllegalArgumentException("Unknown Xtext grammar element: " + element); + } + } + + private void collectToken(String value) { + identifierTokens.add(valueToTokenIdMap.get(value)); + } + } +} diff --git a/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/ProblemTokenSource.java b/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/ProblemTokenSource.java new file mode 100644 index 00000000..0b4e7185 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/ProblemTokenSource.java @@ -0,0 +1,125 @@ +/* + * generated by Xtext 2.29.0.M2 + */ +package tools.refinery.language.parser.antlr; + +import com.google.inject.Inject; +import org.antlr.runtime.Token; +import org.antlr.runtime.TokenSource; +import tools.refinery.language.parser.antlr.internal.InternalProblemParser; + +import java.util.ArrayDeque; +import java.util.Deque; + +public class ProblemTokenSource implements TokenSource { + private IdentifierTokenProvider identifierTokenProvider; + + private final TokenSource delegate; + + private final Deque buffer = new ArrayDeque<>(); + + private boolean recursive; + + private boolean seenId; + + public ProblemTokenSource(TokenSource delegate) { + this.delegate = delegate; + } + + @Inject + public void setIdentifierTokenProvider(IdentifierTokenProvider identifierTokenProvider) { + this.identifierTokenProvider = identifierTokenProvider; + } + + public boolean isRecursive() { + return recursive; + } + + public void setRecursive(boolean recursive) { + this.recursive = recursive; + } + + @Override + public Token nextToken() { + if (!buffer.isEmpty()) { + return buffer.removeFirst(); + } + var token = delegate.nextToken(); + if (isIdentifier(token)) { + seenId = true; + } else if (seenId && isPlusOrTransitiveClosure(token)) { + if (peekForTransitiveClosure()) { + token.setType(InternalProblemParser.RULE_TRANSITIVE_CLOSURE); + } + } else if (isVisibleToken(token)) { + seenId = false; + } + return token; + } + + @Override + public String getSourceName() { + return "[%s]%s".formatted(this.getClass().getSimpleName(), delegate.getSourceName()); + } + + protected boolean isIdentifier(Token token) { + return identifierTokenProvider.isIdentifierToken(token.getType()); + } + + protected boolean isPlusOrTransitiveClosure(Token token) { + return token.getType() == InternalProblemParser.PlusSign; + } + + protected boolean isVisibleToken(Token token) { + int tokenId = token.getType(); + return tokenId != InternalProblemParser.RULE_WS && tokenId != InternalProblemParser.RULE_SL_COMMENT && + tokenId != InternalProblemParser.RULE_ML_COMMENT; + } + + protected boolean peekForTransitiveClosure() { + Token token = peekWithSkipWhitespace(); + if (token.getType() != InternalProblemParser.LeftParenthesis) { + return false; + } + while (true) { + token = peekWithSkipWhitespace(); + if (!isIdentifier(token)) { + return false; + } + token = peekWithSkipWhitespace(); + switch (token.getType()) { + case InternalProblemParser.Comma: + return true; + case InternalProblemParser.ColonColon: + break; + default: + // By default, we do not peek at inner plus signs to limit recursion depth. + // Such expressions are never valid, so we don't have to parse them correctly. + if (recursive && isPlusOrTransitiveClosure(token) && peekForTransitiveClosure()) { + token.setType(InternalProblemParser.RULE_TRANSITIVE_CLOSURE); + } + // Not a transitive closure for the initial position where we started peeking. + return false; + } + } + } + + protected Token peekToken() { + var token = delegate.nextToken(); + if (isIdentifier(token)) { + seenId = true; + } else if (isVisibleToken(token)) { + seenId = false; + } + buffer.addLast(token); + return token; + } + + protected Token peekWithSkipWhitespace() { + Token token; + do { + token = peekToken(); + } while (token != null && !isVisibleToken(token)); + return token; + } +} diff --git a/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/TokenSourceInjectingProblemParser.java b/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/TokenSourceInjectingProblemParser.java new file mode 100644 index 00000000..0cdd38d8 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/parser/antlr/TokenSourceInjectingProblemParser.java @@ -0,0 +1,18 @@ +package tools.refinery.language.parser.antlr; + +import com.google.inject.Inject; +import com.google.inject.Injector; +import org.antlr.runtime.CharStream; +import org.antlr.runtime.TokenSource; + +public class TokenSourceInjectingProblemParser extends ProblemParser { + @Inject + private Injector injector; + + @Override + protected TokenSource createLexer(CharStream stream) { + var tokenSource = super.createLexer(stream); + injector.injectMembers(tokenSource); + return tokenSource; + } +} diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java b/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java index b76c4bf7..6176b0c4 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/DerivedVariableComputer.java @@ -1,36 +1,15 @@ package tools.refinery.language.resource; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.google.inject.name.Named; import org.eclipse.xtext.linking.impl.LinkingHelper; import org.eclipse.xtext.naming.IQualifiedNameConverter; -import org.eclipse.xtext.nodemodel.INode; -import org.eclipse.xtext.nodemodel.util.NodeModelUtils; -import org.eclipse.xtext.scoping.IScope; import org.eclipse.xtext.scoping.IScopeProvider; import org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider; +import tools.refinery.language.model.problem.*; -import com.google.inject.Inject; -import com.google.inject.Singleton; -import com.google.inject.name.Named; - -import tools.refinery.language.model.problem.Argument; -import tools.refinery.language.model.problem.Atom; -import tools.refinery.language.model.problem.CompoundLiteral; -import tools.refinery.language.model.problem.Conjunction; -import tools.refinery.language.model.problem.ExistentialQuantifier; -import tools.refinery.language.model.problem.ImplicitVariable; -import tools.refinery.language.model.problem.Literal; -import tools.refinery.language.model.problem.Parameter; -import tools.refinery.language.model.problem.ParametricDefinition; -import tools.refinery.language.model.problem.Problem; -import tools.refinery.language.model.problem.ProblemFactory; -import tools.refinery.language.model.problem.ProblemPackage; -import tools.refinery.language.model.problem.Statement; -import tools.refinery.language.model.problem.VariableOrNodeArgument; -import tools.refinery.language.naming.NamingUtil; +import java.util.*; @Singleton public class DerivedVariableComputer { @@ -53,107 +32,60 @@ public class DerivedVariableComputer { } protected void installDerivedParametricDefinitionState(ParametricDefinition definition, Set nodeNames) { - Set knownVariables = new HashSet<>(); - knownVariables.addAll(nodeNames); + Set knownVariables = new HashSet<>(nodeNames); for (Parameter parameter : definition.getParameters()) { String name = parameter.getName(); if (name != null) { knownVariables.add(name); } } - for (Conjunction body : definition.getBodies()) { - installDeriveConjunctionState(body, knownVariables); + if (definition instanceof PredicateDefinition predicateDefinition) { + installDerivedPredicateDefinitionState(predicateDefinition, knownVariables); + } else if (definition instanceof FunctionDefinition functionDefinition) { + installDerivedFunctionDefinitionState(functionDefinition, knownVariables); + } else if (definition instanceof RuleDefinition ruleDefinition) { + installDerivedRuleDefinitionState(ruleDefinition, knownVariables); + } else { + throw new IllegalArgumentException("Unknown ParametricDefinition: " + definition); } } - protected void installDeriveConjunctionState(Conjunction conjunction, Set knownVariables) { - Set newVariables = new HashSet<>(); - for (Literal literal : conjunction.getLiterals()) { - if (literal instanceof Atom atom) { - createSigletonVariablesAndCollectVariables(atom, knownVariables, newVariables); - } - } - createVariables(conjunction, newVariables); - newVariables.addAll(knownVariables); - for (Literal literal : conjunction.getLiterals()) { - if (literal instanceof CompoundLiteral compoundLiteral) { - installDerivedCompoundLiteralState(compoundLiteral, newVariables); - } + protected void installDerivedPredicateDefinitionState(PredicateDefinition definition, Set knownVariables) { + for (Conjunction body : definition.getBodies()) { + createVariablesForScope(new ImplicitVariableScope(body, knownVariables)); } } - protected void installDerivedCompoundLiteralState(CompoundLiteral compoundLiteral, Set knownVariables) { - Set newVariables = new HashSet<>(); - createSigletonVariablesAndCollectVariables(compoundLiteral.getAtom(), knownVariables, newVariables); - createVariables(compoundLiteral, newVariables); - } - - protected void createSigletonVariablesAndCollectVariables(Atom atom, Set knownVariables, - Set newVariables) { - for (Argument argument : atom.getArguments()) { - if (argument instanceof VariableOrNodeArgument variableOrNodeArgument) { - IScope scope = scopeProvider.getScope(variableOrNodeArgument, - ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__VARIABLE_OR_NODE); - List nodes = NodeModelUtils.findNodesForFeature(variableOrNodeArgument, - ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__VARIABLE_OR_NODE); - for (INode node : nodes) { - var variableName = linkingHelper.getCrossRefNodeAsString(node, true); - var created = tryCreateVariableForArgument(variableOrNodeArgument, variableName, scope, - knownVariables, newVariables); - if (created) { - break; - } + protected void installDerivedFunctionDefinitionState(FunctionDefinition definition, Set knownVariables) { + for (Case body : definition.getCases()) { + if (body instanceof Conjunction conjunction) { + createVariablesForScope(new ImplicitVariableScope(conjunction, knownVariables)); + } else if (body instanceof Match match) { + var condition = match.getCondition(); + if (condition != null) { + createVariablesForScope(new ImplicitVariableScope(match, match.getCondition(), knownVariables)); } + } else { + throw new IllegalArgumentException("Unknown Case: " + body); } } } - protected boolean tryCreateVariableForArgument(VariableOrNodeArgument variableOrNodeArgument, String variableName, - IScope scope, Set knownVariables, Set newVariables) { - if (!NamingUtil.isValidId(variableName)) { - return false; - } - var qualifiedName = qualifiedNameConverter.toQualifiedName(variableName); - if (scope.getSingleElement(qualifiedName) != null) { - return false; - } - if (NamingUtil.isSingletonVariableName(variableName)) { - createSingletonVariable(variableOrNodeArgument, variableName); - return true; - } - if (!knownVariables.contains(variableName)) { - newVariables.add(variableName); - return true; - } - return false; - } - - protected void createVariables(ExistentialQuantifier quantifier, Set newVariables) { - for (String variableName : newVariables) { - createVariable(quantifier, variableName); - } - } - - protected void createVariable(ExistentialQuantifier quantifier, String variableName) { - if (NamingUtil.isValidId(variableName)) { - ImplicitVariable variable = createNamedVariable(variableName); - quantifier.getImplicitVariables().add(variable); + protected void installDerivedRuleDefinitionState(RuleDefinition definition, Set knownVariables) { + for (Conjunction precondition : definition.getPreconditions()) { + createVariablesForScope(new ImplicitVariableScope(precondition, knownVariables)); } } - protected void createSingletonVariable(VariableOrNodeArgument argument, String variableName) { - if (NamingUtil.isValidId(variableName)) { - ImplicitVariable variable = createNamedVariable(variableName); - argument.setSingletonVariable(variable); + protected void createVariablesForScope(ImplicitVariableScope scope) { + var queue = new ArrayDeque(); + queue.addLast(scope); + while (!queue.isEmpty()) { + var nextScope = queue.removeFirst(); + nextScope.createVariables(scopeProvider, linkingHelper, qualifiedNameConverter, queue); } } - protected ImplicitVariable createNamedVariable(String variableName) { - var variable = ProblemFactory.eINSTANCE.createImplicitVariable(); - variable.setName(variableName); - return variable; - } - public void discardDerivedVariables(Problem problem) { for (Statement statement : problem.getStatements()) { if (statement instanceof ParametricDefinition parametricDefinition) { @@ -163,28 +95,31 @@ public class DerivedVariableComputer { } protected void discardParametricDefinitionState(ParametricDefinition definition) { - for (Conjunction body : definition.getBodies()) { - body.getImplicitVariables().clear(); - for (Literal literal : body.getLiterals()) { - if (literal instanceof Atom atom) { - discardDerivedAtomState(atom); - } - if (literal instanceof CompoundLiteral compoundLiteral) { - compoundLiteral.getImplicitVariables().clear(); - discardDerivedAtomState(compoundLiteral.getAtom()); - } + List existentialQuantifiers = new ArrayList<>(); + List variableOrNodeExprs = new ArrayList<>(); + var treeIterator = definition.eAllContents(); + // We must collect the nodes where we are discarding derived state and only discard them after the iteration, + // because modifying the containment hierarchy during iteration causes the TreeIterator to fail with + // IndexOutOfBoundsException. + while (treeIterator.hasNext()) { + var child = treeIterator.next(); + var containingFeature = child.eContainingFeature(); + if (containingFeature == ProblemPackage.Literals.EXISTENTIAL_QUANTIFIER__IMPLICIT_VARIABLES || + containingFeature == ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__SINGLETON_VARIABLE) { + treeIterator.prune(); + } else if (child instanceof ExistentialQuantifier existentialQuantifier && + !existentialQuantifier.getImplicitVariables().isEmpty()) { + existentialQuantifiers.add(existentialQuantifier); + } else if (child instanceof VariableOrNodeExpr variableOrNodeExpr && + variableOrNodeExpr.getSingletonVariable() != null) { + variableOrNodeExprs.add(variableOrNodeExpr); } } - } - - protected void discardDerivedAtomState(Atom atom) { - if (atom == null) { - return; + for (var existentialQuantifier : existentialQuantifiers) { + existentialQuantifier.getImplicitVariables().clear(); } - for (Argument argument : atom.getArguments()) { - if (argument instanceof VariableOrNodeArgument variableOrNodeArgument) { - variableOrNodeArgument.setSingletonVariable(null); - } + for (var variableOrNodeExpr : variableOrNodeExprs) { + variableOrNodeExpr.setSingletonVariable(null); } } } diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ImplicitVariableScope.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ImplicitVariableScope.java new file mode 100644 index 00000000..b0ac2ab6 --- /dev/null +++ b/subprojects/language/src/main/java/tools/refinery/language/resource/ImplicitVariableScope.java @@ -0,0 +1,138 @@ +package tools.refinery.language.resource; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.linking.impl.LinkingHelper; +import org.eclipse.xtext.naming.IQualifiedNameConverter; +import org.eclipse.xtext.naming.QualifiedName; +import org.eclipse.xtext.nodemodel.INode; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; +import org.eclipse.xtext.scoping.IScope; +import org.eclipse.xtext.scoping.IScopeProvider; +import tools.refinery.language.model.problem.*; +import tools.refinery.language.naming.NamingUtil; + +import java.util.Deque; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class ImplicitVariableScope { + private final EObject root; + + private final ExistentialQuantifier quantifier; + + private final ImplicitVariableScope parent; + + private Set knownVariables; + + private ImplicitVariableScope(ExistentialQuantifier quantifier, ImplicitVariableScope parent) { + this.root = quantifier; + this.quantifier = quantifier; + this.parent = parent; + this.knownVariables = null; + } + + public ImplicitVariableScope(EObject root, ExistentialQuantifier quantifier, Set knownVariables) { + this.root = root; + this.quantifier = quantifier; + this.parent = null; + this.knownVariables = new HashSet<>(knownVariables); + } + + public ImplicitVariableScope(ExistentialQuantifier root, Set knownVariables) { + this(root, root, knownVariables); + } + + public void createVariables(IScopeProvider scopeProvider, LinkingHelper linkingHelper, + IQualifiedNameConverter qualifiedNameConverter, + Deque scopeQueue) { + initializeKnownVariables(); + processEObject(root, scopeProvider, linkingHelper, qualifiedNameConverter); + var treeIterator = root.eAllContents(); + while (treeIterator.hasNext()) { + var child = treeIterator.next(); + if (child instanceof ExistentialQuantifier nestedQuantifier) { + scopeQueue.addLast(new ImplicitVariableScope(nestedQuantifier, this)); + treeIterator.prune(); + } else { + processEObject(child, scopeProvider, linkingHelper, qualifiedNameConverter); + } + } + } + + private void initializeKnownVariables() { + boolean hasKnownVariables = knownVariables != null; + boolean hasParent = parent != null; + if ((hasKnownVariables && hasParent) || (!hasKnownVariables && !hasParent)) { + throw new IllegalStateException("Either known variables or parent must be provided, but not both"); + } + if (hasKnownVariables) { + return; + } + if (parent.knownVariables == null) { + throw new IllegalStateException("Parent scope must be processed before current scope"); + } + knownVariables = new HashSet<>(parent.knownVariables); + } + + private void processEObject(EObject eObject, IScopeProvider scopeProvider, LinkingHelper linkingHelper, + IQualifiedNameConverter qualifiedNameConverter) { + if (!(eObject instanceof VariableOrNodeExpr variableOrNodeExpr)) { + return; + } + IScope scope = scopeProvider.getScope(variableOrNodeExpr, + ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE); + List nodes = NodeModelUtils.findNodesForFeature(variableOrNodeExpr, + ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE); + for (INode node : nodes) { + var variableName = linkingHelper.getCrossRefNodeAsString(node, true); + var created = tryCreateVariableForArgument(variableOrNodeExpr, variableName, qualifiedNameConverter, + scope); + if (created) { + break; + } + } + } + + protected boolean tryCreateVariableForArgument(VariableOrNodeExpr variableOrNodeExpr, String variableName, + IQualifiedNameConverter qualifiedNameConverter, IScope scope) { + if (!NamingUtil.isValidId(variableName)) { + return false; + } + QualifiedName qualifiedName; + try { + qualifiedName = qualifiedNameConverter.toQualifiedName(variableName); + } catch (IllegalArgumentException e) { + return false; + } + if (scope.getSingleElement(qualifiedName) != null) { + return false; + } + if (NamingUtil.isSingletonVariableName(variableName)) { + createSingletonVariable(variableOrNodeExpr, variableName); + return true; + } + if (!knownVariables.contains(variableName)) { + createVariable(variableName); + return true; + } + return false; + } + + protected void createVariable(String variableName) { + knownVariables.add(variableName); + ImplicitVariable variable = createNamedVariable(variableName); + quantifier.getImplicitVariables().add(variable); + } + + protected void createSingletonVariable(VariableOrNodeExpr variableOrNodeExpr, String variableName) { + ImplicitVariable variable = createNamedVariable(variableName); + variableOrNodeExpr.setSingletonVariable(variable); + } + + protected ImplicitVariable createNamedVariable(String variableName) { + var variable = ProblemFactory.eINSTANCE.createImplicitVariable(); + variable.setName(variableName); + return variable; + } +} diff --git a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemXmiResourceFactory.java b/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemXmiResourceFactory.java deleted file mode 100644 index 68aa6016..00000000 --- a/subprojects/language/src/main/java/tools/refinery/language/resource/ProblemXmiResourceFactory.java +++ /dev/null @@ -1,16 +0,0 @@ -package tools.refinery.language.resource; - -import org.eclipse.emf.common.util.URI; -import org.eclipse.emf.ecore.resource.Resource; -import org.eclipse.xtext.resource.IResourceFactory; - -import tools.refinery.language.model.problem.util.ProblemResourceFactoryImpl; - -public class ProblemXmiResourceFactory implements IResourceFactory { - private Resource.Factory problemResourceFactory = new ProblemResourceFactoryImpl(); - - @Override - public Resource createResource(URI uri) { - return problemResourceFactory.createResource(uri); - } -} diff --git a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java index 567c3c26..c2045aea 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java +++ b/subprojects/language/src/main/java/tools/refinery/language/scoping/ProblemScopeProvider.java @@ -3,34 +3,23 @@ */ package tools.refinery.language.scoping; -import java.util.ArrayList; -import java.util.List; - +import com.google.inject.Inject; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.scoping.IScope; import org.eclipse.xtext.scoping.Scopes; - -import com.google.inject.Inject; - -import tools.refinery.language.model.problem.ClassDeclaration; -import tools.refinery.language.model.problem.Consequent; -import tools.refinery.language.model.problem.ExistentialQuantifier; -import tools.refinery.language.model.problem.NewAction; -import tools.refinery.language.model.problem.ParametricDefinition; -import tools.refinery.language.model.problem.Problem; -import tools.refinery.language.model.problem.ProblemPackage; -import tools.refinery.language.model.problem.ReferenceDeclaration; -import tools.refinery.language.model.problem.Variable; -import tools.refinery.language.model.problem.VariableOrNodeArgument; +import tools.refinery.language.model.problem.*; import tools.refinery.language.utils.ProblemDesugarer; +import java.util.ArrayList; +import java.util.List; + /** * This class contains custom scoping description. - * + *

* See - * https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#scoping + * ... * on how and when to use it. */ public class ProblemScopeProvider extends AbstractProblemScopeProvider { @@ -44,7 +33,7 @@ public class ProblemScopeProvider extends AbstractProblemScopeProvider { || reference == ProblemPackage.Literals.NODE_VALUE_ASSERTION__NODE) { return getNodesScope(context, scope); } - if (reference == ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__VARIABLE_OR_NODE + if (reference == ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE || reference == ProblemPackage.Literals.NEW_ACTION__PARENT || reference == ProblemPackage.Literals.DELETE_ACTION__VARIABLE_OR_NODE) { return getVariableScope(context, scope); @@ -80,8 +69,8 @@ public class ProblemScopeProvider extends AbstractProblemScopeProvider { } protected void addSingletonVariableToScope(EObject context, List variables) { - if (context instanceof VariableOrNodeArgument argument) { - Variable singletonVariable = argument.getSingletonVariable(); + if (context instanceof VariableOrNodeExpr expr) { + Variable singletonVariable = expr.getSingletonVariable(); if (singletonVariable != null) { variables.add(singletonVariable); } @@ -91,6 +80,8 @@ public class ProblemScopeProvider extends AbstractProblemScopeProvider { protected void addExistentiallyQualifiedVariableToScope(EObject currentContext, List variables) { if (currentContext instanceof ExistentialQuantifier quantifier) { variables.addAll(quantifier.getImplicitVariables()); + } else if (currentContext instanceof Match match) { + variables.addAll(match.getCondition().getImplicitVariables()); } else if (currentContext instanceof Consequent consequent) { for (var literal : consequent.getActions()) { if (literal instanceof NewAction newAction && newAction.getVariable() != null) { @@ -106,10 +97,9 @@ public class ProblemScopeProvider extends AbstractProblemScopeProvider { return delegateScope; } var relation = referenceDeclaration.getReferenceType(); - if (!(relation instanceof ClassDeclaration)) { + if (!(relation instanceof ClassDeclaration classDeclaration)) { return delegateScope; } - var classDeclaration = (ClassDeclaration) relation; var referenceDeclarations = desugarer.getAllReferenceDeclarations(classDeclaration); return Scopes.scopeFor(referenceDeclarations, delegateScope); } diff --git a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java index a7acd747..1e5164d3 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java +++ b/subprojects/language/src/main/java/tools/refinery/language/utils/ProblemUtil.java @@ -44,7 +44,7 @@ public final class ProblemUtil { } public static boolean isSingletonVariable(Variable variable) { - return variable.eContainingFeature() == ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__SINGLETON_VARIABLE; + return variable.eContainingFeature() == ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__SINGLETON_VARIABLE; } public static boolean isImplicitVariable(Variable variable) { diff --git a/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java index a0e78e1d..659d882c 100644 --- a/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java +++ b/subprojects/language/src/main/java/tools/refinery/language/validation/ProblemValidator.java @@ -8,19 +8,15 @@ import org.eclipse.xtext.validation.Check; import com.google.inject.Inject; -import tools.refinery.language.model.problem.Node; -import tools.refinery.language.model.problem.Problem; -import tools.refinery.language.model.problem.ProblemPackage; -import tools.refinery.language.model.problem.Variable; -import tools.refinery.language.model.problem.VariableOrNodeArgument; +import tools.refinery.language.model.problem.*; import tools.refinery.language.resource.ReferenceCounter; import tools.refinery.language.utils.ProblemUtil; /** * This class contains custom validation rules. - * + *

* See - * https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#validation + * ... */ public class ProblemValidator extends AbstractProblemValidator { private static final String ISSUE_PREFIX = "tools.refinery.language.validation.ProblemValidator."; @@ -33,29 +29,29 @@ public class ProblemValidator extends AbstractProblemValidator { private ReferenceCounter referenceCounter; @Check - public void checkUniqueVariable(VariableOrNodeArgument argument) { - var variableOrNode = argument.getVariableOrNode(); + public void checkUniqueVariable(VariableOrNodeExpr expr) { + var variableOrNode = expr.getVariableOrNode(); if (variableOrNode instanceof Variable variable && ProblemUtil.isImplicitVariable(variable) && !ProblemUtil.isSingletonVariable(variable)) { var problem = EcoreUtil2.getContainerOfType(variable, Problem.class); if (problem != null && referenceCounter.countReferences(problem, variable) <= 1) { var name = variable.getName(); - var message = "Variable '%s' has only a single reference. Add another reference or mark is as a singleton variable: '_%s'" - .formatted(name, name); - warning(message, argument, ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__VARIABLE_OR_NODE, + var message = ("Variable '%s' has only a single reference. " + + "Add another reference or mark is as a singleton variable: '_%s'").formatted(name, name); + warning(message, expr, ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE, INSIGNIFICANT_INDEX, SINGLETON_VARIABLE_ISSUE); } } } @Check - public void checkNonUniqueNode(VariableOrNodeArgument argument) { - var variableOrNode = argument.getVariableOrNode(); + public void checkNonUniqueNode(VariableOrNodeExpr expr) { + var variableOrNode = expr.getVariableOrNode(); if (variableOrNode instanceof Node node && !ProblemUtil.isIndividualNode(node)) { var name = node.getName(); - var message = "Only individual nodes can be referenced in predicates. Mark '%s' as individual with the declaration 'indiv %s.'" - .formatted(name, name); - error(message, argument, ProblemPackage.Literals.VARIABLE_OR_NODE_ARGUMENT__VARIABLE_OR_NODE, + var message = ("Only individual nodes can be referenced in predicates. " + + "Mark '%s' as individual with the declaration 'indiv %s.'").formatted(name, name); + error(message, expr, ProblemPackage.Literals.VARIABLE_OR_NODE_EXPR__VARIABLE_OR_NODE, INSIGNIFICANT_INDEX, NON_INDIVIDUAL_NODE_ISSUE); } } diff --git a/subprojects/language/src/main/resources/tools/refinery/language/builtin.problem b/subprojects/language/src/main/resources/tools/refinery/language/builtin.problem index 2f7c667a..38e77237 100644 --- a/subprojects/language/src/main/resources/tools/refinery/language/builtin.problem +++ b/subprojects/language/src/main/resources/tools/refinery/language/builtin.problem @@ -30,7 +30,7 @@ pred root(node node). % !contains(node, _), !root(node). % % error tooManyContainers(contained node) <-> -% count contains(_, node) > 1 +% #contains(_, node) > 1 % ; % contains(_, node), root(node) % ; diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.java index 1e8682a3..3a6e015f 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/ProblemParsingTest.java @@ -1,17 +1,15 @@ package tools.refinery.language.tests; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.empty; - +import com.google.inject.Inject; import org.eclipse.xtext.testing.InjectWith; import org.eclipse.xtext.testing.extensions.InjectionExtension; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - -import com.google.inject.Inject; - import tools.refinery.language.model.tests.utils.ProblemParseHelper; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; + @ExtendWith(InjectionExtension.class) @InjectWith(ProblemInjectorProvider.class) class ProblemParsingTest { @@ -40,7 +38,7 @@ class ProblemParsingTest { error invalidTaxStatus(Person p) <-> taxStatus(p, child), children(p, _q). - indiv family. + individual family. Family(family). members(family, anne): true. members(family, bob). diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java index 083c5184..a6e38130 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/formatting2/ProblemFormatterTest.java @@ -1,10 +1,7 @@ package tools.refinery.language.tests.formatting2; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; - -import java.util.List; - +import com.google.inject.Inject; +import com.google.inject.Provider; import org.eclipse.xtext.formatting2.FormatterRequest; import org.eclipse.xtext.formatting2.IFormatter2; import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess; @@ -16,13 +13,14 @@ import org.eclipse.xtext.testing.extensions.InjectionExtension; import org.eclipse.xtext.testing.util.ParseHelper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - -import com.google.inject.Inject; -import com.google.inject.Provider; - import tools.refinery.language.model.problem.Problem; import tools.refinery.language.tests.ProblemInjectorProvider; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + @ExtendWith(InjectionExtension.class) @InjectWith(ProblemInjectorProvider.class) class ProblemFormatterTest { @@ -181,7 +179,7 @@ class ProblemFormatterTest { @Test void individualDeclarationTest() { - testFormatter(" indiv a , b . ", "indiv a, b.\n"); + testFormatter(" individual a , b . ", "individual a, b.\n"); } @Test diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/IdentifierTokenProviderTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/IdentifierTokenProviderTest.java new file mode 100644 index 00000000..abff8d9c --- /dev/null +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/IdentifierTokenProviderTest.java @@ -0,0 +1,39 @@ +package tools.refinery.language.tests.parser.antlr; + +import com.google.inject.Inject; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import tools.refinery.language.parser.antlr.IdentifierTokenProvider; +import tools.refinery.language.parser.antlr.internal.InternalProblemParser; +import tools.refinery.language.tests.ProblemInjectorProvider; + +import java.util.stream.Stream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +@ExtendWith(InjectionExtension.class) +@InjectWith(ProblemInjectorProvider.class) +class IdentifierTokenProviderTest { + @Inject + private IdentifierTokenProvider identifierTokenProvider; + + @ParameterizedTest(name = "{0} is identifier: {2}") + @MethodSource + void isIdentifierTokenTest(String ignoredTokenName, int tokenId, boolean expected) { + assertThat(identifierTokenProvider.isIdentifierToken(tokenId), equalTo(expected)); + } + + static Stream isIdentifierTokenTest() { + return Stream.of( + Arguments.of("RULE_ID", InternalProblemParser.RULE_ID, true), + Arguments.of("contained", InternalProblemParser.Contained, true), + Arguments.of("contains", InternalProblemParser.Contains, true), + Arguments.of("(", InternalProblemParser.LeftParenthesis, false) + ); + } +} diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/ProblemTokenSourceTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/ProblemTokenSourceTest.java new file mode 100644 index 00000000..cb42d5d0 --- /dev/null +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/ProblemTokenSourceTest.java @@ -0,0 +1,134 @@ +package tools.refinery.language.tests.parser.antlr; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.name.Named; +import org.antlr.runtime.ANTLRStringStream; +import org.antlr.runtime.Token; +import org.eclipse.xtext.parser.antlr.Lexer; +import org.eclipse.xtext.parser.antlr.LexerBindings; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.hamcrest.Matcher; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import tools.refinery.language.parser.antlr.IdentifierTokenProvider; +import tools.refinery.language.parser.antlr.ProblemTokenSource; +import tools.refinery.language.parser.antlr.internal.InternalProblemParser; +import tools.refinery.language.tests.ProblemInjectorProvider; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +@ExtendWith(InjectionExtension.class) +@InjectWith(ProblemInjectorProvider.class) +class ProblemTokenSourceTest { + @Inject + @Named(LexerBindings.RUNTIME) + private Provider lexerProvider; + + @Inject + private IdentifierTokenProvider identifierTokenProvider; + + @ParameterizedTest + @ValueSource(strings = { + "a+b", + "a+(b)", + "a+(b(x, y), x)", + "a + (b)", + "a+(b::x)", + "c+(a+(b)", + "a+(1, b)", + // These are never valid expressions, so we do try to peek at the inner plus sign + // to limit recursion depth in the token source: + "c+(equals+(a, b))", + "equals+(equals+(a, b), c)", + }) + void plusSignInTokenStreamTest(String text) { + var tokenList = createTokenList(text); + assertThat(tokenList, hasTokenOfType(InternalProblemParser.PlusSign)); + assertThat(tokenList, not(hasTokenOfType(InternalProblemParser.RULE_TRANSITIVE_CLOSURE))); + } + + @ParameterizedTest + @ValueSource(strings = { + "equals+(a, b)", + "equals + (a, b)", + "equals+(a::x, b)" + }) + void transitiveClosureInTokenStreamTest(String text) { + var tokenList = createTokenList(text); + assertThat(tokenList, not(hasTokenOfType(InternalProblemParser.PlusSign))); + assertThat(tokenList, hasTokenOfType(InternalProblemParser.RULE_TRANSITIVE_CLOSURE)); + } + + @ParameterizedTest + @MethodSource + void plusAndTransitiveClosureInSameTokenStreamTest(String text, boolean recursive) { + var tokenSource = createTokenSource(text); + tokenSource.setRecursive(recursive); + Token token; + int i = 0; + int plusIndex = -1; + int transitiveClosureIndex = -1; + do { + token = tokenSource.nextToken(); + switch (token.getType()) { + case InternalProblemParser.PlusSign -> { + assertThat("multiple plus signs", plusIndex, equalTo(-1)); + plusIndex = i; + } + case InternalProblemParser.RULE_TRANSITIVE_CLOSURE -> { + assertThat("multiple transitive closures", transitiveClosureIndex, equalTo(-1)); + transitiveClosureIndex = i; + } + } + i++; + } while (token.getType() != InternalProblemParser.EOF); + assertThat("no plus sign", plusIndex, not(equalTo(-1))); + assertThat("no transitive closure", transitiveClosureIndex, not(equalTo(-1))); + assertThat("transitive closure before plus", transitiveClosureIndex, greaterThan(plusIndex)); + } + + static Stream plusAndTransitiveClosureInSameTokenStreamTest() { + return Stream.of( + Arguments.of("c+(d), equals+(a, b)", false), + Arguments.of("foo+(bar baz+(a, b))", false), + // Here we can peek at the inner plus sign without recursion: + Arguments.of("c+(1, equals+(a, b))", false), + // But these cases need recursion: + Arguments.of("c+(equals+(a, b))", true), + Arguments.of("equals+(equals+(a, b), c)", true) + ); + } + + private ProblemTokenSource createTokenSource(String text) { + var lexer = lexerProvider.get(); + lexer.setCharStream(new ANTLRStringStream(text)); + var tokenSource = new ProblemTokenSource(lexer); + tokenSource.setIdentifierTokenProvider(identifierTokenProvider); + return tokenSource; + } + + private List createTokenList(String text) { + var tokenSource = createTokenSource(text); + var tokens = new ArrayList(); + Token token; + do { + token = tokenSource.nextToken(); + tokens.add(token); + } while (token.getType() != InternalProblemParser.EOF); + return tokens; + } + + private Matcher> hasTokenOfType(int tokenId) { + return hasItem(hasProperty("type", equalTo(tokenId))); + } +} diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/TransitiveClosureParserTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/TransitiveClosureParserTest.java new file mode 100644 index 00000000..96d12edf --- /dev/null +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/parser/antlr/TransitiveClosureParserTest.java @@ -0,0 +1,49 @@ +package tools.refinery.language.tests.parser.antlr; + +import com.google.inject.Inject; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import tools.refinery.language.model.problem.ArithmeticBinaryExpr; +import tools.refinery.language.model.problem.Atom; +import tools.refinery.language.model.problem.BinaryOp; +import tools.refinery.language.model.problem.ComparisonExpr; +import tools.refinery.language.model.tests.utils.ProblemParseHelper; +import tools.refinery.language.tests.ProblemInjectorProvider; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +@ExtendWith(InjectionExtension.class) +@InjectWith(ProblemInjectorProvider.class) +class TransitiveClosureParserTest { + @Inject + private ProblemParseHelper parseHelper; + + @Test + void binaryAddOperatorTest() { + var problem = parseHelper.parse(""" + pred foo(int a, int b) <-> a + (b) > 10. + """); + assertThat(problem.errors(), empty()); + var literal = problem.pred("foo").conj(0).lit(0).get(); + assertThat(literal, instanceOf(ComparisonExpr.class)); + var left = ((ComparisonExpr) literal).getLeft(); + assertThat(left, instanceOf(ArithmeticBinaryExpr.class)); + var binary = (ArithmeticBinaryExpr) left; + assertThat(binary.getOp(), equalTo(BinaryOp.ADD)); + } + + @Test + void transitiveClosureTest() { + var problem = parseHelper.parse(""" + pred foo(a, b) <-> equals+(a, b). + """); + assertThat(problem.errors(), empty()); + var literal = problem.pred("foo").conj(0).lit(0).get(); + assertThat(literal, instanceOf(Atom.class)); + var atom = (Atom) literal; + assertThat(atom.isTransitiveClosure(), equalTo(true)); + } +} diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java index 5c905ede..9049b8ec 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/scoping/NodeScopingTest.java @@ -1,13 +1,6 @@ package tools.refinery.language.tests.scoping; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasItems; -import static org.hamcrest.Matchers.not; - -import java.util.stream.Stream; - +import com.google.inject.Inject; import org.eclipse.xtext.testing.InjectWith; import org.eclipse.xtext.testing.extensions.InjectionExtension; import org.junit.jupiter.api.Test; @@ -16,13 +9,15 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; - -import com.google.inject.Inject; - import tools.refinery.language.model.tests.utils.ProblemParseHelper; import tools.refinery.language.model.tests.utils.WrappedProblem; import tools.refinery.language.tests.ProblemInjectorProvider; +import java.util.stream.Stream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + @ExtendWith(InjectionExtension.class) @InjectWith(ProblemInjectorProvider.class) class NodeScopingTest { @@ -85,7 +80,7 @@ class NodeScopingTest { @MethodSource("individualNodeReferenceSource") void individualNodeInAssertionTest(String qualifiedNamePrefix, boolean namedProblem) { var problem = parse(""" - indiv a, b. + individual a, b. pred predicate(node x, node y) <-> node(x). predicate({PARAM}a, {PARAM}a). ?predicate({PARAM}a, {PARAM}b). @@ -102,7 +97,7 @@ class NodeScopingTest { @MethodSource("individualNodeReferenceSource") void individualNodeInNodeValueAssertionTest(String qualifiedNamePrefix, boolean namedProblem) { var problem = parse(""" - indiv a. + individual a. {PARAM}a: 16. """, qualifiedNamePrefix, namedProblem); assertThat(problem.errors(), empty()); @@ -114,7 +109,7 @@ class NodeScopingTest { @MethodSource("individualNodeReferenceSource") void individualNodeInPredicateTest(String qualifiedNamePrefix, boolean namedProblem) { var problem = parse(""" - indiv b. + individual b. pred predicate(node a) <-> node({PARAM}b). """); assertThat(problem.errors(), empty()); diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java index ea858e92..150e47a4 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/serializer/ProblemSerializerTest.java @@ -1,13 +1,6 @@ package tools.refinery.language.tests.serializer; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Map; -import java.util.stream.Stream; - +import com.google.inject.Inject; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; @@ -19,20 +12,18 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; - -import com.google.inject.Inject; - -import tools.refinery.language.model.problem.Atom; -import tools.refinery.language.model.problem.LogicValue; -import tools.refinery.language.model.problem.Node; -import tools.refinery.language.model.problem.PredicateDefinition; -import tools.refinery.language.model.problem.Problem; -import tools.refinery.language.model.problem.ProblemFactory; -import tools.refinery.language.model.problem.Relation; -import tools.refinery.language.model.problem.VariableOrNode; +import tools.refinery.language.model.problem.*; import tools.refinery.language.model.tests.utils.WrappedProblem; import tools.refinery.language.tests.ProblemInjectorProvider; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Map; +import java.util.stream.Stream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + @ExtendWith(InjectionExtension.class) @InjectWith(ProblemInjectorProvider.class) class ProblemSerializerTest { @@ -68,7 +59,7 @@ class ProblemSerializerTest { assertSerializedResult(""" pred foo(node p). - indiv a. + individual a. """ + serializedAssertion + "\n"); } @@ -166,10 +157,10 @@ class ProblemSerializerTest { private Atom createAtom(Relation relation, VariableOrNode variable1, VariableOrNode variable2) { var atom = ProblemFactory.eINSTANCE.createAtom(); atom.setRelation(relation); - var arg1 = ProblemFactory.eINSTANCE.createVariableOrNodeArgument(); + var arg1 = ProblemFactory.eINSTANCE.createVariableOrNodeExpr(); arg1.setVariableOrNode(variable1); atom.getArguments().add(arg1); - var arg2 = ProblemFactory.eINSTANCE.createVariableOrNodeArgument(); + var arg2 = ProblemFactory.eINSTANCE.createVariableOrNodeExpr(); arg2.setVariableOrNode(variable2); atom.getArguments().add(arg2); return atom; @@ -188,10 +179,10 @@ class ProblemSerializerTest { var atom = ProblemFactory.eINSTANCE.createAtom(); var equals = nodeType.reference("equals"); atom.setRelation(equals); - var arg1 = ProblemFactory.eINSTANCE.createVariableOrNodeArgument(); + var arg1 = ProblemFactory.eINSTANCE.createVariableOrNodeExpr(); arg1.setVariableOrNode(parameter); atom.getArguments().add(arg1); - var arg2 = ProblemFactory.eINSTANCE.createVariableOrNodeArgument(); + var arg2 = ProblemFactory.eINSTANCE.createVariableOrNodeExpr(); var variable = ProblemFactory.eINSTANCE.createImplicitVariable(); variable.setName("_q"); arg2.setSingletonVariable(variable); diff --git a/subprojects/language/src/test/java/tools/refinery/language/tests/utils/SymbolCollectorTest.java b/subprojects/language/src/test/java/tools/refinery/language/tests/utils/SymbolCollectorTest.java index a05f3335..98c16352 100644 --- a/subprojects/language/src/test/java/tools/refinery/language/tests/utils/SymbolCollectorTest.java +++ b/subprojects/language/src/test/java/tools/refinery/language/tests/utils/SymbolCollectorTest.java @@ -44,7 +44,7 @@ class SymbolCollectorTest { @Test void individualNodeTest() { var problem = parseHelper.parse(""" - indiv a. + individual a. """); var collectedSymbols = desugarer.collectSymbols(problem.get()); var node = problem.individualNode("a"); @@ -161,7 +161,7 @@ class SymbolCollectorTest { static Stream predicateTest() { return Stream.of(Arguments.of("pred", ContainmentRole.NONE), Arguments.of("error", ContainmentRole.NONE), - Arguments.of("contained", ContainmentRole.CONTAINED), Arguments.of("containment", + Arguments.of("contained pred", ContainmentRole.CONTAINED), Arguments.of("containment pred", ContainmentRole.CONTAINMENT)); } diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedArgument.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedArgument.java index ad57a438..9e4c59f5 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedArgument.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedArgument.java @@ -2,13 +2,13 @@ package tools.refinery.language.model.tests.utils; import tools.refinery.language.model.problem.*; -public record WrappedArgument(Argument argument) { - public Argument get() { - return argument; +public record WrappedArgument(Expr expr) { + public Expr get() { + return expr; } public VariableOrNode variableOrNode() { - return ((VariableOrNodeArgument) argument).getVariableOrNode(); + return ((VariableOrNodeExpr) expr).getVariableOrNode(); } public Variable variable() { diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedLiteral.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedLiteral.java index 2282198d..4aa71b99 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedLiteral.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedLiteral.java @@ -1,20 +1,20 @@ package tools.refinery.language.model.tests.utils; import tools.refinery.language.model.problem.Atom; -import tools.refinery.language.model.problem.Literal; -import tools.refinery.language.model.problem.NegativeLiteral; +import tools.refinery.language.model.problem.Expr; +import tools.refinery.language.model.problem.NegationExpr; -public record WrappedLiteral(Literal literal) { - public Literal get() { - return literal; +public record WrappedLiteral(Expr expr) { + public Expr get() { + return expr; } - + public WrappedAtom atom() { - return new WrappedAtom((Atom) literal); + return new WrappedAtom((Atom) expr); } public WrappedAtom negated() { - return new WrappedAtom(((NegativeLiteral) literal).getAtom()); + return new WrappedAtom((Atom) ((NegationExpr) expr).getBody()); } public WrappedArgument arg(int i) { diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedParametricDefinition.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedParametricDefinition.java index b390051a..c2f18a60 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedParametricDefinition.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedParametricDefinition.java @@ -4,13 +4,9 @@ import tools.refinery.language.model.problem.Parameter; import tools.refinery.language.model.problem.ParametricDefinition; public interface WrappedParametricDefinition { - public ParametricDefinition get(); - - public default Parameter param(int i) { - return get().getParameters().get(i); - } + ParametricDefinition get(); - public default WrappedConjunction conj(int i) { - return new WrappedConjunction(get().getBodies().get(i)); + default Parameter param(int i) { + return get().getParameters().get(i); } } diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedPredicateDefinition.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedPredicateDefinition.java index 6b07366d..7b95ecc1 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedPredicateDefinition.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedPredicateDefinition.java @@ -8,4 +8,8 @@ public record WrappedPredicateDefinition(PredicateDefinition predicateDefinition public PredicateDefinition get() { return predicateDefinition; } + + public WrappedConjunction conj(int i) { + return new WrappedConjunction(predicateDefinition.getBodies().get(i)); + } } diff --git a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedRuleDefinition.java b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedRuleDefinition.java index b9f299f6..a4cf2eaf 100644 --- a/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedRuleDefinition.java +++ b/subprojects/language/src/testFixtures/java/tools/refinery/language/model/tests/utils/WrappedRuleDefinition.java @@ -7,7 +7,11 @@ public record WrappedRuleDefinition(RuleDefinition ruleDefinition) implements Wr public RuleDefinition get() { return ruleDefinition; } - + + public WrappedConjunction conj(int i) { + return new WrappedConjunction(ruleDefinition.getPreconditions().get(i)); + } + public WrappedConsequent consequent(int i) { return new WrappedConsequent(ruleDefinition.getConsequents().get(i)); } -- cgit v1.2.3-54-g00ecf