mirror of
https://github.com/sigmasternchen/xtext-core
synced 2025-03-15 08:18:55 +00:00
refactoring of hoisting code
This commit is contained in:
parent
cb606d6937
commit
5fdb3424ac
25 changed files with 599 additions and 501 deletions
|
@ -17,9 +17,9 @@ import org.eclipse.xtext.XtextStandaloneSetup;
|
|||
import org.eclipse.xtext.resource.XtextResource;
|
||||
import org.eclipse.xtext.testing.GlobalRegistries;
|
||||
import org.eclipse.xtext.tests.AbstractXtextTests;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.HoistingGuard;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.HoistingProcessor;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.TokenAnalysisAbortedException;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards.HoistingGuard;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.pathAnalysis.TokenAnalysisAbortedException;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 itemis AG (http://www.itemis.eu) and others.
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
|
||||
/**
|
||||
* @author overflow - Initial contribution and API
|
||||
*/
|
||||
public class HoistingConfiguration {
|
||||
private final int tokenLimit = 10;
|
||||
|
||||
public int getTokenLimit() {
|
||||
return tokenLimit;
|
||||
}
|
||||
}
|
|
@ -11,43 +11,45 @@ package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.eclipse.emf.ecore.EStructuralFeature;
|
||||
import org.eclipse.xtend.lib.macro.declaration.MutableInterfaceDeclaration;
|
||||
import org.eclipse.xtext.AbstractElement;
|
||||
import org.eclipse.xtext.AbstractRule;
|
||||
import org.eclipse.xtext.AbstractSemanticPredicate;
|
||||
import org.eclipse.xtext.Action;
|
||||
import org.eclipse.xtext.Alternatives;
|
||||
import org.eclipse.xtext.Assignment;
|
||||
import org.eclipse.xtext.DisambiguatingSemanticPredicate;
|
||||
import org.eclipse.xtext.GatedSemanticPredicate;
|
||||
import org.eclipse.xtext.GrammarUtil;
|
||||
import org.eclipse.xtext.Group;
|
||||
import org.eclipse.xtext.JavaAction;
|
||||
import org.eclipse.xtext.Keyword;
|
||||
import org.eclipse.xtext.ParserRule;
|
||||
import org.eclipse.xtext.RuleCall;
|
||||
import org.eclipse.xtext.UnorderedGroup;
|
||||
import org.eclipse.xtext.XtextFactory;
|
||||
import org.eclipse.xtext.XtextPackage;
|
||||
import org.eclipse.xtext.util.Tuples;
|
||||
import org.eclipse.xtext.util.XtextSwitch;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.utils.MutablePrimitiveWrapper;
|
||||
import static org.eclipse.xtext.GrammarUtil.*;
|
||||
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards.AlternativeTokenSequenceGuard;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards.AlternativesGuard;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards.GroupGuard;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards.HoistingGuard;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards.MergedPathGuard;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards.PathGuard;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards.PredicateGuard;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards.SingleTokenGuard;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards.TokenGuard;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards.TokenSequenceGuard;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.pathAnalysis.TokenAnalysis;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.token.Token;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.utils.StreamUtils;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Streams;
|
||||
|
||||
/**
|
||||
|
@ -59,427 +61,14 @@ public class HoistingProcessor {
|
|||
|
||||
private Logger log = Logger.getLogger(this.getClass());
|
||||
|
||||
private static final int TOKEN_ANALYSIS_LIMIT = 10;
|
||||
private HoistingConfiguration config = new HoistingConfiguration();
|
||||
private TokenAnalysis analysis;
|
||||
|
||||
private boolean isParserRule(AbstractElement element) {
|
||||
return (element instanceof RuleCall) && (((RuleCall) element).getRule() instanceof ParserRule);
|
||||
}
|
||||
|
||||
private boolean cardinalityAllowsEmpty(AbstractElement element) {
|
||||
String cardinality = element.getCardinality();
|
||||
return cardinality != null && (cardinality.equals("?") || cardinality.equals("*"));
|
||||
}
|
||||
|
||||
private boolean cardinalityAllowsRepetition(AbstractElement element) {
|
||||
String cardinality = element.getCardinality();
|
||||
return cardinality != null && (cardinality.equals("+") || cardinality.equals("*"));
|
||||
}
|
||||
|
||||
private TokenAnalysisPaths getTokenForIndexesAlternatives(Alternatives path, TokenAnalysisPaths prefix, boolean needsLength) throws TokenAnalysisAbortedException {
|
||||
if (prefix.isDone()) {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
TokenAnalysisPaths result;
|
||||
if (cardinalityAllowsEmpty(path)) {
|
||||
result = prefix;
|
||||
if (needsLength) {
|
||||
// analysis is not done but there are no more mandatory tokens
|
||||
throw new TokenAnalysisAbortedException();
|
||||
}
|
||||
} else {
|
||||
result = TokenAnalysisPaths.empty(prefix);
|
||||
}
|
||||
|
||||
boolean loop = cardinalityAllowsRepetition(path);
|
||||
|
||||
do {
|
||||
boolean allDone = true;
|
||||
|
||||
for (AbstractElement element : path.getElements()) {
|
||||
TokenAnalysisPaths current = new TokenAnalysisPaths(prefix);
|
||||
current = getTokenForIndexes(element, current, needsLength); // will check for needsLength
|
||||
|
||||
if (!current.isDone()) {
|
||||
allDone = false;
|
||||
}
|
||||
|
||||
result = result.merge(current);
|
||||
}
|
||||
|
||||
if (allDone) {
|
||||
break;
|
||||
}
|
||||
|
||||
prefix = result;
|
||||
|
||||
// repeat until all further extensions of prefix are done
|
||||
} while(loop);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private TokenAnalysisPaths getTokenForIndexesGroup(Group path, TokenAnalysisPaths prefix, boolean needsLength) throws TokenAnalysisAbortedException {
|
||||
if (prefix.isDone()) {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
TokenAnalysisPaths result;
|
||||
TokenAnalysisPaths current = new TokenAnalysisPaths(prefix);
|
||||
|
||||
if (cardinalityAllowsEmpty(path)) {
|
||||
result = prefix;
|
||||
if (needsLength) {
|
||||
// analysis is not done but there are no more mandatory tokens
|
||||
throw new TokenAnalysisAbortedException();
|
||||
}
|
||||
} else {
|
||||
result = TokenAnalysisPaths.empty(prefix);
|
||||
}
|
||||
|
||||
boolean loop = cardinalityAllowsRepetition(path);
|
||||
|
||||
do {
|
||||
for (AbstractElement element : path.getElements()) {
|
||||
current = getTokenForIndexes(element, current, false);
|
||||
|
||||
if (current.isDone()) {
|
||||
// no need to look further
|
||||
|
||||
return result.merge(current);
|
||||
}
|
||||
}
|
||||
|
||||
if (needsLength && !current.isDone()) {
|
||||
// analysis is not done but there are no more mandatory tokens
|
||||
throw new TokenAnalysisAbortedException();
|
||||
}
|
||||
|
||||
result = result.merge(current);
|
||||
current = new TokenAnalysisPaths(result);
|
||||
} while(loop);
|
||||
|
||||
// if cardinality is trivial or ? return result
|
||||
return result;
|
||||
}
|
||||
|
||||
private TokenAnalysisPaths getTokenForIndexesDefault(AbstractElement path, TokenAnalysisPaths prefix, boolean needsLength) throws TokenAnalysisAbortedException {
|
||||
if (prefix.isDone()) {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
TokenAnalysisPaths result;
|
||||
|
||||
if (cardinalityAllowsEmpty(path)) {
|
||||
result = prefix;
|
||||
if (needsLength) {
|
||||
throw new TokenAnalysisAbortedException();
|
||||
}
|
||||
} else {
|
||||
result = TokenAnalysisPaths.empty(prefix);
|
||||
}
|
||||
|
||||
TokenAnalysisPaths current = new TokenAnalysisPaths(prefix);
|
||||
|
||||
boolean loop = cardinalityAllowsRepetition(path);
|
||||
do {
|
||||
if (Token.isToken(path)) {
|
||||
current.add(path);
|
||||
} else if (isParserRule(path)) {
|
||||
// path doesn't need length, because we're going to check that anyway in this function
|
||||
current = getTokenForIndexes(((RuleCall) path).getRule().getAlternatives(), current, false);
|
||||
} else if (path instanceof Assignment) {
|
||||
current = getTokenForIndexes(((Assignment) path).getTerminal(), current, false);
|
||||
} else {
|
||||
throw new IllegalArgumentException("unknown element: " + path.eClass().getName());
|
||||
}
|
||||
|
||||
// add path to result
|
||||
result = result.merge(current);
|
||||
|
||||
// if current path is done return result
|
||||
// precondition: either !needsLength or result empty
|
||||
// result is only non-empty if ? cardinality
|
||||
// but then needsLength can't be true.
|
||||
if (current.isDone()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (needsLength) {
|
||||
throw new TokenAnalysisAbortedException();
|
||||
}
|
||||
} while(loop);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private TokenAnalysisPaths getTokenForIndexes(AbstractElement path, TokenAnalysisPaths prefix, boolean needsLength) throws TokenAnalysisAbortedException {
|
||||
if (path instanceof Alternatives) {
|
||||
return getTokenForIndexesAlternatives((Alternatives) path, prefix, needsLength);
|
||||
} else if (path instanceof Group) {
|
||||
return getTokenForIndexesGroup((Group) path, prefix, needsLength);
|
||||
} else if (path instanceof Action ||
|
||||
path instanceof AbstractSemanticPredicate ||
|
||||
path instanceof JavaAction
|
||||
) {
|
||||
return prefix;
|
||||
} else {
|
||||
return getTokenForIndexesDefault(path, prefix, needsLength);
|
||||
}
|
||||
}
|
||||
|
||||
private List<List<Token>> getTokenForIndexes(AbstractElement path, List<Integer> indexes) throws TokenAnalysisAbortedException {
|
||||
return getTokenForIndexes(path, new TokenAnalysisPaths(indexes), true).getTokenPaths();
|
||||
}
|
||||
|
||||
private boolean arePathsIdenticalSymbolic(AbstractElement path1, AbstractElement path2) throws SymbolicAnalysisFailedException {
|
||||
// ignore symbolic analysis for the moment
|
||||
// TODO
|
||||
throw new SymbolicAnalysisFailedException();
|
||||
}
|
||||
|
||||
private List<Integer> range(int i, int j) {
|
||||
return IntStream.rangeClosed(i, j).boxed().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private boolean arePathsIdenticalFallback(AbstractElement path1, AbstractElement path2) {
|
||||
// + 1, because otherwise identical paths of length TOKEN_ANALYSIS_LIMIT can't be checked
|
||||
for (int i = 0; i < TOKEN_ANALYSIS_LIMIT + 1; i++) {
|
||||
Set<List<Token>> tokenListSet1;
|
||||
Set<List<Token>> tokenListSet2;
|
||||
|
||||
List<Integer> range = range(0, i);
|
||||
|
||||
try {
|
||||
tokenListSet1 = new HashSet<>(getTokenForIndexes(path1, range));
|
||||
} catch (TokenAnalysisAbortedException e) {
|
||||
tokenListSet1 = null;
|
||||
}
|
||||
|
||||
try {
|
||||
tokenListSet2 = new HashSet<>(getTokenForIndexes(path2, range));
|
||||
} catch (TokenAnalysisAbortedException e) {
|
||||
tokenListSet2 = null;
|
||||
}
|
||||
|
||||
if (tokenListSet1 == null && tokenListSet2 == null) {
|
||||
return true;
|
||||
}
|
||||
if (tokenListSet1 == null || tokenListSet2 == null) {
|
||||
// the paths have different length
|
||||
return false;
|
||||
}
|
||||
if (!tokenListSet1.equals(tokenListSet2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// we can't analyze the paths any further
|
||||
// TODO maybe assume paths are equal and show warning instead of exception
|
||||
throw new TokenAnalysisAbortedException();
|
||||
}
|
||||
|
||||
private boolean arePathsIdentical(AbstractElement path1, AbstractElement path2) {
|
||||
try {
|
||||
return arePathsIdenticalSymbolic(path1, path2);
|
||||
} catch (SymbolicAnalysisFailedException e) {
|
||||
return arePathsIdenticalFallback(path1, path2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void tokenCombinations(Function<List<Integer>, Boolean> callback) {
|
||||
MutablePrimitiveWrapper<Integer> limit = new MutablePrimitiveWrapper<>(TOKEN_ANALYSIS_LIMIT);
|
||||
|
||||
for (int i = 1; i <= limit.get(); i++) {
|
||||
if (tokenCombinations(0, 0, i, callback, limit)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// we tried all possible combinations
|
||||
// -> abort
|
||||
throw new TokenAnalysisAbortedException();
|
||||
}
|
||||
private boolean tokenCombinations(long prefix, int prefixLength, int ones, Function<List<Integer>, Boolean> callback, MutablePrimitiveWrapper<Integer> limit) {
|
||||
if (ones == 0) {
|
||||
List<Integer> indexes = new ArrayList<>(TOKEN_ANALYSIS_LIMIT);
|
||||
for (int i = 0; i < limit.get(); i++) {
|
||||
if ((prefix & (1 << i)) > 0) {
|
||||
indexes.add(i);
|
||||
}
|
||||
}
|
||||
return callback.apply(indexes);
|
||||
} else if (prefixLength + ones > limit.get()) {
|
||||
// prefix is too long
|
||||
return false;
|
||||
} else {
|
||||
for (int i = prefixLength; i < limit.get() - ones + 1; i++) {
|
||||
long current = prefix | (1 << i);
|
||||
try {
|
||||
if (tokenCombinations(current, i + 1, ones - 1, callback, limit)) {
|
||||
return true;
|
||||
}
|
||||
} catch (TokenAnalysisAbortedException e) {
|
||||
// tokens exhausted; abort current prefix
|
||||
// set limit for calling functions so this index is not checked again
|
||||
limit.set(i);
|
||||
log.info("tokens exhausted");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void abstractElementToString(AbstractElement element, StringBuilder builder, int indentation) {
|
||||
String indentationString = Strings.repeat(" ", indentation);
|
||||
|
||||
if (element == null) {
|
||||
builder.append(indentationString).append("null");
|
||||
return;
|
||||
}
|
||||
|
||||
new XtextSwitch<Boolean>(){
|
||||
@Override
|
||||
public Boolean caseKeyword(Keyword object) {
|
||||
builder.append(indentationString);
|
||||
builder.append("Keyword (").append(object.getValue()).append(")");
|
||||
return true;
|
||||
};
|
||||
@Override
|
||||
public Boolean caseGroup(Group object) {
|
||||
builder.append(indentationString);
|
||||
builder.append("Group (\n");
|
||||
object.getElements().forEach(e -> {
|
||||
abstractElementToString(e, builder, indentation + 1);
|
||||
});
|
||||
builder.append(indentationString);
|
||||
builder.append(")");
|
||||
return true;
|
||||
};
|
||||
@Override
|
||||
public Boolean caseAlternatives(Alternatives object) {
|
||||
builder.append(indentationString);
|
||||
builder.append("Alternatives (\n");
|
||||
object.getElements().forEach(e -> {
|
||||
abstractElementToString(e, builder, indentation + 1);
|
||||
});
|
||||
builder.append(indentationString);
|
||||
builder.append(")");
|
||||
return true;
|
||||
};
|
||||
@Override
|
||||
public Boolean caseRuleCall(RuleCall object) {
|
||||
AbstractRule rule = object.getRule();
|
||||
if (rule instanceof ParserRule) {
|
||||
builder.append(indentationString);
|
||||
builder.append("ParserRule ").append(rule.getName()).append(" (\n");
|
||||
abstractElementToString(rule.getAlternatives(), builder, indentation + 1);
|
||||
builder.append(indentationString);
|
||||
builder.append(")");
|
||||
} else {
|
||||
builder.append(indentationString);
|
||||
builder.append(rule.eClass().getName()).append(" ").append(rule.getName());
|
||||
}
|
||||
return true;
|
||||
};
|
||||
@Override
|
||||
public Boolean caseJavaAction(JavaAction object) {
|
||||
builder.append(indentationString);
|
||||
builder.append("JavaAction (").append(object.getCode().getSource()).append(")");
|
||||
return true;
|
||||
};
|
||||
@Override
|
||||
public Boolean caseGatedSemanticPredicate(GatedSemanticPredicate object) {
|
||||
builder.append(indentationString);
|
||||
builder.append("GatedSemanticPredicate (").append(object.getCode().getSource()).append(")");
|
||||
return true;
|
||||
};
|
||||
@Override
|
||||
public Boolean caseDisambiguatingSemanticPredicate(DisambiguatingSemanticPredicate object) {
|
||||
builder.append(indentationString);
|
||||
builder.append("DisambiguatingSemanticPredicate (").append(object.getCode().getSource()).append(")");
|
||||
return true;
|
||||
};
|
||||
@Override
|
||||
public Boolean caseAssignment(Assignment object) {
|
||||
builder.append(indentationString);
|
||||
builder.append("Assignment (\n");
|
||||
abstractElementToString(object.getTerminal(), builder, indentation + 1);
|
||||
builder.append(indentationString);
|
||||
builder.append(")");
|
||||
return true;
|
||||
};
|
||||
@Override
|
||||
public Boolean defaultCase(EObject object) {
|
||||
builder.append("unknown element: ").append(object.eClass().getName());
|
||||
return false;
|
||||
};
|
||||
}.doSwitch(element);
|
||||
|
||||
builder.append(Objects.toString(element.getCardinality(), ""));
|
||||
builder.append("\n");
|
||||
}
|
||||
|
||||
private String abstractElementToString(AbstractElement element) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
abstractElementToString(element, builder, 0);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private List<List<List<Token>>> findMinimalPathDifference(List<AbstractElement> paths) throws TokenAnalysisAbortedException {
|
||||
// first dimension of result corresponds to the paths
|
||||
// the second dimension are the alternatives of the path
|
||||
// the third dimension are tokens of the alternative of the path
|
||||
|
||||
List<List<List<Token>>> result = paths.stream()
|
||||
.map(p -> (List<List<Token>>) null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
paths.forEach(p -> {
|
||||
log.info("\n" + abstractElementToString(p));
|
||||
});
|
||||
|
||||
tokenCombinations(indexList -> {
|
||||
log.info("current index list: " + indexList);
|
||||
|
||||
// will throw TokenAnalysisAborted if any path is too short
|
||||
List<List<List<Token>>> tokenListsForPaths = paths.stream()
|
||||
//.peek(p -> log.info("next path: " + p))
|
||||
.map(p -> getTokenForIndexes(p, indexList))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
log.info("token lists: " + tokenListsForPaths);
|
||||
|
||||
int size = result.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (result.get(i) != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<List<Token>> tokenListOfCurrentPath = tokenListsForPaths.get(i);
|
||||
if (!tokenListOfCurrentPath.stream()
|
||||
.anyMatch(tokenList -> tokenListsForPaths.stream()
|
||||
.filter(s -> s != tokenListOfCurrentPath)
|
||||
// does any other path contain a similar token list (= alternative)
|
||||
.anyMatch(s -> s.contains(tokenList))
|
||||
)
|
||||
) {
|
||||
// token list set is unique for path i
|
||||
result.set(i, tokenListOfCurrentPath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result.stream()
|
||||
.allMatch(Objects::nonNull);
|
||||
});
|
||||
|
||||
return result;
|
||||
public HoistingProcessor() {
|
||||
analysis = new TokenAnalysis(config);
|
||||
}
|
||||
|
||||
// TODO: handling for TokenAnalysisAbortedException
|
||||
// TODO: skip if all guards are trivial
|
||||
private HoistingGuard findGuardForAlternatives(Alternatives alternatives) {
|
||||
log.info("find guard for alternative");
|
||||
|
||||
|
@ -493,7 +82,7 @@ public class HoistingProcessor {
|
|||
int size = paths.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
for (int j = i + 1; j < size; j++) {
|
||||
if (arePathsIdentical(paths.get(i), paths.get(j))) {
|
||||
if (analysis.arePathsIdentical(paths.get(i), paths.get(j))) {
|
||||
guards.get(i).add(guards.get(j));
|
||||
|
||||
paths.remove(j);
|
||||
|
@ -512,7 +101,7 @@ public class HoistingProcessor {
|
|||
// -> size = 1
|
||||
if (size > 1) {
|
||||
return StreamUtils.zip(
|
||||
findMinimalPathDifference(paths).stream()
|
||||
analysis.findMinimalPathDifference(paths).stream()
|
||||
.map(a -> a.stream()
|
||||
.map(s -> s.stream()
|
||||
.map(SingleTokenGuard::new)
|
||||
|
@ -531,25 +120,6 @@ public class HoistingProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
private EObject cloneEObject(EObject element) {
|
||||
EObject clone = XtextFactory.eINSTANCE.create(element.eClass());
|
||||
for (EStructuralFeature feature : element.eClass().getEAllStructuralFeatures()) {
|
||||
Object value = element.eGet(feature);
|
||||
if (value instanceof EObject) {
|
||||
// if value is EObject a deep copy is needed since an EObject can only be
|
||||
// referenced by one other EObject.
|
||||
|
||||
value = cloneEObject((EObject) value);
|
||||
}
|
||||
clone.eSet(feature, value);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
private AbstractElement cloneAbstractElement(AbstractElement element) {
|
||||
return (AbstractElement) cloneEObject(element);
|
||||
}
|
||||
|
||||
private HoistingGuard findGuardForGroup(Group group) {
|
||||
log.info("find guard for group");
|
||||
|
||||
|
@ -579,43 +149,29 @@ public class HoistingProcessor {
|
|||
cardinality.equals("*")) {
|
||||
// rewrite cardinality to alternatives
|
||||
// A? B -> A B | B
|
||||
// A* B -> A+ B | B
|
||||
|
||||
// we need a deep clone of all elements because otherwise we would destroy the original tree
|
||||
// the reason is that by default ecore doesn't allow an EObject to used used twice as a reference
|
||||
// A* B -> A+ B | B -> A B (see above)
|
||||
|
||||
// we need a clone of the element because we need to set the cardinality without changing the
|
||||
// original syntax tree
|
||||
AbstractElement clonedElement = cloneAbstractElement(element);
|
||||
if (cardinality.equals("?")) {
|
||||
clonedElement.setCardinality(null);
|
||||
} else {
|
||||
// for the * cardinality the empty case is covered by the alternative
|
||||
clonedElement.setCardinality("+");
|
||||
}
|
||||
clonedElement.setCardinality(null);
|
||||
|
||||
// make copy of every element because we can't use any element twice
|
||||
List<AbstractElement> remainingElementsInGroup = StreamUtils.fromIterator(iterator)
|
||||
.map(this::cloneAbstractElement)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// make copy of first branch and add the cloned element
|
||||
List<AbstractElement> remainingElementsInGroupIncludingCurrent = Streams.concat(
|
||||
Stream.of(clonedElement),
|
||||
remainingElementsInGroup.stream()
|
||||
.map(this::cloneAbstractElement)
|
||||
).collect(Collectors.toList());
|
||||
|
||||
// construct alternatives
|
||||
|
||||
EStructuralFeature compoundElementElementsFeature = XtextPackage.Literals.COMPOUND_ELEMENT.getEStructuralFeature(XtextPackage.COMPOUND_ELEMENT__ELEMENTS);
|
||||
|
||||
List<AbstractElement> remainingElementsInGroupIncludingCurrent = new LinkedList<>(remainingElementsInGroup);
|
||||
remainingElementsInGroupIncludingCurrent.add(0, clonedElement);
|
||||
|
||||
Group virtualPathRemaining = XtextFactory.eINSTANCE.createGroup();
|
||||
virtualPathRemaining.eSet(compoundElementElementsFeature, remainingElementsInGroup);
|
||||
addElementsToCompoundElement(virtualPathRemaining, remainingElementsInGroup);
|
||||
|
||||
Group virtualPathRemainingPlusCurrent = XtextFactory.eINSTANCE.createGroup();
|
||||
virtualPathRemainingPlusCurrent.eSet(compoundElementElementsFeature, remainingElementsInGroupIncludingCurrent);
|
||||
addElementsToCompoundElement(virtualPathRemainingPlusCurrent, remainingElementsInGroupIncludingCurrent);
|
||||
|
||||
Alternatives virtualAlternatives = XtextFactory.eINSTANCE.createAlternatives();
|
||||
virtualAlternatives.eSet(compoundElementElementsFeature, Arrays.asList(virtualPathRemaining, virtualPathRemainingPlusCurrent));
|
||||
addElementsToCompoundElement(virtualAlternatives, Arrays.asList(virtualPathRemaining, virtualPathRemainingPlusCurrent));
|
||||
|
||||
// get Guard for virtual alternatives
|
||||
HoistingGuard guard = findGuardForElementWithTrivialCardinality(virtualAlternatives);
|
||||
|
@ -652,14 +208,16 @@ public class HoistingProcessor {
|
|||
|
||||
if (Token.isToken(path)) {
|
||||
return true;
|
||||
} else if (isParserRule(path)) {
|
||||
} else if (isParserRuleCall(path)) {
|
||||
return pathHasToken(((RuleCall) path).getRule().getAlternatives());
|
||||
} else if (path instanceof Assignment) {
|
||||
return pathHasToken(((Assignment) path).getTerminal());
|
||||
} else if (path instanceof Group) {
|
||||
return ((Group) path).getElements().stream().anyMatch(this::pathHasToken);
|
||||
return ((Group) path).getElements().stream()
|
||||
.anyMatch(this::pathHasToken);
|
||||
} else if (path instanceof Alternatives) {
|
||||
return ((Alternatives) path).getElements().stream().anyMatch(this::pathHasToken);
|
||||
return ((Alternatives) path).getElements().stream()
|
||||
.anyMatch(this::pathHasToken);
|
||||
} else {
|
||||
// Actions, JavaActions, Predicates, ...
|
||||
return false;
|
||||
|
@ -711,7 +269,7 @@ public class HoistingProcessor {
|
|||
return new PredicateGuard((AbstractSemanticPredicate) element);
|
||||
} else if (Token.isToken(element)) {
|
||||
return HoistingGuard.terminal();
|
||||
} else if (isParserRule(element)) {
|
||||
} else if (isParserRuleCall(element)) {
|
||||
RuleCall call = (RuleCall) element;
|
||||
return findGuardForRule((ParserRule) call.getRule());
|
||||
} else if (element instanceof Action) {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards;
|
||||
|
||||
/**
|
||||
* @author overflow - Initial contribution and API
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards;
|
||||
|
||||
/**
|
||||
* @author overflow - Initial contribution and API
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards;
|
||||
|
||||
/**
|
||||
* @author overflow - Initial contribution and API
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards;
|
||||
|
||||
import org.eclipse.xtext.AbstractSemanticPredicate;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.JavaCodeUtils;
|
|
@ -6,7 +6,9 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards;
|
||||
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.token.Token;
|
||||
|
||||
/**
|
||||
* @author overflow - Initial contribution and API
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards;
|
||||
|
||||
/**
|
||||
* @author overflow - Initial contribution and API
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.pathAnalysis;
|
||||
|
||||
/**
|
||||
* @author overflow - Initial contribution and API
|
|
@ -0,0 +1,355 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 itemis AG (http://www.itemis.eu) and others.
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.pathAnalysis;
|
||||
|
||||
import static org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.utils.DebugUtils.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.eclipse.xtext.AbstractElement;
|
||||
import org.eclipse.xtext.AbstractSemanticPredicate;
|
||||
import org.eclipse.xtext.Action;
|
||||
import org.eclipse.xtext.Alternatives;
|
||||
import org.eclipse.xtext.Assignment;
|
||||
import org.eclipse.xtext.Group;
|
||||
import org.eclipse.xtext.JavaAction;
|
||||
import org.eclipse.xtext.RuleCall;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.HoistingConfiguration;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.token.Token;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.utils.MutablePrimitiveWrapper;
|
||||
|
||||
import static org.eclipse.xtext.GrammarUtil.*;
|
||||
|
||||
/**
|
||||
* @author overflow - Initial contribution and API
|
||||
*/
|
||||
public class TokenAnalysis {
|
||||
private HoistingConfiguration config;
|
||||
|
||||
private Logger log = Logger.getLogger(TokenAnalysis.class);
|
||||
|
||||
public TokenAnalysis(HoistingConfiguration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
private TokenAnalysisPaths getTokenForIndexesAlternatives(Alternatives path, TokenAnalysisPaths prefix, boolean needsLength) throws TokenAnalysisAbortedException {
|
||||
if (prefix.isDone()) {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
TokenAnalysisPaths result;
|
||||
if (isOptionalCardinality(path)) {
|
||||
result = prefix;
|
||||
if (needsLength) {
|
||||
// analysis is not done but there are no more mandatory tokens
|
||||
throw new TokenAnalysisAbortedException();
|
||||
}
|
||||
} else {
|
||||
result = TokenAnalysisPaths.empty(prefix);
|
||||
}
|
||||
|
||||
boolean loop = isMultipleCardinality(path);
|
||||
|
||||
do {
|
||||
boolean allDone = true;
|
||||
|
||||
for (AbstractElement element : path.getElements()) {
|
||||
TokenAnalysisPaths current = new TokenAnalysisPaths(prefix);
|
||||
current = getTokenForIndexes(element, current, needsLength); // will check for needsLength
|
||||
|
||||
if (!current.isDone()) {
|
||||
allDone = false;
|
||||
}
|
||||
|
||||
result = result.merge(current);
|
||||
}
|
||||
|
||||
if (allDone) {
|
||||
break;
|
||||
}
|
||||
|
||||
prefix = result;
|
||||
|
||||
// repeat until all further extensions of prefix are done
|
||||
} while(loop);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private TokenAnalysisPaths getTokenForIndexesGroup(Group path, TokenAnalysisPaths prefix, boolean needsLength) throws TokenAnalysisAbortedException {
|
||||
if (prefix.isDone()) {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
TokenAnalysisPaths result;
|
||||
TokenAnalysisPaths current = new TokenAnalysisPaths(prefix);
|
||||
|
||||
if (isOptionalCardinality(path)) {
|
||||
result = prefix;
|
||||
if (needsLength) {
|
||||
// analysis is not done but there are no more mandatory tokens
|
||||
throw new TokenAnalysisAbortedException();
|
||||
}
|
||||
} else {
|
||||
result = TokenAnalysisPaths.empty(prefix);
|
||||
}
|
||||
|
||||
boolean loop = isMultipleCardinality(path);
|
||||
|
||||
do {
|
||||
for (AbstractElement element : path.getElements()) {
|
||||
current = getTokenForIndexes(element, current, false);
|
||||
|
||||
if (current.isDone()) {
|
||||
// no need to look further
|
||||
|
||||
return result.merge(current);
|
||||
}
|
||||
}
|
||||
|
||||
if (needsLength && !current.isDone()) {
|
||||
// analysis is not done but there are no more mandatory tokens
|
||||
throw new TokenAnalysisAbortedException();
|
||||
}
|
||||
|
||||
result = result.merge(current);
|
||||
current = new TokenAnalysisPaths(result);
|
||||
} while(loop);
|
||||
|
||||
// if cardinality is trivial or ? return result
|
||||
return result;
|
||||
}
|
||||
|
||||
private TokenAnalysisPaths getTokenForIndexesDefault(AbstractElement path, TokenAnalysisPaths prefix, boolean needsLength) throws TokenAnalysisAbortedException {
|
||||
if (prefix.isDone()) {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
TokenAnalysisPaths result;
|
||||
|
||||
if (isOptionalCardinality(path)) {
|
||||
result = prefix;
|
||||
if (needsLength) {
|
||||
throw new TokenAnalysisAbortedException();
|
||||
}
|
||||
} else {
|
||||
result = TokenAnalysisPaths.empty(prefix);
|
||||
}
|
||||
|
||||
TokenAnalysisPaths current = new TokenAnalysisPaths(prefix);
|
||||
|
||||
boolean loop = isMultipleCardinality(path);
|
||||
do {
|
||||
if (Token.isToken(path)) {
|
||||
current.add(path);
|
||||
} else if (isParserRuleCall(path)) {
|
||||
// path doesn't need length, because we're going to check that anyway in this function
|
||||
current = getTokenForIndexes(((RuleCall) path).getRule().getAlternatives(), current, false);
|
||||
} else if (path instanceof Assignment) {
|
||||
current = getTokenForIndexes(((Assignment) path).getTerminal(), current, false);
|
||||
} else {
|
||||
throw new IllegalArgumentException("unknown element: " + path.eClass().getName());
|
||||
}
|
||||
|
||||
// add path to result
|
||||
result = result.merge(current);
|
||||
|
||||
// if current path is done return result
|
||||
// precondition: either !needsLength or result empty
|
||||
// result is only non-empty if ? cardinality
|
||||
// but then needsLength can't be true.
|
||||
if (current.isDone()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (needsLength) {
|
||||
throw new TokenAnalysisAbortedException();
|
||||
}
|
||||
} while(loop);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private TokenAnalysisPaths getTokenForIndexes(AbstractElement path, TokenAnalysisPaths prefix, boolean needsLength) throws TokenAnalysisAbortedException {
|
||||
if (path instanceof Alternatives) {
|
||||
return getTokenForIndexesAlternatives((Alternatives) path, prefix, needsLength);
|
||||
} else if (path instanceof Group) {
|
||||
return getTokenForIndexesGroup((Group) path, prefix, needsLength);
|
||||
} else if (path instanceof Action ||
|
||||
path instanceof AbstractSemanticPredicate ||
|
||||
path instanceof JavaAction
|
||||
) {
|
||||
return prefix;
|
||||
} else {
|
||||
return getTokenForIndexesDefault(path, prefix, needsLength);
|
||||
}
|
||||
}
|
||||
|
||||
private List<List<Token>> getTokenForIndexes(AbstractElement path, List<Integer> indexes) throws TokenAnalysisAbortedException {
|
||||
return getTokenForIndexes(path, new TokenAnalysisPaths(indexes), true).getTokenPaths();
|
||||
}
|
||||
|
||||
private boolean arePathsIdenticalSymbolic(AbstractElement path1, AbstractElement path2) throws SymbolicAnalysisFailedException {
|
||||
// ignore symbolic analysis for the moment
|
||||
// TODO
|
||||
throw new SymbolicAnalysisFailedException();
|
||||
}
|
||||
|
||||
private List<Integer> range(int i, int j) {
|
||||
return IntStream.rangeClosed(i, j).boxed().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private boolean arePathsIdenticalFallback(AbstractElement path1, AbstractElement path2) {
|
||||
// + 1, because otherwise identical paths of length TOKEN_ANALYSIS_LIMIT can't be checked
|
||||
for (int i = 0; i < config.getTokenLimit() + 1; i++) {
|
||||
Set<List<Token>> tokenListSet1;
|
||||
Set<List<Token>> tokenListSet2;
|
||||
|
||||
List<Integer> range = range(0, i);
|
||||
|
||||
try {
|
||||
tokenListSet1 = new HashSet<>(getTokenForIndexes(path1, range));
|
||||
} catch (TokenAnalysisAbortedException e) {
|
||||
tokenListSet1 = null;
|
||||
}
|
||||
|
||||
try {
|
||||
tokenListSet2 = new HashSet<>(getTokenForIndexes(path2, range));
|
||||
} catch (TokenAnalysisAbortedException e) {
|
||||
tokenListSet2 = null;
|
||||
}
|
||||
|
||||
if (tokenListSet1 == null && tokenListSet2 == null) {
|
||||
return true;
|
||||
}
|
||||
if (tokenListSet1 == null || tokenListSet2 == null) {
|
||||
// the paths have different length
|
||||
return false;
|
||||
}
|
||||
if (!tokenListSet1.equals(tokenListSet2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// we can't analyze the paths any further
|
||||
// TODO maybe assume paths are equal and show warning instead of exception
|
||||
throw new TokenAnalysisAbortedException();
|
||||
}
|
||||
|
||||
public boolean arePathsIdentical(AbstractElement path1, AbstractElement path2) {
|
||||
try {
|
||||
return arePathsIdenticalSymbolic(path1, path2);
|
||||
} catch (SymbolicAnalysisFailedException e) {
|
||||
return arePathsIdenticalFallback(path1, path2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void tokenCombinations(Function<List<Integer>, Boolean> callback) {
|
||||
MutablePrimitiveWrapper<Integer> limit = new MutablePrimitiveWrapper<>(config.getTokenLimit());
|
||||
|
||||
for (int i = 1; i <= limit.get(); i++) {
|
||||
if (tokenCombinations(0, 0, i, callback, limit)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// we tried all possible combinations
|
||||
// -> abort
|
||||
throw new TokenAnalysisAbortedException();
|
||||
}
|
||||
private boolean tokenCombinations(long prefix, int prefixLength, int ones, Function<List<Integer>, Boolean> callback, MutablePrimitiveWrapper<Integer> limit) {
|
||||
if (ones == 0) {
|
||||
List<Integer> indexes = new ArrayList<>(limit.get());
|
||||
for (int i = 0; i < limit.get(); i++) {
|
||||
if ((prefix & (1 << i)) > 0) {
|
||||
indexes.add(i);
|
||||
}
|
||||
}
|
||||
return callback.apply(indexes);
|
||||
} else if (prefixLength + ones > limit.get()) {
|
||||
// prefix is too long
|
||||
return false;
|
||||
} else {
|
||||
for (int i = prefixLength; i < limit.get() - ones + 1; i++) {
|
||||
long current = prefix | (1 << i);
|
||||
try {
|
||||
if (tokenCombinations(current, i + 1, ones - 1, callback, limit)) {
|
||||
return true;
|
||||
}
|
||||
} catch (TokenAnalysisAbortedException e) {
|
||||
// tokens exhausted; abort current prefix
|
||||
// set limit for calling functions so this index is not checked again
|
||||
limit.set(i);
|
||||
log.info("tokens exhausted");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public List<List<List<Token>>> findMinimalPathDifference(List<AbstractElement> paths) throws TokenAnalysisAbortedException {
|
||||
// first dimension of result corresponds to the paths
|
||||
// the second dimension are the alternatives of the path
|
||||
// the third dimension are tokens of the alternative of the path
|
||||
|
||||
List<List<List<Token>>> result = paths.stream()
|
||||
.map(p -> (List<List<Token>>) null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
paths.forEach(p -> {
|
||||
log.info("\n" + abstractElementToString(p));
|
||||
});
|
||||
|
||||
tokenCombinations(indexList -> {
|
||||
log.info("current index list: " + indexList);
|
||||
|
||||
// will throw TokenAnalysisAborted if any path is too short
|
||||
List<List<List<Token>>> tokenListsForPaths = paths.stream()
|
||||
//.peek(p -> log.info("next path: " + p))
|
||||
.map(p -> getTokenForIndexes(p, indexList))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
log.info("token lists: " + tokenListsForPaths);
|
||||
|
||||
int size = result.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (result.get(i) != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<List<Token>> tokenListOfCurrentPath = tokenListsForPaths.get(i);
|
||||
if (!tokenListOfCurrentPath.stream()
|
||||
.anyMatch(tokenList -> tokenListsForPaths.stream()
|
||||
.filter(s -> s != tokenListOfCurrentPath)
|
||||
// does any other path contain a similar token list (= alternative)
|
||||
.anyMatch(s -> s.contains(tokenList))
|
||||
)
|
||||
) {
|
||||
// token list set is unique for path i
|
||||
result.set(i, tokenListOfCurrentPath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result.stream()
|
||||
.allMatch(Objects::nonNull);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.pathAnalysis;
|
||||
|
||||
/**
|
||||
* @author overflow - Initial contribution and API
|
|
@ -6,13 +6,14 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.pathAnalysis;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.xtext.AbstractElement;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.token.Token;
|
||||
|
||||
/**
|
||||
* @author overflow - Initial contribution and API
|
|
@ -6,13 +6,14 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.pathAnalysis;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.xtext.AbstractElement;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.token.Token;
|
||||
|
||||
/**
|
||||
* @author overflow - Initial contribution and API
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.token;
|
||||
|
||||
import org.eclipse.xtext.Keyword;
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.token;
|
||||
|
||||
/**
|
||||
* @author overflow - Initial contribution and API
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.token;
|
||||
|
||||
import org.eclipse.xtext.TerminalRule;
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting;
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.token;
|
||||
|
||||
import org.eclipse.xtext.AbstractElement;
|
||||
import org.eclipse.xtext.AbstractRule;
|
|
@ -0,0 +1,128 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2021 itemis AG (http://www.itemis.eu) and others.
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.utils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.emf.ecore.EObject;
|
||||
import org.eclipse.xtext.AbstractElement;
|
||||
import org.eclipse.xtext.AbstractRule;
|
||||
import org.eclipse.xtext.Alternatives;
|
||||
import org.eclipse.xtext.Assignment;
|
||||
import org.eclipse.xtext.DisambiguatingSemanticPredicate;
|
||||
import org.eclipse.xtext.GatedSemanticPredicate;
|
||||
import org.eclipse.xtext.Group;
|
||||
import org.eclipse.xtext.JavaAction;
|
||||
import org.eclipse.xtext.Keyword;
|
||||
import org.eclipse.xtext.ParserRule;
|
||||
import org.eclipse.xtext.RuleCall;
|
||||
import org.eclipse.xtext.util.XtextSwitch;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
/**
|
||||
* @author overflow - Initial contribution and API
|
||||
*/
|
||||
public class DebugUtils {
|
||||
private static void abstractElementToString(AbstractElement element, StringBuilder builder, int indentation) {
|
||||
String indentationString = Strings.repeat(" ", indentation);
|
||||
|
||||
if (element == null) {
|
||||
builder.append(indentationString).append("null");
|
||||
return;
|
||||
}
|
||||
|
||||
new XtextSwitch<Boolean>(){
|
||||
@Override
|
||||
public Boolean caseKeyword(Keyword object) {
|
||||
builder.append(indentationString);
|
||||
builder.append("Keyword (").append(object.getValue()).append(")");
|
||||
return true;
|
||||
};
|
||||
@Override
|
||||
public Boolean caseGroup(Group object) {
|
||||
builder.append(indentationString);
|
||||
builder.append("Group (\n");
|
||||
object.getElements().forEach(e -> {
|
||||
abstractElementToString(e, builder, indentation + 1);
|
||||
});
|
||||
builder.append(indentationString);
|
||||
builder.append(")");
|
||||
return true;
|
||||
};
|
||||
@Override
|
||||
public Boolean caseAlternatives(Alternatives object) {
|
||||
builder.append(indentationString);
|
||||
builder.append("Alternatives (\n");
|
||||
object.getElements().forEach(e -> {
|
||||
abstractElementToString(e, builder, indentation + 1);
|
||||
});
|
||||
builder.append(indentationString);
|
||||
builder.append(")");
|
||||
return true;
|
||||
};
|
||||
@Override
|
||||
public Boolean caseRuleCall(RuleCall object) {
|
||||
AbstractRule rule = object.getRule();
|
||||
if (rule instanceof ParserRule) {
|
||||
builder.append(indentationString);
|
||||
builder.append("ParserRule ").append(rule.getName()).append(" (\n");
|
||||
abstractElementToString(rule.getAlternatives(), builder, indentation + 1);
|
||||
builder.append(indentationString);
|
||||
builder.append(")");
|
||||
} else {
|
||||
builder.append(indentationString);
|
||||
builder.append(rule.eClass().getName()).append(" ").append(rule.getName());
|
||||
}
|
||||
return true;
|
||||
};
|
||||
@Override
|
||||
public Boolean caseJavaAction(JavaAction object) {
|
||||
builder.append(indentationString);
|
||||
builder.append("JavaAction (").append(object.getCode().getSource()).append(")");
|
||||
return true;
|
||||
};
|
||||
@Override
|
||||
public Boolean caseGatedSemanticPredicate(GatedSemanticPredicate object) {
|
||||
builder.append(indentationString);
|
||||
builder.append("GatedSemanticPredicate (").append(object.getCode().getSource()).append(")");
|
||||
return true;
|
||||
};
|
||||
@Override
|
||||
public Boolean caseDisambiguatingSemanticPredicate(DisambiguatingSemanticPredicate object) {
|
||||
builder.append(indentationString);
|
||||
builder.append("DisambiguatingSemanticPredicate (").append(object.getCode().getSource()).append(")");
|
||||
return true;
|
||||
};
|
||||
@Override
|
||||
public Boolean caseAssignment(Assignment object) {
|
||||
builder.append(indentationString);
|
||||
builder.append("Assignment (\n");
|
||||
abstractElementToString(object.getTerminal(), builder, indentation + 1);
|
||||
builder.append(indentationString);
|
||||
builder.append(")");
|
||||
return true;
|
||||
};
|
||||
@Override
|
||||
public Boolean defaultCase(EObject object) {
|
||||
builder.append("unknown element: ").append(object.eClass().getName());
|
||||
return false;
|
||||
};
|
||||
}.doSwitch(element);
|
||||
|
||||
builder.append(Objects.toString(element.getCardinality(), ""));
|
||||
builder.append("\n");
|
||||
}
|
||||
|
||||
public static String abstractElementToString(AbstractElement element) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
abstractElementToString(element, builder, 0);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
|
@ -20,6 +20,8 @@ import java.util.Iterator;
|
|||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.emf.common.util.TreeIterator;
|
||||
import org.eclipse.emf.ecore.EClass;
|
||||
|
@ -700,4 +702,35 @@ public class GrammarUtil {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static EObject cloneEObject(EObject element) {
|
||||
EObject clone = XtextFactory.eINSTANCE.create(element.eClass());
|
||||
for (EStructuralFeature feature : element.eClass().getEAllStructuralFeatures()) {
|
||||
Object value = element.eGet(feature);
|
||||
if (value instanceof EObject) {
|
||||
// if value is EObject a deep copy is needed since an EObject can only be
|
||||
// referenced by one other EObject.
|
||||
|
||||
value = cloneEObject((EObject) value);
|
||||
}
|
||||
clone.eSet(feature, value);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
public static AbstractElement cloneAbstractElement(AbstractElement element) {
|
||||
return (AbstractElement) cloneEObject(element);
|
||||
}
|
||||
|
||||
public static void addElementsToCompoundElement(CompoundElement element, Stream<? extends AbstractElement> elements) {
|
||||
EStructuralFeature compoundElementElementsFeature = XtextPackage.Literals.COMPOUND_ELEMENT.getEStructuralFeature(XtextPackage.COMPOUND_ELEMENT__ELEMENTS);
|
||||
|
||||
element.eSet(compoundElementElementsFeature, elements
|
||||
.map(GrammarUtil::cloneAbstractElement)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public static void addElementsToCompoundElement(CompoundElement element, Collection<? extends AbstractElement> elements) {
|
||||
addElementsToCompoundElement(element, elements.stream());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue