mirror of
https://github.com/sigmasternchen/xtext-core
synced 2025-03-15 08:18:55 +00:00
fixed problem with tail-recursion in context analysis
recursive (only tail recursion) causes problems because context of rule call causes endless recursion in getNextElementsInContext() example: S: {S} $$ p0 $$?=> 's' | $$ p1 $$?=> 's' s=S ; solution: added set of visited rule calls to parameter list, skip rule call if it was already handled by another recursion added test cases
This commit is contained in:
parent
9c6b8673d0
commit
9dfc0b4bfc
3 changed files with 61 additions and 5 deletions
|
@ -8,6 +8,8 @@
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.eclipse.xtext.xtext.generator.hoisting;
|
package org.eclipse.xtext.xtext.generator.hoisting;
|
||||||
|
|
||||||
|
import javax.management.RuntimeErrorException;
|
||||||
|
|
||||||
import org.eclipse.emf.ecore.EPackage;
|
import org.eclipse.emf.ecore.EPackage;
|
||||||
import org.eclipse.emf.ecore.xml.type.XMLTypePackage;
|
import org.eclipse.emf.ecore.xml.type.XMLTypePackage;
|
||||||
import org.eclipse.xtext.AbstractRule;
|
import org.eclipse.xtext.AbstractRule;
|
||||||
|
@ -1079,4 +1081,47 @@ public class HoistingProcessorTest extends AbstractXtextTests {
|
||||||
assertTrue(guard.hasTerminal());
|
assertTrue(guard.hasTerminal());
|
||||||
assertEquals("(((" + getSyntaxForKeywordToken("a", 2) + " && " + getSyntaxForEofToken(2) + ") || (p0)) && (" + getSyntaxForKeywordToken("b", 2) + " || (p1)))", guard.render());
|
assertEquals("(((" + getSyntaxForKeywordToken("a", 2) + " && " + getSyntaxForEofToken(2) + ") || (p0)) && (" + getSyntaxForKeywordToken("b", 2) + " || (p1)))", guard.render());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRecursiveContextWithIntermediate_expectCorrectResult() throws Exception {
|
||||||
|
// @formatter:off
|
||||||
|
String model =
|
||||||
|
MODEL_PREAMBLE +
|
||||||
|
"tokenLimit 4\n" +
|
||||||
|
"hoistingDebug\n" +
|
||||||
|
"S: a=A ;\n" +
|
||||||
|
"A: $$ p0 $$?=> 'a' \n" +
|
||||||
|
" | $$ p1 $$?=> 'a' s=S ;\n";
|
||||||
|
// @formatter:off
|
||||||
|
XtextResource resource = getResourceFromString(model);
|
||||||
|
Grammar grammar = ((Grammar) resource.getContents().get(0));
|
||||||
|
hoistingProcessor.init(grammar);
|
||||||
|
AbstractRule rule = getRule(grammar, "A");
|
||||||
|
|
||||||
|
HoistingGuard guard = hoistingProcessor.findHoistingGuard(rule.getAlternatives());
|
||||||
|
assertFalse(guard.isTrivial());
|
||||||
|
assertTrue(guard.hasTerminal());
|
||||||
|
assertEquals("((" + getSyntaxForEofToken(2) + " || (p0)) && (" + getSyntaxForKeywordToken("a", 2) + " || (p1)))", guard.render());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRecursiveContextNoIntermediate_expectCorrectResult() throws Exception {
|
||||||
|
// @formatter:off
|
||||||
|
String model =
|
||||||
|
MODEL_PREAMBLE +
|
||||||
|
"tokenLimit 4\n" +
|
||||||
|
"hoistingDebug\n" +
|
||||||
|
"S: $$ p0 $$?=> 'a' \n" +
|
||||||
|
" | $$ p1 $$?=> 'a' s=S ;\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(2) + " || (p0)) && (" + getSyntaxForKeywordToken("a", 2) + " || (p1)))", guard.render());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -329,7 +329,7 @@ public class HoistingProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
private HoistingGuard findGuardForUnorderedGroup(UnorderedGroup element, AbstractRule currentRule) {
|
private HoistingGuard findGuardForUnorderedGroup(UnorderedGroup element, AbstractRule currentRule) {
|
||||||
// Unordered group (A & B) is the same as (A | B)+ or (A | B)* (is A and B are optional)
|
// Unordered group (A & B) is the same as (A | B)+ or (A | B)* (if A and B are optional)
|
||||||
// but the cardinality doesn't matter for hoisting
|
// but the cardinality doesn't matter for hoisting
|
||||||
// if A and B are optional the guard for the alternatives need to check the context
|
// if A and B are optional the guard for the alternatives need to check the context
|
||||||
// if not the alternatives are actual alternatives
|
// if not the alternatives are actual alternatives
|
||||||
|
|
|
@ -16,6 +16,7 @@ import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
@ -61,6 +62,10 @@ public class TokenAnalysis {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<AbstractElement> getNextElementsInContext(AbstractElement last) {
|
private List<AbstractElement> getNextElementsInContext(AbstractElement last) {
|
||||||
|
return getNextElementsInContext(last, new HashSet<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<AbstractElement> getNextElementsInContext(AbstractElement last, Set<AbstractElement> visited) {
|
||||||
List<AbstractElement> result = new ArrayList<>();
|
List<AbstractElement> result = new ArrayList<>();
|
||||||
|
|
||||||
AbstractElement _last = last;
|
AbstractElement _last = last;
|
||||||
|
@ -95,7 +100,9 @@ public class TokenAnalysis {
|
||||||
if (compoundContainer == null) {
|
if (compoundContainer == null) {
|
||||||
// no container element; this is last element in a rule definition
|
// no container element; this is last element in a rule definition
|
||||||
AbstractRule rule = containingRule(last);
|
AbstractRule rule = containingRule(last);
|
||||||
List<RuleCall> calls = findAllRuleCalls(grammar, rule);
|
List<RuleCall> calls = findAllRuleCalls(grammar, rule).stream()
|
||||||
|
.filter(Predicate.not(visited::contains))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if (calls.isEmpty()) {
|
if (calls.isEmpty()) {
|
||||||
// has to be start rule
|
// has to be start rule
|
||||||
|
@ -104,7 +111,9 @@ public class TokenAnalysis {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (RuleCall call : calls) {
|
for (RuleCall call : calls) {
|
||||||
result.addAll(getNextElementsInContext(call));
|
Set<AbstractElement> _visited = new HashSet<>(visited);
|
||||||
|
_visited.add(call);
|
||||||
|
result.addAll(getNextElementsInContext(call, _visited));
|
||||||
}
|
}
|
||||||
} else if (compoundContainer instanceof Group) {
|
} else if (compoundContainer instanceof Group) {
|
||||||
List<AbstractElement> elements = compoundContainer.getElements();
|
List<AbstractElement> elements = compoundContainer.getElements();
|
||||||
|
@ -139,13 +148,13 @@ public class TokenAnalysis {
|
||||||
result.add(compoundContainer);
|
result.add(compoundContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.addAll(getNextElementsInContext(compoundContainer));
|
result.addAll(getNextElementsInContext(compoundContainer, visited));
|
||||||
}
|
}
|
||||||
} else if (compoundContainer instanceof UnorderedGroup) {
|
} else if (compoundContainer instanceof UnorderedGroup) {
|
||||||
result.addAll(compoundContainer.getElements().stream()
|
result.addAll(compoundContainer.getElements().stream()
|
||||||
.collect(Collectors.toList())
|
.collect(Collectors.toList())
|
||||||
);
|
);
|
||||||
result.addAll(getNextElementsInContext(compoundContainer));
|
result.addAll(getNextElementsInContext(compoundContainer, visited));
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("unknown compound element: " + container.eClass().getName());
|
throw new IllegalArgumentException("unknown compound element: " + container.eClass().getName());
|
||||||
}
|
}
|
||||||
|
@ -188,6 +197,7 @@ public class TokenAnalysis {
|
||||||
if (path.isDone()) {
|
if (path.isDone()) {
|
||||||
result = result.merge(path);
|
result = result.merge(path);
|
||||||
} else {
|
} else {
|
||||||
|
log.info("context analysis failed");
|
||||||
throw new TokenAnalysisAbortedException("context analysis failed");
|
throw new TokenAnalysisAbortedException("context analysis failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -495,6 +505,7 @@ public class TokenAnalysis {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (TokenAnalysisAbortedException e) {
|
} catch (TokenAnalysisAbortedException e) {
|
||||||
|
log.info("token combinations: " + e.getMessage());
|
||||||
// tokens exhausted; abort current prefix
|
// tokens exhausted; abort current prefix
|
||||||
// set limit for calling functions so this index is not checked again
|
// set limit for calling functions so this index is not checked again
|
||||||
limit.set(i);
|
limit.set(i);
|
||||||
|
|
Loading…
Reference in a new issue