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();
+ }
+}