refactoring of hoisting code

This commit is contained in:
overflowerror 2021-11-27 18:37:24 +01:00
parent cb606d6937
commit 5fdb3424ac
25 changed files with 599 additions and 501 deletions

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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) {

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

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

View file

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