mirror of
https://github.com/sigmasternchen/xtext-core
synced 2025-03-15 08:18:55 +00:00
added support for context analysis with optional cardinalities
unordered groups not yet working properly
This commit is contained in:
parent
d2c6a46071
commit
0a6b1b69c9
4 changed files with 112 additions and 28 deletions
|
@ -291,8 +291,8 @@ public class HoistingProcessorTest extends AbstractXtextTests {
|
||||||
assertEquals("((" + getSyntaxForKeywordToken("s", 1) + " || (p1)) && (" + getSyntaxForKeywordToken("a", 1) + " || (p0)))", guard.render());
|
assertEquals("((" + getSyntaxForKeywordToken("s", 1) + " || (p1)) && (" + getSyntaxForKeywordToken("a", 1) + " || (p0)))", guard.render());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = UnsupportedConstructException.class)
|
@Test
|
||||||
public void testCardinalityQuestionmarkWithoutContext_expectUnsupportedConstruct() throws Exception {
|
public void testCardinalityQuestionmarkWithoutContext_expectNoContextCheck() throws Exception {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
String model =
|
String model =
|
||||||
MODEL_PREAMBLE +
|
MODEL_PREAMBLE +
|
||||||
|
@ -303,9 +303,31 @@ public class HoistingProcessorTest extends AbstractXtextTests {
|
||||||
hoistingProcessor.init(grammar);
|
hoistingProcessor.init(grammar);
|
||||||
AbstractRule rule = getRule(grammar, "S");
|
AbstractRule rule = getRule(grammar, "S");
|
||||||
|
|
||||||
hoistingProcessor.findHoistingGuard(rule.getAlternatives());
|
HoistingGuard guard = hoistingProcessor.findHoistingGuard(rule.getAlternatives());
|
||||||
|
assertFalse(guard.isTrivial());
|
||||||
|
assertTrue(guard.hasTerminal());
|
||||||
|
assertEquals("(" + getSyntaxForKeywordToken("a", 1) + " || (p0))", guard.render());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCardinalityQuestionmarkWithExternalContext_expectContextCheck() throws Exception {
|
||||||
|
// @formatter:off
|
||||||
|
String model =
|
||||||
|
MODEL_PREAMBLE +
|
||||||
|
"S: A 'a' 'b';\n" +
|
||||||
|
"A: ($$ p0 $$?=> 'a')?;";
|
||||||
|
// @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("(" + getSyntaxForKeywordToken("a", 1) + " || (p0))", guard.render());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCardinalityStarWithContext() throws Exception {
|
public void testCardinalityStarWithContext() throws Exception {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
|
@ -341,8 +363,8 @@ public class HoistingProcessorTest extends AbstractXtextTests {
|
||||||
assertFalse(guard.hasTerminal());
|
assertFalse(guard.hasTerminal());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = UnsupportedConstructException.class)
|
@Test
|
||||||
public void testCardinalityStarWithoutContext_expectUnsupporedConstruct() throws Exception {
|
public void testCardinalityStarWithoutContext_expectContextCheck() throws Exception {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
String model =
|
String model =
|
||||||
MODEL_PREAMBLE +
|
MODEL_PREAMBLE +
|
||||||
|
@ -353,7 +375,10 @@ public class HoistingProcessorTest extends AbstractXtextTests {
|
||||||
hoistingProcessor.init(grammar);
|
hoistingProcessor.init(grammar);
|
||||||
AbstractRule rule = getRule(grammar, "S");
|
AbstractRule rule = getRule(grammar, "S");
|
||||||
|
|
||||||
hoistingProcessor.findHoistingGuard(rule.getAlternatives());
|
HoistingGuard guard = hoistingProcessor.findHoistingGuard(rule.getAlternatives());
|
||||||
|
assertFalse(guard.isTrivial());
|
||||||
|
assertTrue(guard.hasTerminal());
|
||||||
|
assertEquals("(" + getSyntaxForKeywordToken("a", 1) + " || (p0))", guard.render());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -410,7 +435,8 @@ 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 = UnsupportedConstructException.class)
|
// TODO: this is no longer unsupported
|
||||||
|
// @Test(expected = UnsupportedConstructException.class)
|
||||||
public void testUnorderedGroupWithEmptyPathsWithoutContext_expectUnsupportedConstruct() throws Exception {
|
public void testUnorderedGroupWithEmptyPathsWithoutContext_expectUnsupportedConstruct() throws Exception {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
String model =
|
String model =
|
||||||
|
@ -443,6 +469,7 @@ public class HoistingProcessorTest extends AbstractXtextTests {
|
||||||
assertEquals("((" + getSyntaxForKeywordToken("s", 1) + " || (p2)) && (" + getSyntaxForKeywordToken("a", 1) + " || (p0)) && (" + getSyntaxForKeywordToken("b", 1) + " || (p1)))", guard.render());
|
assertEquals("((" + getSyntaxForKeywordToken("s", 1) + " || (p2)) && (" + getSyntaxForKeywordToken("a", 1) + " || (p0)) && (" + getSyntaxForKeywordToken("b", 1) + " || (p1)))", guard.render());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this is no longer unsupported
|
||||||
@Test(expected = UnsupportedConstructException.class)
|
@Test(expected = UnsupportedConstructException.class)
|
||||||
public void testUnorderedGroupWithoutMandatoryContentWithoutContext_expectUnsupportedConstruct() throws Exception {
|
public void testUnorderedGroupWithoutMandatoryContentWithoutContext_expectUnsupportedConstruct() throws Exception {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
|
|
|
@ -70,7 +70,45 @@ public class HoistingProcessor {
|
||||||
analysis = new TokenAnalysis(config, grammar);
|
analysis = new TokenAnalysis(config, grammar);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: handling for TokenAnalysisAbortedException
|
private HoistingGuard findGuardForOptionalCardinalityWithoutContext(AbstractElement element, AbstractRule currentRule) {
|
||||||
|
HoistingGuard pathGuard = findGuardForElementWithTrivialCardinality(element, currentRule);
|
||||||
|
|
||||||
|
if (pathGuard.isTrivial()) {
|
||||||
|
// path can be skipped
|
||||||
|
return HoistingGuard.unguarded();
|
||||||
|
}
|
||||||
|
|
||||||
|
// set cardinality so the token analyse works
|
||||||
|
element = cloneAbstractElement(element);
|
||||||
|
if (isMultipleCardinality(element)) {
|
||||||
|
element.setCardinality("+");
|
||||||
|
} else {
|
||||||
|
// would be ? cardinality
|
||||||
|
element.setCardinality(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// identity analysis can be skipped
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new AlternativesGuard(
|
||||||
|
new PathGuard(
|
||||||
|
new AlternativeTokenSequenceGuard(
|
||||||
|
analysis.findMinimalPathDifference(element).stream()
|
||||||
|
.map(s -> s.stream()
|
||||||
|
.map(SingleTokenGuard::new)
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
)
|
||||||
|
.map(TokenSequenceGuard::new)
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
),
|
||||||
|
pathGuard
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch(TokenAnalysisAbortedException e) {
|
||||||
|
throw new TokenAnalysisAbortedException(e.getMessage(), e, currentRule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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");
|
||||||
|
|
||||||
|
@ -335,25 +373,7 @@ public class HoistingProcessor {
|
||||||
) {
|
) {
|
||||||
return findGuardForElementWithTrivialCardinality(element, currentRule);
|
return findGuardForElementWithTrivialCardinality(element, currentRule);
|
||||||
} else if (isOptionalCardinality(element)) {
|
} else if (isOptionalCardinality(element)) {
|
||||||
if (pathHasTokenOrAction(element)) {
|
return findGuardForOptionalCardinalityWithoutContext(element, currentRule);
|
||||||
if (!pathHasHoistablePredicate(currentRule.getAlternatives())) {
|
|
||||||
// unsupported construct doesn't matter since there is no
|
|
||||||
// hoistable predicate in the rule anyway.
|
|
||||||
return HoistingGuard.unguarded();
|
|
||||||
} else {
|
|
||||||
// there might be a token in this element
|
|
||||||
// no context accessible to construct guard
|
|
||||||
// this does only work when analyzing group
|
|
||||||
|
|
||||||
// TODO: maybe generate warning and return terminal()
|
|
||||||
throw new OptionalCardinalityWithoutContextException("optional cardinality is only supported in groups", currentRule);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// element with cardinality ? or * has no token or action
|
|
||||||
// -> the path is accessible whether or not this element is guarded
|
|
||||||
// -> we can assume it is unguarded
|
|
||||||
return HoistingGuard.unguarded();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("unknown cardinality: " + element.getCardinality());
|
throw new IllegalArgumentException("unknown cardinality: " + element.getCardinality());
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,11 @@ import java.util.stream.Collectors;
|
||||||
public class AlternativesGuard implements HoistingGuard {
|
public class AlternativesGuard implements HoistingGuard {
|
||||||
private List<PathGuard> paths;
|
private List<PathGuard> paths;
|
||||||
|
|
||||||
private AlternativesGuard(List<PathGuard> paths) {
|
public AlternativesGuard(PathGuard ...pathArray) {
|
||||||
|
this(Arrays.asList(pathArray));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlternativesGuard(List<PathGuard> paths) {
|
||||||
this.paths = PathGuard.collapse(paths);
|
this.paths = PathGuard.collapse(paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -300,6 +300,10 @@ public class TokenAnalysis {
|
||||||
return getTokenPaths(path, new TokenAnalysisPaths(indexes), analyseContext, true).getTokenPaths();
|
return getTokenPaths(path, new TokenAnalysisPaths(indexes), analyseContext, true).getTokenPaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<List<Token>> getTokenPathsContextOnly(AbstractElement path, List<Integer> indexes) {
|
||||||
|
return getTokenPathsContext(path, new TokenAnalysisPaths(indexes)).getTokenPaths();
|
||||||
|
}
|
||||||
|
|
||||||
private boolean arePathsIdenticalSymbolic(AbstractElement path1, AbstractElement path2) throws SymbolicAnalysisFailedException {
|
private boolean arePathsIdenticalSymbolic(AbstractElement path1, AbstractElement path2) throws SymbolicAnalysisFailedException {
|
||||||
// ignore symbolic analysis for the moment
|
// ignore symbolic analysis for the moment
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -403,6 +407,35 @@ public class TokenAnalysis {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<List<Token>> findMinimalPathDifference(AbstractElement element) throws TokenAnalysisAbortedException {
|
||||||
|
// this method is for finding the path differences between the
|
||||||
|
// element (with optional cardinality) and the context
|
||||||
|
|
||||||
|
// first dimension of result corresponds to the alternatives
|
||||||
|
// the second dimension are tokens of the alternatives
|
||||||
|
|
||||||
|
MutablePrimitiveWrapper<List<List<Token>>> result = new MutablePrimitiveWrapper<List<List<Token>>>(null);
|
||||||
|
|
||||||
|
tokenCombinations(indexList -> {
|
||||||
|
log.info("current index list: " + indexList);
|
||||||
|
|
||||||
|
// no context analysis
|
||||||
|
List<List<Token>> tokenListsForPath = getTokenPaths(element, indexList, false);
|
||||||
|
List<List<Token>> tokenListForContext = getTokenPathsContextOnly(element, indexList);
|
||||||
|
|
||||||
|
if (!tokenListsForPath.stream()
|
||||||
|
.anyMatch(tokenListForContext::contains)
|
||||||
|
) {
|
||||||
|
// context does not contain any token lists for path
|
||||||
|
result.set(tokenListsForPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.get() != null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.get();
|
||||||
|
}
|
||||||
|
|
||||||
public List<List<List<Token>>> findMinimalPathDifference(List<AbstractElement> paths) throws TokenAnalysisAbortedException {
|
public List<List<List<Token>>> findMinimalPathDifference(List<AbstractElement> paths) throws TokenAnalysisAbortedException {
|
||||||
// first dimension of result corresponds to the paths
|
// first dimension of result corresponds to the paths
|
||||||
// the second dimension are the alternatives of the path
|
// the second dimension are the alternatives of the path
|
||||||
|
|
Loading…
Reference in a new issue