diff --git a/src/main/java/de/vill/main/IterativeParseTreeWalker.java b/src/main/java/de/vill/main/IterativeParseTreeWalker.java new file mode 100644 index 0000000..b06e06a --- /dev/null +++ b/src/main/java/de/vill/main/IterativeParseTreeWalker.java @@ -0,0 +1,69 @@ +package de.vill.main; + +import java.util.ArrayDeque; +import java.util.Deque; + +import org.antlr.v4.runtime.tree.ErrorNode; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.ParseTreeListener; +import org.antlr.v4.runtime.tree.ParseTreeWalker; +import org.antlr.v4.runtime.tree.RuleNode; +import org.antlr.v4.runtime.tree.TerminalNode; + +/** + * Iterative variant of ANTLR's ParseTreeWalker. + * + * ANTLR's default ParseTreeWalker recursively walks the parse tree. + * Deeply nested constraints such as F0 | F1 | ... | F6999 can therefore + * overflow the Java call stack before the UVL model is constructed. + */ +final class IterativeParseTreeWalker extends ParseTreeWalker { + + @Override + public void walk(ParseTreeListener listener, ParseTree tree) { + final Deque stack = new ArrayDeque<>(); + stack.push(new Frame(tree)); + + while (!stack.isEmpty()) { + final Frame frame = stack.peek(); + final ParseTree current = frame.tree; + + if (current instanceof ErrorNode) { + listener.visitErrorNode((ErrorNode) current); + stack.pop(); + continue; + } + + if (current instanceof TerminalNode) { + listener.visitTerminal((TerminalNode) current); + stack.pop(); + continue; + } + + final RuleNode ruleNode = (RuleNode) current; + + if (!frame.entered) { + enterRule(listener, ruleNode); + frame.entered = true; + } + + if (frame.nextChildIndex < current.getChildCount()) { + stack.push(new Frame(current.getChild(frame.nextChildIndex))); + frame.nextChildIndex++; + } else { + exitRule(listener, ruleNode); + stack.pop(); + } + } + } + + private static final class Frame { + private final ParseTree tree; + private boolean entered; + private int nextChildIndex; + + private Frame(ParseTree tree) { + this.tree = tree; + } + } +} diff --git a/src/main/java/de/vill/main/UVLListener.java b/src/main/java/de/vill/main/UVLListener.java index 9922c8d..6fb5e5c 100644 --- a/src/main/java/de/vill/main/UVLListener.java +++ b/src/main/java/de/vill/main/UVLListener.java @@ -352,7 +352,13 @@ public void exitNotConstraint(UVLJavaParser.NotConstraintContext ctx) { public void exitAndConstraint(UVLJavaParser.AndConstraintContext ctx) { Constraint rightConstraint = constraintStack.pop(); Constraint leftConstraint = constraintStack.pop(); - Constraint constraint = new AndConstraint(leftConstraint, rightConstraint); + Constraint constraint; + if (leftConstraint instanceof AndConstraint) { + ((AndConstraint) leftConstraint).addChild(rightConstraint); + constraint = leftConstraint; + } else { + constraint = new AndConstraint(leftConstraint, rightConstraint); + } constraintStack.push(constraint); Token t = ctx.getStart(); int line = t.getLine(); @@ -363,7 +369,13 @@ public void exitAndConstraint(UVLJavaParser.AndConstraintContext ctx) { public void exitOrConstraint(UVLJavaParser.OrConstraintContext ctx) { Constraint rightConstraint = constraintStack.pop(); Constraint leftConstraint = constraintStack.pop(); - Constraint constraint = new OrConstraint(leftConstraint, rightConstraint); + Constraint constraint; + if (leftConstraint instanceof OrConstraint) { + ((OrConstraint) leftConstraint).addChild(rightConstraint); + constraint = leftConstraint; + } else { + constraint = new OrConstraint(leftConstraint, rightConstraint); + } constraintStack.push(constraint); Token t = ctx.getStart(); int line = t.getLine(); diff --git a/src/main/java/de/vill/main/UVLModelFactory.java b/src/main/java/de/vill/main/UVLModelFactory.java index 483e512..924c1a0 100644 --- a/src/main/java/de/vill/main/UVLModelFactory.java +++ b/src/main/java/de/vill/main/UVLModelFactory.java @@ -33,7 +33,6 @@ import org.antlr.v4.runtime.ConsoleErrorListener; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; -import org.antlr.v4.runtime.tree.ParseTreeWalker; import java.io.IOException; import java.nio.file.FileSystems; @@ -134,7 +133,8 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, int }); UVLListener uvlListener = createUVLListener(); - ParseTreeWalker walker = new ParseTreeWalker(); + IterativeParseTreeWalker walker = new IterativeParseTreeWalker(); + walker.walk(uvlListener, UVLJavaParser.constraintLine()); return uvlListener.getConstraint(); @@ -314,8 +314,10 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, int }); + UVLListener uvlListener = createUVLListener(); - ParseTreeWalker walker = new ParseTreeWalker(); + IterativeParseTreeWalker walker = new IterativeParseTreeWalker(); + walker.walk(uvlListener, UVLJavaParser.featureModel()); FeatureModel featureModel = null; @@ -388,42 +390,55 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, int } private void resolveImportPlaceholders(Constraint constraint, FeatureModel featureModel) { - if (constraint instanceof AndConstraint || constraint instanceof OrConstraint || constraint instanceof NotConstraint || constraint instanceof ImplicationConstraint || constraint instanceof ParenthesisConstraint || constraint instanceof EquivalenceConstraint) { - for (Constraint subPart : constraint.getConstraintSubParts()) { - resolveImportPlaceholders(subPart, featureModel); + final Deque stack = new ArrayDeque<>(); + stack.push(constraint); + + while (!stack.isEmpty()) { + final Constraint current = stack.pop(); + + if (current instanceof ExpressionConstraint) { + ExpressionConstraint expressionConstraint = (ExpressionConstraint) current; + resolveImportPlaceholders(expressionConstraint.getLeft(), featureModel); + resolveImportPlaceholders(expressionConstraint.getRight(), featureModel); + } else if (current instanceof LiteralConstraint) { + LiteralConstraint literalConstraint = (LiteralConstraint) current; + if (literalConstraint.getReference() instanceof ImportedVariablePlaceholder) { + ImportedVariablePlaceholder placeholder = (ImportedVariablePlaceholder) literalConstraint.getReference(); + literalConstraint.setReference(resolvePlaceholder(placeholder, featureModel)); + } } - } else if (constraint instanceof ExpressionConstraint) { - ExpressionConstraint expressionConstraint = (ExpressionConstraint) constraint; - resolveImportPlaceholders(expressionConstraint.getLeft(), featureModel); - resolveImportPlaceholders(expressionConstraint.getRight(), featureModel); - } else if (constraint instanceof LiteralConstraint) { - LiteralConstraint literalConstraint = (LiteralConstraint) constraint; - if (literalConstraint.getReference() instanceof ImportedVariablePlaceholder) { - ImportedVariablePlaceholder placeholder = (ImportedVariablePlaceholder) literalConstraint.getReference(); - literalConstraint.setReference(resolvePlaceholder(placeholder, featureModel)); + + final List subConstraints = current.getConstraintSubParts(); + for (int i = subConstraints.size() - 1; i >= 0; i--) { + stack.push(subConstraints.get(i)); } } } private void resolveImportPlaceholders(Expression expression, FeatureModel featureModel) { - if (expression instanceof BinaryExpression) { - BinaryExpression binaryExpression = (BinaryExpression) expression; - resolveImportPlaceholders(binaryExpression.getLeft(), featureModel); - resolveImportPlaceholders(binaryExpression.getRight(), featureModel); - } else if (expression instanceof ParenthesisExpression) { - ParenthesisExpression parenthesisExpression = (ParenthesisExpression) expression; - resolveImportPlaceholders(parenthesisExpression.getContent(), featureModel); - } else if (expression instanceof LengthAggregateFunctionExpression) { - LengthAggregateFunctionExpression lengthAggregateFunctionExpression = (LengthAggregateFunctionExpression) expression; - if (lengthAggregateFunctionExpression.getReference() instanceof ImportedVariablePlaceholder) { - ImportedVariablePlaceholder placeholder = (ImportedVariablePlaceholder) lengthAggregateFunctionExpression.getReference(); - lengthAggregateFunctionExpression.setReference(resolvePlaceholder(placeholder, featureModel)); + final Deque stack = new ArrayDeque<>(); + stack.push(expression); + + while (!stack.isEmpty()) { + final Expression current = stack.pop(); + + if (current instanceof LengthAggregateFunctionExpression) { + LengthAggregateFunctionExpression lengthAggregateFunctionExpression = (LengthAggregateFunctionExpression) current; + if (lengthAggregateFunctionExpression.getReference() instanceof ImportedVariablePlaceholder) { + ImportedVariablePlaceholder placeholder = (ImportedVariablePlaceholder) lengthAggregateFunctionExpression.getReference(); + lengthAggregateFunctionExpression.setReference(resolvePlaceholder(placeholder, featureModel)); + } + } else if (current instanceof LiteralExpression) { + LiteralExpression literalExpression = (LiteralExpression) current; + if (literalExpression.getContent() instanceof ImportedVariablePlaceholder) { + ImportedVariablePlaceholder placeholder = (ImportedVariablePlaceholder) literalExpression.getContent(); + literalExpression.setContent(resolvePlaceholder(placeholder, featureModel)); + } } - } else if (expression instanceof LiteralExpression) { - LiteralExpression literalExpression = (LiteralExpression) expression; - if (literalExpression.getContent() instanceof ImportedVariablePlaceholder) { - ImportedVariablePlaceholder placeholder = (ImportedVariablePlaceholder) literalExpression.getContent(); - literalExpression.setContent(resolvePlaceholder(placeholder, featureModel)); + + final List subExpressions = current.getExpressionSubParts(); + for (int i = subExpressions.size() - 1; i >= 0; i--) { + stack.push(subExpressions.get(i)); } } } @@ -521,28 +536,38 @@ private void validateTypeLevelConstraints(final FeatureModel featureModel) { } private boolean validateTypeLevelConstraint(final Constraint constraint) { - boolean result = true; - if (constraint instanceof ExpressionConstraint) { - String leftReturnType = ((ExpressionConstraint) constraint).getLeft().getReturnType(); - String rightReturnType = ((ExpressionConstraint) constraint).getRight().getReturnType(); + final Deque stack = new ArrayDeque<>(); + stack.push(constraint); - if (!(leftReturnType.equalsIgnoreCase(Constants.TRUE) || rightReturnType.equalsIgnoreCase(Constants.TRUE))) { - // if not attribute constraint - result = result && ((ExpressionConstraint) constraint).getLeft().getReturnType().equalsIgnoreCase(((ExpressionConstraint) constraint).getRight().getReturnType()); - } - if (!result) { - return false; - } - for (final Expression expr: ((ExpressionConstraint) constraint).getExpressionSubParts()) { - result = result && validateTypeLevelExpression(expr); + while (!stack.isEmpty()) { + final Constraint current = stack.pop(); + + if (current instanceof ExpressionConstraint) { + final ExpressionConstraint expressionConstraint = (ExpressionConstraint) current; + + final String leftReturnType = expressionConstraint.getLeft().getReturnType(); + final String rightReturnType = expressionConstraint.getRight().getReturnType(); + + if (!(leftReturnType.equalsIgnoreCase(Constants.TRUE) || rightReturnType.equalsIgnoreCase(Constants.TRUE))) { + if (!leftReturnType.equalsIgnoreCase(rightReturnType)) { + return false; + } + } + + for (final Expression expr : expressionConstraint.getExpressionSubParts()) { + if (!validateTypeLevelExpression(expr)) { + return false; + } + } } - } - for (final Constraint subCons: constraint.getConstraintSubParts()) { - result = result && validateTypeLevelConstraint(subCons); + final List subConstraints = current.getConstraintSubParts(); + for (int i = subConstraints.size() - 1; i >= 0; i--) { + stack.push(subConstraints.get(i)); + } } - return result; + return true; } private boolean validateTypeLevelExpression(final Expression expression) { diff --git a/src/main/java/de/vill/model/constraint/MultiOrConstraint.java b/src/main/java/de/vill/model/constraint/MultiOrConstraint.java deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/java/de/vill/parsing/LongConstraintParsingTest.java b/src/test/java/de/vill/parsing/LongConstraintParsingTest.java new file mode 100644 index 0000000..cb47478 --- /dev/null +++ b/src/test/java/de/vill/parsing/LongConstraintParsingTest.java @@ -0,0 +1,44 @@ +package de.vill.parsing; + +import de.vill.main.UVLModelFactory; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class LongConstraintParsingTest { + + @Test + void parsesLongOrConstraint() { + final int numberOfLiterals = 7000; + final String model = createModel(numberOfLiterals); + + final UVLModelFactory factory = new UVLModelFactory(); + + assertDoesNotThrow(() -> factory.parse(model)); + } + + private String createModel(int numberOfLiterals) { + final StringBuilder builder = new StringBuilder(); + + builder.append("features\n"); + builder.append(" Root\n"); + builder.append(" optional\n"); + + for (int i = 0; i < numberOfLiterals; i++) { + builder.append(" F").append(i).append("\n"); + } + + builder.append("constraints\n"); + builder.append(" "); + + for (int i = 0; i < numberOfLiterals; i++) { + if (i > 0) { + builder.append(" | "); + } + builder.append("F").append(i); + } + + builder.append("\n"); + return builder.toString(); + } +}