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/UVLModelFactory.java b/src/main/java/de/vill/main/UVLModelFactory.java index c6dd819..09ca424 100644 --- a/src/main/java/de/vill/main/UVLModelFactory.java +++ b/src/main/java/de/vill/main/UVLModelFactory.java @@ -30,7 +30,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; @@ -125,7 +124,7 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, int }); UVLListener uvlListener = new UVLListener(); - ParseTreeWalker walker = new ParseTreeWalker(); + IterativeParseTreeWalker walker = new IterativeParseTreeWalker(); walker.walk(uvlListener, UVLJavaParser.constraintLine()); return uvlListener.getConstraint(); @@ -302,7 +301,7 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, int UVLListener uvlListener = new UVLListener(); - ParseTreeWalker walker = new ParseTreeWalker(); + IterativeParseTreeWalker walker = new IterativeParseTreeWalker(); walker.walk(uvlListener, UVLJavaParser.featureModel()); FeatureModel featureModel = null; @@ -508,28 +507,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/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(); + } +}