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:
overflowerror 2022-02-11 16:33:24 +01:00
parent 4f7760ee1b
commit e16e1c2ac3
4 changed files with 112 additions and 4 deletions

View file

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

View file

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

View file

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

View file

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