Skip to content

Commit 7710f1e

Browse files
committed
Use iterative traversal for deeply nested constraints
1 parent 91c5fc0 commit 7710f1e

3 files changed

Lines changed: 142 additions & 20 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package de.vill.main;
2+
3+
import java.util.ArrayDeque;
4+
import java.util.Deque;
5+
6+
import org.antlr.v4.runtime.tree.ErrorNode;
7+
import org.antlr.v4.runtime.tree.ParseTree;
8+
import org.antlr.v4.runtime.tree.ParseTreeListener;
9+
import org.antlr.v4.runtime.tree.ParseTreeWalker;
10+
import org.antlr.v4.runtime.tree.RuleNode;
11+
import org.antlr.v4.runtime.tree.TerminalNode;
12+
13+
/**
14+
* Iterative variant of ANTLR's ParseTreeWalker.
15+
*
16+
* ANTLR's default ParseTreeWalker recursively walks the parse tree.
17+
* Deeply nested constraints such as F0 | F1 | ... | F6999 can therefore
18+
* overflow the Java call stack before the UVL model is constructed.
19+
*/
20+
final class IterativeParseTreeWalker extends ParseTreeWalker {
21+
22+
@Override
23+
public void walk(ParseTreeListener listener, ParseTree tree) {
24+
final Deque<Frame> stack = new ArrayDeque<>();
25+
stack.push(new Frame(tree));
26+
27+
while (!stack.isEmpty()) {
28+
final Frame frame = stack.peek();
29+
final ParseTree current = frame.tree;
30+
31+
if (current instanceof ErrorNode) {
32+
listener.visitErrorNode((ErrorNode) current);
33+
stack.pop();
34+
continue;
35+
}
36+
37+
if (current instanceof TerminalNode) {
38+
listener.visitTerminal((TerminalNode) current);
39+
stack.pop();
40+
continue;
41+
}
42+
43+
final RuleNode ruleNode = (RuleNode) current;
44+
45+
if (!frame.entered) {
46+
enterRule(listener, ruleNode);
47+
frame.entered = true;
48+
}
49+
50+
if (frame.nextChildIndex < current.getChildCount()) {
51+
stack.push(new Frame(current.getChild(frame.nextChildIndex)));
52+
frame.nextChildIndex++;
53+
} else {
54+
exitRule(listener, ruleNode);
55+
stack.pop();
56+
}
57+
}
58+
}
59+
60+
private static final class Frame {
61+
private final ParseTree tree;
62+
private boolean entered;
63+
private int nextChildIndex;
64+
65+
private Frame(ParseTree tree) {
66+
this.tree = tree;
67+
}
68+
}
69+
}

src/main/java/de/vill/main/UVLModelFactory.java

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import org.antlr.v4.runtime.ConsoleErrorListener;
3131
import org.antlr.v4.runtime.RecognitionException;
3232
import org.antlr.v4.runtime.Recognizer;
33-
import org.antlr.v4.runtime.tree.ParseTreeWalker;
3433

3534
import java.io.IOException;
3635
import java.nio.file.FileSystems;
@@ -125,7 +124,7 @@ public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int
125124
});
126125

127126
UVLListener uvlListener = new UVLListener();
128-
ParseTreeWalker walker = new ParseTreeWalker();
127+
IterativeParseTreeWalker walker = new IterativeParseTreeWalker();
129128
walker.walk(uvlListener, UVLJavaParser.constraintLine());
130129

131130
return uvlListener.getConstraint();
@@ -302,7 +301,7 @@ public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int
302301

303302

304303
UVLListener uvlListener = new UVLListener();
305-
ParseTreeWalker walker = new ParseTreeWalker();
304+
IterativeParseTreeWalker walker = new IterativeParseTreeWalker();
306305
walker.walk(uvlListener, UVLJavaParser.featureModel());
307306
FeatureModel featureModel = null;
308307

@@ -508,28 +507,38 @@ private void validateTypeLevelConstraints(final FeatureModel featureModel) {
508507
}
509508

510509
private boolean validateTypeLevelConstraint(final Constraint constraint) {
511-
boolean result = true;
512-
if (constraint instanceof ExpressionConstraint) {
513-
String leftReturnType = ((ExpressionConstraint) constraint).getLeft().getReturnType();
514-
String rightReturnType = ((ExpressionConstraint) constraint).getRight().getReturnType();
510+
final Deque<Constraint> stack = new ArrayDeque<>();
511+
stack.push(constraint);
515512

516-
if (!(leftReturnType.equalsIgnoreCase(Constants.TRUE) || rightReturnType.equalsIgnoreCase(Constants.TRUE))) {
517-
// if not attribute constraint
518-
result = result && ((ExpressionConstraint) constraint).getLeft().getReturnType().equalsIgnoreCase(((ExpressionConstraint) constraint).getRight().getReturnType());
519-
}
520-
if (!result) {
521-
return false;
522-
}
523-
for (final Expression expr: ((ExpressionConstraint) constraint).getExpressionSubParts()) {
524-
result = result && validateTypeLevelExpression(expr);
513+
while (!stack.isEmpty()) {
514+
final Constraint current = stack.pop();
515+
516+
if (current instanceof ExpressionConstraint) {
517+
final ExpressionConstraint expressionConstraint = (ExpressionConstraint) current;
518+
519+
final String leftReturnType = expressionConstraint.getLeft().getReturnType();
520+
final String rightReturnType = expressionConstraint.getRight().getReturnType();
521+
522+
if (!(leftReturnType.equalsIgnoreCase(Constants.TRUE) || rightReturnType.equalsIgnoreCase(Constants.TRUE))) {
523+
if (!leftReturnType.equalsIgnoreCase(rightReturnType)) {
524+
return false;
525+
}
526+
}
527+
528+
for (final Expression expr : expressionConstraint.getExpressionSubParts()) {
529+
if (!validateTypeLevelExpression(expr)) {
530+
return false;
531+
}
532+
}
525533
}
526-
}
527534

528-
for (final Constraint subCons: constraint.getConstraintSubParts()) {
529-
result = result && validateTypeLevelConstraint(subCons);
535+
final List<Constraint> subConstraints = current.getConstraintSubParts();
536+
for (int i = subConstraints.size() - 1; i >= 0; i--) {
537+
stack.push(subConstraints.get(i));
538+
}
530539
}
531540

532-
return result;
541+
return true;
533542
}
534543

535544
private boolean validateTypeLevelExpression(final Expression expression) {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package de.vill.parsing;
2+
3+
import de.vill.main.UVLModelFactory;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
7+
8+
class LongConstraintParsingTest {
9+
10+
@Test
11+
void parsesLongOrConstraint() {
12+
final int numberOfLiterals = 7000;
13+
final String model = createModel(numberOfLiterals);
14+
15+
final UVLModelFactory factory = new UVLModelFactory();
16+
17+
assertDoesNotThrow(() -> factory.parse(model));
18+
}
19+
20+
private String createModel(int numberOfLiterals) {
21+
final StringBuilder builder = new StringBuilder();
22+
23+
builder.append("features\n");
24+
builder.append(" Root\n");
25+
builder.append(" optional\n");
26+
27+
for (int i = 0; i < numberOfLiterals; i++) {
28+
builder.append(" F").append(i).append("\n");
29+
}
30+
31+
builder.append("constraints\n");
32+
builder.append(" ");
33+
34+
for (int i = 0; i < numberOfLiterals; i++) {
35+
if (i > 0) {
36+
builder.append(" | ");
37+
}
38+
builder.append("F").append(i);
39+
}
40+
41+
builder.append("\n");
42+
return builder.toString();
43+
}
44+
}

0 commit comments

Comments
 (0)