mirror of
https://github.com/sigmasternchen/xtext-core
synced 2025-03-15 08:18:55 +00:00
fixed problem with unordered groups and non-trivial cardinalities in
nested prefix alternatives analysis now flattenPaths() considers following repetitions new problem: unordered groups and non-trivial cardinalities without non-optional elements causes explosion of generated alternatives, which in turn cause the identity analysis to go out of control example: S: ('a'? | 'b'?)+ with a token limit of 10 the nested prefix alternatives analysis would generate over 1300 alternatives current quick fix: limit of alternatives
This commit is contained in:
parent
36f790b37b
commit
ebef309674
8 changed files with 320 additions and 89 deletions
|
@ -14,6 +14,7 @@ import org.eclipse.xtext.AbstractRule;
|
||||||
import org.eclipse.xtext.Alternatives;
|
import org.eclipse.xtext.Alternatives;
|
||||||
import org.eclipse.xtext.Grammar;
|
import org.eclipse.xtext.Grammar;
|
||||||
import org.eclipse.xtext.Group;
|
import org.eclipse.xtext.Group;
|
||||||
|
import org.eclipse.xtext.Keyword;
|
||||||
import org.eclipse.xtext.UnorderedGroup;
|
import org.eclipse.xtext.UnorderedGroup;
|
||||||
import org.eclipse.xtext.XtextStandaloneSetup;
|
import org.eclipse.xtext.XtextStandaloneSetup;
|
||||||
import org.eclipse.xtext.resource.XtextResource;
|
import org.eclipse.xtext.resource.XtextResource;
|
||||||
|
@ -434,7 +435,7 @@ public class HoistingProcessorTest extends AbstractXtextTests {
|
||||||
assertEquals("((" + getSyntaxForKeywordToken("a", 1) + " || (p0)) && (" + getSyntaxForKeywordToken("b", 1) + " || (p1)))", guard.render());
|
assertEquals("((" + getSyntaxForKeywordToken("a", 1) + " || (p0)) && (" + getSyntaxForKeywordToken("b", 1) + " || (p1)))", guard.render());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = TokenAnalysisAbortedException.class)
|
@Test
|
||||||
public void testUnorderedGroupWithEmptyPathsWithoutContext_expectTokenAnalysisAbortedException() throws Exception {
|
public void testUnorderedGroupWithEmptyPathsWithoutContext_expectTokenAnalysisAbortedException() throws Exception {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
String model =
|
String model =
|
||||||
|
@ -936,6 +937,44 @@ public class HoistingProcessorTest extends AbstractXtextTests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNestedAlternativesWithIdenticalPrefix_parentElementShouldNotBeChanged_expectContextCheckInResult() throws Exception {
|
||||||
|
// @formatter:off
|
||||||
|
String model =
|
||||||
|
MODEL_PREAMBLE +
|
||||||
|
"S: {S} $$ p0 $$?=> ('a')? \n" +
|
||||||
|
" | {S} $$ p1 $$?=> ('b')? ;\n";
|
||||||
|
// @formatter:off
|
||||||
|
XtextResource resource = getResourceFromString(model);
|
||||||
|
Grammar grammar = ((Grammar) resource.getContents().get(0));
|
||||||
|
hoistingProcessor.init(grammar);
|
||||||
|
AbstractRule rule = getRule(grammar, "S");
|
||||||
|
|
||||||
|
HoistingGuard guard = hoistingProcessor.findHoistingGuard(rule.getAlternatives());
|
||||||
|
assertFalse(guard.isTrivial());
|
||||||
|
assertTrue(guard.hasTerminal());
|
||||||
|
assertEquals(
|
||||||
|
"(" +
|
||||||
|
"(" + getSyntaxForEofToken(1) + " || (p0) || (p1)) && " +
|
||||||
|
"(" + getSyntaxForKeywordToken("a", 1) + " || (p0)) && " +
|
||||||
|
"(" + getSyntaxForKeywordToken("b", 1) + " || (p1))" +
|
||||||
|
")",
|
||||||
|
guard.render()
|
||||||
|
);
|
||||||
|
|
||||||
|
// check sizes of groups and cardinalities of keywords
|
||||||
|
Alternatives alternatives = (Alternatives) rule.getAlternatives();
|
||||||
|
assertEquals(2, alternatives.getElements().size());
|
||||||
|
Group group = (Group) alternatives.getElements().get(0);
|
||||||
|
assertEquals(3, group.getElements().size());
|
||||||
|
Keyword keyword = (Keyword) group.getElements().get(2);
|
||||||
|
assertEquals("?", keyword.getCardinality());
|
||||||
|
group = (Group) alternatives.getElements().get(1);
|
||||||
|
assertEquals(3, group.getElements().size());
|
||||||
|
keyword = (Keyword) group.getElements().get(2);
|
||||||
|
assertEquals("?", keyword.getCardinality());
|
||||||
|
}
|
||||||
|
|
||||||
// symbolic analysis not yet implemented
|
// symbolic analysis not yet implemented
|
||||||
//@Test
|
//@Test
|
||||||
public void testAlternativesIdenticalPathsWithSymbolicAnalysis() throws Exception {
|
public void testAlternativesIdenticalPathsWithSymbolicAnalysis() throws Exception {
|
||||||
|
|
|
@ -16,11 +16,14 @@ import java.util.LinkedList;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import static org.eclipse.emf.ecore.util.EcoreUtil.*;
|
import static org.eclipse.emf.ecore.util.EcoreUtil.*;
|
||||||
|
|
||||||
|
import org.eclipse.emf.ecore.EObject;
|
||||||
|
import org.eclipse.emf.ecore.util.EcoreUtil;
|
||||||
import org.eclipse.xtext.AbstractElement;
|
import org.eclipse.xtext.AbstractElement;
|
||||||
import org.eclipse.xtext.AbstractRule;
|
import org.eclipse.xtext.AbstractRule;
|
||||||
import org.eclipse.xtext.AbstractSemanticPredicate;
|
import org.eclipse.xtext.AbstractSemanticPredicate;
|
||||||
|
@ -28,6 +31,7 @@ import org.eclipse.xtext.Action;
|
||||||
import org.eclipse.xtext.Alternatives;
|
import org.eclipse.xtext.Alternatives;
|
||||||
import org.eclipse.xtext.Assignment;
|
import org.eclipse.xtext.Assignment;
|
||||||
import org.eclipse.xtext.CompoundElement;
|
import org.eclipse.xtext.CompoundElement;
|
||||||
|
import org.eclipse.xtext.EcoreUtil2;
|
||||||
import org.eclipse.xtext.Grammar;
|
import org.eclipse.xtext.Grammar;
|
||||||
import org.eclipse.xtext.Group;
|
import org.eclipse.xtext.Group;
|
||||||
import org.eclipse.xtext.JavaAction;
|
import org.eclipse.xtext.JavaAction;
|
||||||
|
@ -41,7 +45,6 @@ import static org.eclipse.xtext.GrammarUtil.*;
|
||||||
|
|
||||||
import org.eclipse.xtext.xtext.generator.parser.antlr.JavaCodeUtils;
|
import org.eclipse.xtext.xtext.generator.parser.antlr.JavaCodeUtils;
|
||||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.NestedPrefixAlternativesException;
|
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.NestedPrefixAlternativesException;
|
||||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.OptionalCardinalityWithoutContextException;
|
|
||||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.TokenAnalysisAbortedException;
|
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.TokenAnalysisAbortedException;
|
||||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.UnsupportedConstructException;
|
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.UnsupportedConstructException;
|
||||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards.AlternativeTokenSequenceGuard;
|
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards.AlternativeTokenSequenceGuard;
|
||||||
|
@ -125,13 +128,53 @@ public class HoistingProcessor {
|
||||||
return virtualNopJavaAction;
|
return virtualNopJavaAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompoundElement flattenPaths(CompoundElement original, List<AbstractElement> paths, List<? extends HoistingGuard> guards) {
|
private CompoundElement flattenPaths(AbstractElement original, List<AbstractElement> paths, List<? extends HoistingGuard> guards) {
|
||||||
// work on copy of original to preserve eParent
|
// we need to preserve parent for context analysis
|
||||||
CompoundElement flattened = copy(original);
|
// but we are not allowed to change the tree
|
||||||
|
// -> clone parent
|
||||||
|
EObject clonedParent = copy(original.eContainer());
|
||||||
|
CompoundElement flattened = XtextFactory.eINSTANCE.createAlternatives();
|
||||||
|
|
||||||
|
if (clonedParent instanceof CompoundElement) {
|
||||||
|
setElementsOfCompoundElement((CompoundElement) clonedParent,
|
||||||
|
((CompoundElement) clonedParent).getElements().stream()
|
||||||
|
.map(e -> {
|
||||||
|
if (e == original) {
|
||||||
|
return flattened;
|
||||||
|
} else {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
EcoreUtil2.setEParent(flattened, clonedParent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<AbstractElement> pathsStream = paths.stream();
|
||||||
|
|
||||||
|
if (original instanceof UnorderedGroup) {
|
||||||
|
// in case original element is UnorderedGroup add original after each path
|
||||||
|
// so token analysis can fetches all possible paths correctly
|
||||||
|
|
||||||
|
pathsStream = pathsStream
|
||||||
|
.map(p -> {
|
||||||
|
Group virtualGroup = XtextFactory.eINSTANCE.createGroup();
|
||||||
|
setElementsOfCompoundElement(virtualGroup, Arrays.asList(p, copy(original)));
|
||||||
|
return virtualGroup;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setElementsOfCompoundElement(flattened,
|
setElementsOfCompoundElement(flattened,
|
||||||
StreamUtils.zip(
|
StreamUtils.zip(
|
||||||
paths.stream()
|
pathsStream
|
||||||
.map(analysis::getAllPossiblePaths),
|
.map(analysis::getAllPossiblePaths)
|
||||||
|
.map(l1 -> l1.stream()
|
||||||
|
.map(l2 -> l2.stream()
|
||||||
|
.map(EcoreUtil::copy)
|
||||||
|
// remove cardinality; token analysis already handled that
|
||||||
|
.peek(e -> e.setCardinality(null))
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
).collect(Collectors.toList())
|
||||||
|
),
|
||||||
guards.stream()
|
guards.stream()
|
||||||
.map(HoistingGuard.class::cast),
|
.map(HoistingGuard.class::cast),
|
||||||
(List<List<AbstractElement>> e, HoistingGuard g) -> Tuples.pair(e, g)
|
(List<List<AbstractElement>> e, HoistingGuard g) -> Tuples.pair(e, g)
|
||||||
|
@ -179,6 +222,8 @@ public class HoistingProcessor {
|
||||||
return flattened;
|
return flattened;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean hasSeen = false;
|
||||||
|
|
||||||
private HoistingGuard findGuardForAlternatives(CompoundElement alternatives, AbstractRule currentRule) {
|
private HoistingGuard findGuardForAlternatives(CompoundElement alternatives, AbstractRule currentRule) {
|
||||||
log.info("find guard for alternative");
|
log.info("find guard for alternative");
|
||||||
|
|
||||||
|
@ -249,12 +294,20 @@ public class HoistingProcessor {
|
||||||
// -> flatten paths to alternative and try again
|
// -> flatten paths to alternative and try again
|
||||||
// this is very inefficient
|
// this is very inefficient
|
||||||
log.warn("nested prefix alternatives detected");
|
log.warn("nested prefix alternatives detected");
|
||||||
log.warn("avoid these since they can't be handled efficiency");
|
log.warn("avoid these since they can't be handled efficiently");
|
||||||
log.info(abstractElementToString(alternatives));
|
log.info(abstractElementToString(alternatives));
|
||||||
|
|
||||||
CompoundElement flattened = flattenPaths(alternatives, paths, guards);
|
CompoundElement flattened = flattenPaths(alternatives, paths, guards);
|
||||||
log.info(abstractElementToString(flattened));
|
log.info(abstractElementToString(flattened));
|
||||||
|
|
||||||
|
log.info(flattened.getElements().size());
|
||||||
|
// TODO: value configurable?
|
||||||
|
if (flattened.getElements().size() > 100) {
|
||||||
|
throw new NestedPrefixAlternativesException("nested prefix alternatives cant be analysed because of too many paths");
|
||||||
|
}
|
||||||
|
|
||||||
|
//throw new RuntimeException();
|
||||||
|
|
||||||
return findGuardForAlternatives(flattened, currentRule);
|
return findGuardForAlternatives(flattened, currentRule);
|
||||||
} catch(TokenAnalysisAbortedException e) {
|
} catch(TokenAnalysisAbortedException e) {
|
||||||
throw new TokenAnalysisAbortedException(e.getMessage(), e, currentRule);
|
throw new TokenAnalysisAbortedException(e.getMessage(), e, currentRule);
|
||||||
|
@ -285,9 +338,7 @@ public class HoistingProcessor {
|
||||||
AbstractElement element = iterator.next();
|
AbstractElement element = iterator.next();
|
||||||
|
|
||||||
String cardinality = element.getCardinality();
|
String cardinality = element.getCardinality();
|
||||||
if (cardinality == null ||
|
if (isTrivialCardinality(element) || isOneOrMoreCardinality(element)) {
|
||||||
cardinality.equals("") ||
|
|
||||||
cardinality.equals("+")) {
|
|
||||||
|
|
||||||
HoistingGuard guard = findGuardForElementWithTrivialCardinality(element, currentRule);
|
HoistingGuard guard = findGuardForElementWithTrivialCardinality(element, currentRule);
|
||||||
groupGuard.add(guard);
|
groupGuard.add(guard);
|
||||||
|
@ -300,18 +351,12 @@ public class HoistingProcessor {
|
||||||
groupGuard.setHasTerminal();
|
groupGuard.setHasTerminal();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (cardinality.equals("?") ||
|
} else if (isOptionalCardinality(element)) {
|
||||||
cardinality.equals("*")) {
|
|
||||||
// though not technically necessary, rewrite tree to context checks are not needed
|
// though not technically necessary, rewrite tree to context checks are not needed
|
||||||
|
|
||||||
// rewrite cardinality to alternatives
|
// rewrite cardinality to alternatives
|
||||||
// A? B -> A B | B
|
// A? B -> A B | B
|
||||||
// A* B -> A+ B | B -> A B (see above)
|
// A* B -> A+ B | B -> A B | 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 = copy(element);
|
|
||||||
clonedElement.setCardinality(null);
|
|
||||||
|
|
||||||
// make copy of every element because we can't use any element twice
|
// make copy of every element because we can't use any element twice
|
||||||
List<AbstractElement> remainingElementsInGroup = StreamUtils.fromIterator(iterator)
|
List<AbstractElement> remainingElementsInGroup = StreamUtils.fromIterator(iterator)
|
||||||
|
@ -319,33 +364,50 @@ public class HoistingProcessor {
|
||||||
|
|
||||||
if (remainingElementsInGroup.isEmpty()) {
|
if (remainingElementsInGroup.isEmpty()) {
|
||||||
// B is empty
|
// B is empty
|
||||||
// context is needed to generate virtual alternatives
|
// context will be taken from parent element
|
||||||
throw new OptionalCardinalityWithoutContextException("no context in group", currentRule);
|
|
||||||
|
HoistingGuard guard = findGuardForOptionalCardinalityWithoutContext(element, currentRule);
|
||||||
|
groupGuard.add(guard);
|
||||||
|
|
||||||
|
if (guard.hasTerminal()) {
|
||||||
|
groupGuard.setHasTerminal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// there are no following elements
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// B is non-empty
|
||||||
|
// -> construct context for alternatives
|
||||||
|
|
||||||
|
// we need a clone of the element because we need to set the cardinality without changing the
|
||||||
|
// original syntax tree
|
||||||
|
AbstractElement clonedElement = copy(element);
|
||||||
|
clonedElement.setCardinality(null);
|
||||||
|
|
||||||
|
// make copy of first branch and add the cloned element
|
||||||
|
List<AbstractElement> remainingElementsInGroupIncludingCurrent = new LinkedList<>(remainingElementsInGroup);
|
||||||
|
remainingElementsInGroupIncludingCurrent.add(0, clonedElement);
|
||||||
|
|
||||||
|
Group virtualPathRemaining = XtextFactory.eINSTANCE.createGroup();
|
||||||
|
setElementsOfCompoundElement(virtualPathRemaining, remainingElementsInGroup);
|
||||||
|
|
||||||
|
Group virtualPathRemainingPlusCurrent = XtextFactory.eINSTANCE.createGroup();
|
||||||
|
setElementsOfCompoundElement(virtualPathRemainingPlusCurrent, remainingElementsInGroupIncludingCurrent);
|
||||||
|
|
||||||
|
Alternatives virtualAlternatives = XtextFactory.eINSTANCE.createAlternatives();
|
||||||
|
setElementsOfCompoundElement(virtualAlternatives, Arrays.asList(virtualPathRemaining, virtualPathRemainingPlusCurrent));
|
||||||
|
|
||||||
|
// get Guard for virtual alternatives
|
||||||
|
HoistingGuard guard = findGuardForElementWithTrivialCardinality(virtualAlternatives, currentRule);
|
||||||
|
groupGuard.add(guard);
|
||||||
|
|
||||||
|
if (guard.hasTerminal()) {
|
||||||
|
groupGuard.setHasTerminal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// following elements are included in alternative, no need to check further
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make copy of first branch and add the cloned element
|
|
||||||
List<AbstractElement> remainingElementsInGroupIncludingCurrent = new LinkedList<>(remainingElementsInGroup);
|
|
||||||
remainingElementsInGroupIncludingCurrent.add(0, clonedElement);
|
|
||||||
|
|
||||||
Group virtualPathRemaining = XtextFactory.eINSTANCE.createGroup();
|
|
||||||
setElementsOfCompoundElement(virtualPathRemaining, remainingElementsInGroup);
|
|
||||||
|
|
||||||
Group virtualPathRemainingPlusCurrent = XtextFactory.eINSTANCE.createGroup();
|
|
||||||
setElementsOfCompoundElement(virtualPathRemainingPlusCurrent, remainingElementsInGroupIncludingCurrent);
|
|
||||||
|
|
||||||
Alternatives virtualAlternatives = XtextFactory.eINSTANCE.createAlternatives();
|
|
||||||
setElementsOfCompoundElement(virtualAlternatives, Arrays.asList(virtualPathRemaining, virtualPathRemainingPlusCurrent));
|
|
||||||
|
|
||||||
// get Guard for virtual alternatives
|
|
||||||
HoistingGuard guard = findGuardForElementWithTrivialCardinality(virtualAlternatives, currentRule);
|
|
||||||
groupGuard.add(guard);
|
|
||||||
|
|
||||||
if (guard.hasTerminal()) {
|
|
||||||
groupGuard.setHasTerminal();
|
|
||||||
}
|
|
||||||
|
|
||||||
// following elements are included in alternative, no need to check further
|
|
||||||
break;
|
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("unknown cardinality: " + cardinality);
|
throw new IllegalArgumentException("unknown cardinality: " + cardinality);
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ public class TokenAnalysis {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private TokenAnalysisPaths getTokenPathsContext(AbstractElement last, TokenAnalysisPaths prefix) {
|
private TokenAnalysisPaths getTokenPathsContext(AbstractElement last, TokenAnalysisPaths prefix, boolean shortcutEndlessLoops) {
|
||||||
log.info("get context for: " + abstractElementToShortString(last));
|
log.info("get context for: " + abstractElementToShortString(last));
|
||||||
|
|
||||||
List<AbstractElement> context = getNextElementsInContext(last);
|
List<AbstractElement> context = getNextElementsInContext(last);
|
||||||
|
@ -160,13 +160,13 @@ public class TokenAnalysis {
|
||||||
|
|
||||||
for (AbstractElement element : context) {
|
for (AbstractElement element : context) {
|
||||||
TokenAnalysisPaths path = new TokenAnalysisPaths(prefix);
|
TokenAnalysisPaths path = new TokenAnalysisPaths(prefix);
|
||||||
path = getTokenPaths(element, path, false, false);
|
path = getTokenPaths(element, path, false, false, shortcutEndlessLoops);
|
||||||
if (!path.isDone() && element != null) {
|
if (!path.isDone() && element != null) {
|
||||||
if (path.getMinPosition() == currentPosition) {
|
if (path.getMinPosition() == currentPosition) {
|
||||||
throw new TokenAnalysisAbortedException("no progress in context analysis");
|
throw new TokenAnalysisAbortedException("no progress in context analysis");
|
||||||
}
|
}
|
||||||
|
|
||||||
path = getTokenPathsContext(element, path);
|
path = getTokenPathsContext(element, path, shortcutEndlessLoops);
|
||||||
}
|
}
|
||||||
if (path.isDone()) {
|
if (path.isDone()) {
|
||||||
result = result.merge(path);
|
result = result.merge(path);
|
||||||
|
@ -179,11 +179,11 @@ public class TokenAnalysis {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TokenAnalysisPaths getTokenPathsTrivial(Group path, TokenAnalysisPaths prefix) {
|
private TokenAnalysisPaths getTokenPathsTrivial(Group path, TokenAnalysisPaths prefix, boolean shortcutEndlessLoops) {
|
||||||
TokenAnalysisPaths result = new TokenAnalysisPaths(prefix);
|
TokenAnalysisPaths result = new TokenAnalysisPaths(prefix);
|
||||||
|
|
||||||
for(AbstractElement element : path.getElements()) {
|
for(AbstractElement element : path.getElements()) {
|
||||||
result = getTokenPaths(element, result, false, false);
|
result = getTokenPaths(element, result, false, false, shortcutEndlessLoops);
|
||||||
if (result.isDone()) {
|
if (result.isDone()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -191,16 +191,18 @@ public class TokenAnalysis {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
private TokenAnalysisPaths getTokenPathsTrivial(Alternatives path, TokenAnalysisPaths prefix) {
|
private TokenAnalysisPaths getTokenPathsTrivial(Alternatives path, TokenAnalysisPaths prefix, boolean shortcutEndlessLoops) {
|
||||||
TokenAnalysisPaths result = TokenAnalysisPaths.empty(prefix);
|
TokenAnalysisPaths result = TokenAnalysisPaths.empty(prefix);
|
||||||
|
|
||||||
for(AbstractElement element : path.getElements()) {
|
for(AbstractElement element : path.getElements()) {
|
||||||
result = result.merge(getTokenPaths(element, prefix, false, false));
|
result = result.merge(getTokenPaths(element, prefix, false, false, shortcutEndlessLoops));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
private TokenAnalysisPaths getTokenPathsTrivial(UnorderedGroup path, TokenAnalysisPaths prefix) {
|
private TokenAnalysisPaths getTokenPathsTrivial(UnorderedGroup path, TokenAnalysisPaths prefix, boolean shortcutEndlessLoops) {
|
||||||
|
log.info("unordered group");
|
||||||
|
|
||||||
TokenAnalysisPaths result;
|
TokenAnalysisPaths result;
|
||||||
TokenAnalysisPaths current;
|
TokenAnalysisPaths current;
|
||||||
|
|
||||||
|
@ -213,45 +215,65 @@ public class TokenAnalysis {
|
||||||
int currentPosition = result.getMinPosition();
|
int currentPosition = result.getMinPosition();
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
log.info("unordered group loop");
|
||||||
|
|
||||||
current = TokenAnalysisPaths.empty(result);
|
current = TokenAnalysisPaths.empty(result);
|
||||||
|
current.resetProgress();
|
||||||
for (AbstractElement element : path.getElements()) {
|
for (AbstractElement element : path.getElements()) {
|
||||||
current = current.merge(getTokenPaths(element, result, false, false));
|
log.info(abstractElementToShortString(element));
|
||||||
|
log.info(current.hasProgress() + " - " + current.getSize());
|
||||||
|
current = current.merge(getTokenPaths(element, result, false, false, shortcutEndlessLoops));
|
||||||
|
log.info(current.hasProgress() + " - " + current.getSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.resetProgress();
|
||||||
|
result = result.merge(current);
|
||||||
|
|
||||||
if (current.getMinPosition() == currentPosition) {
|
if (current.getMinPosition() == currentPosition) {
|
||||||
throw new TokenAnalysisAbortedException("no progress in loop");
|
// endless loop
|
||||||
|
// result will never be done since there is no progress to the shortest path
|
||||||
|
if (shortcutEndlessLoops) {
|
||||||
|
if (!result.hasProgress()) {
|
||||||
|
// no progress
|
||||||
|
// abort endless loop
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// there is still some progress done
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new TokenAnalysisAbortedException("no progress in loop");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result = result.merge(current);
|
|
||||||
} while(!current.isDone());
|
} while(!current.isDone());
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TokenAnalysisPaths getTokenPathsTrivial(AbstractElement path, TokenAnalysisPaths prefix) {
|
private TokenAnalysisPaths getTokenPathsTrivial(AbstractElement path, TokenAnalysisPaths prefix, boolean shortcutEndlessLoops) {
|
||||||
return new XtextSwitch<TokenAnalysisPaths>() {
|
return new XtextSwitch<TokenAnalysisPaths>() {
|
||||||
@Override
|
@Override
|
||||||
public TokenAnalysisPaths caseGroup(Group group) {
|
public TokenAnalysisPaths caseGroup(Group group) {
|
||||||
return getTokenPathsTrivial(group, prefix);
|
return getTokenPathsTrivial(group, prefix, shortcutEndlessLoops);
|
||||||
};
|
};
|
||||||
@Override
|
@Override
|
||||||
public TokenAnalysisPaths caseAlternatives(Alternatives alternatives) {
|
public TokenAnalysisPaths caseAlternatives(Alternatives alternatives) {
|
||||||
return getTokenPathsTrivial(alternatives, prefix);
|
return getTokenPathsTrivial(alternatives, prefix, shortcutEndlessLoops);
|
||||||
};
|
};
|
||||||
@Override
|
@Override
|
||||||
public TokenAnalysisPaths caseUnorderedGroup(UnorderedGroup unorderedGroup) {
|
public TokenAnalysisPaths caseUnorderedGroup(UnorderedGroup unorderedGroup) {
|
||||||
return getTokenPathsTrivial(unorderedGroup, prefix);
|
return getTokenPathsTrivial(unorderedGroup, prefix, shortcutEndlessLoops);
|
||||||
};
|
};
|
||||||
@Override
|
@Override
|
||||||
public TokenAnalysisPaths caseAssignment(Assignment assignment) {
|
public TokenAnalysisPaths caseAssignment(Assignment assignment) {
|
||||||
return getTokenPaths(assignment.getTerminal(), prefix, false, false);
|
return getTokenPaths(assignment.getTerminal(), prefix, false, false, shortcutEndlessLoops);
|
||||||
};
|
};
|
||||||
@Override
|
@Override
|
||||||
public TokenAnalysisPaths caseRuleCall(RuleCall call) {
|
public TokenAnalysisPaths caseRuleCall(RuleCall call) {
|
||||||
if (isParserRuleCall(call) ||
|
if (isParserRuleCall(call) ||
|
||||||
isEnumRuleCall(call)
|
isEnumRuleCall(call)
|
||||||
) {
|
) {
|
||||||
return getTokenPaths(call.getRule().getAlternatives(), prefix, false, false);
|
return getTokenPaths(call.getRule().getAlternatives(), prefix, false, false, shortcutEndlessLoops);
|
||||||
} else {
|
} else {
|
||||||
// go to default case
|
// go to default case
|
||||||
return null;
|
return null;
|
||||||
|
@ -274,7 +296,9 @@ public class TokenAnalysis {
|
||||||
}
|
}
|
||||||
|
|
||||||
// analyseContext implies needsLength
|
// analyseContext implies needsLength
|
||||||
private TokenAnalysisPaths getTokenPaths(AbstractElement path, TokenAnalysisPaths prefix, boolean analyseContext, boolean needsLength) {
|
private TokenAnalysisPaths getTokenPaths(
|
||||||
|
AbstractElement path, TokenAnalysisPaths prefix, boolean analyseContext, boolean needsLength, boolean shortcutEndlessLoops
|
||||||
|
) {
|
||||||
if (prefix.isDone()) {
|
if (prefix.isDone()) {
|
||||||
return prefix;
|
return prefix;
|
||||||
}
|
}
|
||||||
|
@ -290,7 +314,7 @@ public class TokenAnalysis {
|
||||||
|
|
||||||
if (isOptionalCardinality(path)) {
|
if (isOptionalCardinality(path)) {
|
||||||
if (analyseContext) {
|
if (analyseContext) {
|
||||||
result = getTokenPathsContext(path, prefix);
|
result = getTokenPathsContext(path, prefix, shortcutEndlessLoops);
|
||||||
} else if (needsLength) {
|
} else if (needsLength) {
|
||||||
throw new TokenAnalysisAbortedException("token expected but path is optional");
|
throw new TokenAnalysisAbortedException("token expected but path is optional");
|
||||||
} else {
|
} else {
|
||||||
|
@ -302,16 +326,17 @@ public class TokenAnalysis {
|
||||||
|
|
||||||
boolean loop = isMultipleCardinality(path);
|
boolean loop = isMultipleCardinality(path);
|
||||||
|
|
||||||
int currentPosition = result.getMinPosition();
|
int currentMinPosition = result.getMinPosition();
|
||||||
|
|
||||||
do {
|
do {
|
||||||
TokenAnalysisPaths tokenPaths = getTokenPathsTrivial(path, result);
|
result.resetProgress();
|
||||||
|
TokenAnalysisPaths tokenPaths = getTokenPathsTrivial(path, result, shortcutEndlessLoops);
|
||||||
|
|
||||||
if (tokenPaths.isDone()) {
|
if (tokenPaths.isDone()) {
|
||||||
result = result.merge(tokenPaths);
|
result = result.merge(tokenPaths);
|
||||||
break;
|
break;
|
||||||
} else if (analyseContext) {
|
} else if (analyseContext) {
|
||||||
tokenPaths = getTokenPathsContext(path, tokenPaths);
|
tokenPaths = getTokenPathsContext(path, tokenPaths, shortcutEndlessLoops);
|
||||||
result = result.merge(tokenPaths);
|
result = result.merge(tokenPaths);
|
||||||
} else if (needsLength) {
|
} else if (needsLength) {
|
||||||
throw new TokenAnalysisAbortedException("requested length not satisfyable");
|
throw new TokenAnalysisAbortedException("requested length not satisfyable");
|
||||||
|
@ -320,10 +345,23 @@ public class TokenAnalysis {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loop) {
|
if (loop) {
|
||||||
if (result.getMinPosition() == currentPosition) {
|
if (result.getMinPosition() == currentMinPosition) {
|
||||||
throw new TokenAnalysisAbortedException("no progress in loop");
|
// endless loop
|
||||||
|
// result will never be done since there is no progress to the shortest path
|
||||||
|
if (shortcutEndlessLoops) {
|
||||||
|
if (!result.hasProgress()) {
|
||||||
|
// no progress
|
||||||
|
// abort endless loop
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// there is still some progress done
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new TokenAnalysisAbortedException("no progress in loop");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
currentPosition = result.getMinPosition();
|
currentMinPosition = result.getMinPosition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (loop);
|
} while (loop);
|
||||||
|
@ -332,11 +370,11 @@ public class TokenAnalysis {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<List<Token>> getTokenPaths(AbstractElement path, List<Integer> indexes, boolean analyseContext) throws TokenAnalysisAbortedException {
|
private List<List<Token>> getTokenPaths(AbstractElement path, List<Integer> indexes, boolean analyseContext) throws TokenAnalysisAbortedException {
|
||||||
return getTokenPaths(path, new TokenAnalysisPaths(indexes), analyseContext, true).getTokenPaths();
|
return getTokenPaths(path, new TokenAnalysisPaths(indexes), analyseContext, true, false).getTokenPaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<List<Token>> getTokenPathsContextOnly(AbstractElement path, List<Integer> indexes) {
|
private List<List<Token>> getTokenPathsContextOnly(AbstractElement path, List<Integer> indexes) {
|
||||||
return getTokenPathsContext(path, new TokenAnalysisPaths(indexes)).getTokenPaths();
|
return getTokenPathsContext(path, new TokenAnalysisPaths(indexes), false).getTokenPaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean arePathsIdenticalSymbolic(AbstractElement path1, AbstractElement path2) throws SymbolicAnalysisFailedException {
|
private boolean arePathsIdenticalSymbolic(AbstractElement path1, AbstractElement path2) throws SymbolicAnalysisFailedException {
|
||||||
|
@ -364,8 +402,8 @@ public class TokenAnalysis {
|
||||||
List<Integer> range = range(0, i);
|
List<Integer> range = range(0, i);
|
||||||
|
|
||||||
// there shouldn't be a TokenAnalysisAbortedException if needsLength is false
|
// there shouldn't be a TokenAnalysisAbortedException if needsLength is false
|
||||||
tokenPaths1 = getTokenPaths(path1, new TokenAnalysisPaths(range), false, false);
|
tokenPaths1 = getTokenPaths(path1, new TokenAnalysisPaths(range), false, false, false);
|
||||||
tokenPaths2 = getTokenPaths(path2, new TokenAnalysisPaths(range), false, false);
|
tokenPaths2 = getTokenPaths(path2, new TokenAnalysisPaths(range), false, false, false);
|
||||||
|
|
||||||
Set<Set<Token>> tokenListSet1 = tokenPaths1.getTokenPaths().stream().map(HashSet::new).collect(Collectors.toSet());
|
Set<Set<Token>> tokenListSet1 = tokenPaths1.getTokenPaths().stream().map(HashSet::new).collect(Collectors.toSet());
|
||||||
Set<Set<Token>> tokenListSet2 = tokenPaths2.getTokenPaths().stream().map(HashSet::new).collect(Collectors.toSet());
|
Set<Set<Token>> tokenListSet2 = tokenPaths2.getTokenPaths().stream().map(HashSet::new).collect(Collectors.toSet());
|
||||||
|
@ -530,7 +568,7 @@ public class TokenAnalysis {
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<List<AbstractElement>> getAllPossiblePaths(AbstractElement path) {
|
public List<List<AbstractElement>> getAllPossiblePaths(AbstractElement path) {
|
||||||
return getTokenPaths(path, new TokenAnalysisPaths(range(0, config.getTokenLimit() + 1)), false, false)
|
return getTokenPaths(path, new TokenAnalysisPaths(range(0, config.getTokenLimit() + 1)), false, false, true)
|
||||||
.getTokenPaths()
|
.getTokenPaths()
|
||||||
.stream()
|
.stream()
|
||||||
.map(l -> l.stream()
|
.map(l -> l.stream()
|
||||||
|
|
|
@ -49,15 +49,19 @@ public class TokenAnalysisPath {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(AbstractElement element) {
|
public boolean add(AbstractElement element) {
|
||||||
if (isDone())
|
if (isDone())
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
if (remainingIndexes.get(0) <= 0) {
|
if (remainingIndexes.get(0) <= 0) {
|
||||||
path.add(Token.fromElement(element, position));
|
path.add(Token.fromElement(element, position));
|
||||||
remainingIndexes.remove(0);
|
remainingIndexes.remove(0);
|
||||||
|
shift();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
shift();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
shift();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Token> getTokenPath() {
|
public List<Token> getTokenPath() {
|
||||||
|
@ -70,4 +74,33 @@ public class TokenAnalysisPath {
|
||||||
path.stream().map(Token::toString).collect(Collectors.joining(", ")) + "),\n (" +
|
path.stream().map(Token::toString).collect(Collectors.joining(", ")) + "),\n (" +
|
||||||
remainingIndexes.stream().map(Object::toString).collect(Collectors.joining(", ")) + ")\n )";
|
remainingIndexes.stream().map(Object::toString).collect(Collectors.joining(", ")) + ")\n )";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((path == null) ? 0 : path.hashCode());
|
||||||
|
result = prime * result + position;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
TokenAnalysisPath other = (TokenAnalysisPath) obj;
|
||||||
|
if (path == null) {
|
||||||
|
if (other.path != null)
|
||||||
|
return false;
|
||||||
|
} else if (!path.equals(other.path))
|
||||||
|
return false;
|
||||||
|
if (position != other.position)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,19 +8,21 @@
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.pathAnalysis;
|
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.pathAnalysis;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.eclipse.xtext.AbstractElement;
|
import org.eclipse.xtext.AbstractElement;
|
||||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.token.Token;
|
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.token.Token;
|
||||||
|
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.utils.StreamUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author overflow - Initial contribution and API
|
* @author overflow - Initial contribution and APILinkedHashSet
|
||||||
*/
|
*/
|
||||||
public class TokenAnalysisPaths {
|
public class TokenAnalysisPaths {
|
||||||
private List<TokenAnalysisPath> tokenPaths = new LinkedList<>();
|
private LinkedHashSet<TokenAnalysisPath> tokenPaths = new LinkedHashSet<>();
|
||||||
private boolean isEmpty = false;
|
private boolean isEmpty = false;
|
||||||
|
private boolean hasProgress = false;
|
||||||
|
|
||||||
public List<List<Token>> getTokenPaths() {
|
public List<List<Token>> getTokenPaths() {
|
||||||
return tokenPaths.stream()
|
return tokenPaths.stream()
|
||||||
|
@ -36,23 +38,34 @@ public class TokenAnalysisPaths {
|
||||||
public TokenAnalysisPaths(TokenAnalysisPaths prefix) {
|
public TokenAnalysisPaths(TokenAnalysisPaths prefix) {
|
||||||
this.tokenPaths = prefix.tokenPaths.stream()
|
this.tokenPaths = prefix.tokenPaths.stream()
|
||||||
.map(TokenAnalysisPath::new)
|
.map(TokenAnalysisPath::new)
|
||||||
.collect(Collectors.toList());
|
.collect(StreamUtils.collectToLinkedHashSet());
|
||||||
|
this.hasProgress = prefix.hasProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDone() {
|
public boolean isDone() {
|
||||||
return !isEmpty && tokenPaths.stream().allMatch(TokenAnalysisPath::isDone);
|
return !isEmpty && tokenPaths.stream().allMatch(TokenAnalysisPath::isDone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasProgress() {
|
||||||
|
return hasProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetProgress() {
|
||||||
|
hasProgress = false;
|
||||||
|
}
|
||||||
|
|
||||||
public void add(AbstractElement element) {
|
public void add(AbstractElement element) {
|
||||||
tokenPaths.forEach(p -> p.add(element));
|
tokenPaths.forEach(p -> hasProgress = p.add(element) || hasProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TokenAnalysisPaths merge(TokenAnalysisPaths other) {
|
public TokenAnalysisPaths merge(TokenAnalysisPaths other) {
|
||||||
if (isEmpty) {
|
if (isEmpty) {
|
||||||
return other;
|
return other;
|
||||||
} else {
|
} else {
|
||||||
// TODO: implement hashCode and equals to check for duplicates right awaz
|
// set hasProgress if other has progress and progress is merged
|
||||||
this.tokenPaths.addAll(other.tokenPaths);
|
if (this.tokenPaths.addAll(other.tokenPaths)) {
|
||||||
|
this.hasProgress |= other.hasProgress;
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,6 +84,10 @@ public class TokenAnalysisPaths {
|
||||||
return tokenPaths.stream().map(TokenAnalysisPath::getPosition).mapToInt(Integer::intValue).max().getAsInt();
|
return tokenPaths.stream().map(TokenAnalysisPath::getPosition).mapToInt(Integer::intValue).max().getAsInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getSize() {
|
||||||
|
return tokenPaths.size();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
if (isEmpty) {
|
if (isEmpty) {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.eclipse.xtext.JavaAction;
|
||||||
import org.eclipse.xtext.Keyword;
|
import org.eclipse.xtext.Keyword;
|
||||||
import org.eclipse.xtext.ParserRule;
|
import org.eclipse.xtext.ParserRule;
|
||||||
import org.eclipse.xtext.RuleCall;
|
import org.eclipse.xtext.RuleCall;
|
||||||
|
import org.eclipse.xtext.UnorderedGroup;
|
||||||
import org.eclipse.xtext.util.XtextSwitch;
|
import org.eclipse.xtext.util.XtextSwitch;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
@ -66,6 +67,16 @@ public class DebugUtils {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@Override
|
@Override
|
||||||
|
public Boolean caseUnorderedGroup(UnorderedGroup object) {
|
||||||
|
builder.append("Unordered Group (\n");
|
||||||
|
object.getElements().forEach(e -> {
|
||||||
|
abstractElementToString(e, builder, indentation + 1, calledRules);
|
||||||
|
});
|
||||||
|
builder.append(indentationString);
|
||||||
|
builder.append(")");
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
@Override
|
||||||
public Boolean caseRuleCall(RuleCall object) {
|
public Boolean caseRuleCall(RuleCall object) {
|
||||||
AbstractRule rule = object.getRule();
|
AbstractRule rule = object.getRule();
|
||||||
if (rule instanceof ParserRule) {
|
if (rule instanceof ParserRule) {
|
||||||
|
@ -139,6 +150,13 @@ public class DebugUtils {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@Override
|
@Override
|
||||||
|
public Boolean caseUnorderedGroup(UnorderedGroup object) {
|
||||||
|
builder.append("UnorderedGroup (");
|
||||||
|
builder.append(object.getElements().size());
|
||||||
|
builder.append(")");
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
@Override
|
||||||
public Boolean caseRuleCall(RuleCall object) {
|
public Boolean caseRuleCall(RuleCall object) {
|
||||||
AbstractRule rule = object.getRule();
|
AbstractRule rule = object.getRule();
|
||||||
if (rule instanceof ParserRule) {
|
if (rule instanceof ParserRule) {
|
||||||
|
|
|
@ -9,14 +9,15 @@
|
||||||
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.utils;
|
package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.utils;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Spliterator;
|
import java.util.Spliterator;
|
||||||
import java.util.Spliterators;
|
import java.util.Spliterators;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collector;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
import com.google.common.base.Splitter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author overflow - Initial contribution and API
|
* @author overflow - Initial contribution and API
|
||||||
*/
|
*/
|
||||||
|
@ -43,4 +44,14 @@ public class StreamUtils {
|
||||||
public static <A> Stream<A> fromIterator(Iterator<A> iterator) {
|
public static <A> Stream<A> fromIterator(Iterator<A> iterator) {
|
||||||
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.NONNULL), false);
|
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.NONNULL), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> Collector<T, ?, LinkedHashSet<T>> collectToLinkedHashSet() {
|
||||||
|
return Collector.of(
|
||||||
|
(Supplier<LinkedHashSet<T>>) LinkedHashSet::new,
|
||||||
|
LinkedHashSet::add,
|
||||||
|
(a, b) -> {
|
||||||
|
a.addAll(b);
|
||||||
|
return a;
|
||||||
|
}, Collector.Characteristics.IDENTITY_FINISH);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.List;
|
||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.eclipse.core.runtime.OperationCanceledException;
|
import org.eclipse.core.runtime.OperationCanceledException;
|
||||||
|
@ -835,4 +836,16 @@ public class EcoreUtil2 extends EcoreUtil {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setEParent(EObject child, EObject parent) {
|
||||||
|
List<EReference> possibleReferences = parent.eClass().getEAllContainments().stream()
|
||||||
|
.filter(EReference::isContainment)
|
||||||
|
.filter(r -> r.getEReferenceType().isInstance(child))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (possibleReferences.size() != 1) {
|
||||||
|
throw new IllegalArgumentException("no obvious containment reference found");
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.eSet(possibleReferences.get(0), child);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue