Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/main/java/de/vill/main/IterativeParseTreeWalker.java
Original file line number Diff line number Diff line change
@@ -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<Frame> 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;
}
}
}
49 changes: 29 additions & 20 deletions src/main/java/de/vill/main/UVLModelFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<Constraint> 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<Constraint> 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) {
Expand Down
44 changes: 44 additions & 0 deletions src/test/java/de/vill/parsing/LongConstraintParsingTest.java
Original file line number Diff line number Diff line change
@@ -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();
}
}