mirror of
https://github.com/sigmasternchen/xtext-core
synced 2025-03-15 08:18:55 +00:00
fix for endless prefix problem
our algorithm can't deal with recursive prefixes. that's not a huge problem though since ANTLR uses LL(*) which can't deal with these constructs either. solution for the moment: if the minimal path difference analysis errors out check if more than 2 path are recursive. if so, throw an exception possible improvement for later: just consider predicates and not tokens - similar to ANTLR which falls back to LL(1)
This commit is contained in:
parent
4f7760ee1b
commit
e16e1c2ac3
4 changed files with 112 additions and 4 deletions
|
@ -21,6 +21,7 @@ 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.HoistingProcessor;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.EndlessPrefixException;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.TokenAnalysisAbortedException;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.guards.hoistingGuards.HoistingGuard;
|
||||
import org.junit.After;
|
||||
|
@ -1383,4 +1384,25 @@ public class HoistingProcessorTest extends AbstractXtextTests {
|
|||
")",
|
||||
guard.render());
|
||||
}
|
||||
|
||||
@Test(expected = EndlessPrefixException.class)
|
||||
public void testRecursionInNestedAlternatives_bugEndlessLoop_expectEndlessPrefixException() throws Exception {
|
||||
// @formatter:off
|
||||
String model =
|
||||
MODEL_PREAMBLE +
|
||||
"hoistingDebug\n" +
|
||||
"S: $$ p0 $$?=> a=A 's'\n" +
|
||||
" | $$ p1 $$?=> a=A 't';\n" +
|
||||
"A: $$ p2 $$?=> b=B 'u'\n" +
|
||||
" | $$ p3 $$?=> b=B 'v';\n" +
|
||||
"B: $$ p4 $$?=> 'a' 'b' 'c'\n" +
|
||||
" | $$ p5 $$?=> 'z' s=S;\n";
|
||||
// @formatter:off
|
||||
XtextResource resource = getResourceFromString(model);
|
||||
Grammar grammar = ((Grammar) resource.getContents().get(0));
|
||||
hoistingProcessor.init(grammar);
|
||||
AbstractRule rule = getRule(grammar, "S");
|
||||
|
||||
hoistingProcessor.findHoistingGuard(rule.getAlternatives());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ import org.eclipse.xtext.util.Tuples;
|
|||
import static org.eclipse.xtext.GrammarUtil.*;
|
||||
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.JavaCodeUtils;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.EndlessPrefixException;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.NestedIdenticalPathException;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.TokenAnalysisAbortedException;
|
||||
import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.exceptions.UnsupportedConstructException;
|
||||
|
@ -223,6 +224,37 @@ public class HoistingProcessor {
|
|||
return flattened;
|
||||
}
|
||||
|
||||
private boolean hasEndlessPath(AbstractElement path) {
|
||||
return hasEndlessPath(path, new ArrayList<>());
|
||||
}
|
||||
private boolean hasEndlessPath(AbstractElement path, final List<RuleCall> calls) {
|
||||
if (isMultipleCardinality(path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (path instanceof Assignment) {
|
||||
return hasEndlessPath(((Assignment) path).getTerminal(), calls);
|
||||
} else if (path instanceof CompoundElement) {
|
||||
return ((CompoundElement) path).getElements().stream().anyMatch(p -> hasEndlessPath(p, calls));
|
||||
} else if (path instanceof RuleCall) {
|
||||
if (calls.stream().anyMatch(c -> EcoreUtil.equals(c, path))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
List<RuleCall> _calls = new ArrayList<>(calls);
|
||||
_calls.add((RuleCall) path);
|
||||
return hasEndlessPath(((RuleCall) path).getRule().getAlternatives(), _calls);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasEndlessPrefix(List<AbstractElement> paths) {
|
||||
return paths.stream()
|
||||
.filter(this::hasEndlessPath)
|
||||
.count() >= 2; // only a problem if more than 2 paths are endless
|
||||
}
|
||||
|
||||
private HoistingGuard findGuardForAlternatives(CompoundElement alternatives, AbstractRule currentRule, boolean skipCache) {
|
||||
if (config.isDebug())
|
||||
log.info("find guard for alternative");
|
||||
|
@ -293,6 +325,14 @@ public class HoistingProcessor {
|
|||
).map(p -> new PathGuard(p.getFirst(), p.getSecond()))
|
||||
.collect(AlternativesGuard.collector());
|
||||
} catch(NestedIdenticalPathException e) {
|
||||
if (hasEndlessPrefix(paths)) {
|
||||
// endless paths in combination with a NestedIndenticalPathException means
|
||||
// there are probably endless prefixes in this element
|
||||
|
||||
// TODO: maybe only use predicates (analog to ANTLR) instead of error-ing out
|
||||
throw new EndlessPrefixException("endless prefix in more than two paths", currentRule);
|
||||
}
|
||||
|
||||
// nested identical paths
|
||||
// -> flatten paths to alternative and try again
|
||||
// this is very inefficient
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*******************************************************************************
|
||||
* 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.exceptions;
|
||||
|
||||
import org.eclipse.xtext.AbstractRule;
|
||||
|
||||
/**
|
||||
* @author overflow - Initial contribution and API
|
||||
*/
|
||||
public class EndlessPrefixException extends TokenAnalysisAbortedException {
|
||||
private static final long serialVersionUID = 2670032012156746113L;
|
||||
|
||||
public EndlessPrefixException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public EndlessPrefixException(String message, AbstractRule rule) {
|
||||
super(message, rule);
|
||||
}
|
||||
|
||||
public EndlessPrefixException(String message, Throwable cause, AbstractRule rule) {
|
||||
super(message, cause, rule);
|
||||
}
|
||||
|
||||
public EndlessPrefixException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public EndlessPrefixException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public EndlessPrefixException(Throwable cause, AbstractRule rule) {
|
||||
super(cause, rule);
|
||||
}
|
||||
|
||||
public EndlessPrefixException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
|
@ -104,8 +104,8 @@ public class GeneratorNodeProcessor {
|
|||
return this.contents;
|
||||
}
|
||||
|
||||
public char charAt(final int arg0) {
|
||||
return this.contents.charAt(arg0);
|
||||
public char charAt(final int index) {
|
||||
return this.contents.charAt(index);
|
||||
}
|
||||
|
||||
public IntStream chars() {
|
||||
|
@ -120,8 +120,8 @@ public class GeneratorNodeProcessor {
|
|||
return this.contents.length();
|
||||
}
|
||||
|
||||
public CharSequence subSequence(final int arg0, final int arg1) {
|
||||
return this.contents.subSequence(arg0, arg1);
|
||||
public CharSequence subSequence(final int start, final int end) {
|
||||
return this.contents.subSequence(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue