From d86e9e64426ecb23119043b03dca984e989509be Mon Sep 17 00:00:00 2001 From: Moritz Eysholdt Date: Thu, 15 Oct 2015 17:51:43 +0200 Subject: [PATCH] hooked the FlattenedGrammarAccess before the serializer Signed-off-by: Moritz Eysholdt --- .../analysis/GrammarConstraintProvider.java | 17 +++- .../analysis/GrammarPDAProvider.java | 79 +++++++++++++++++-- .../xtext/serializer/analysis/IContext.java | 9 ++- .../analysis/SerializationContext.java | 39 ++++++++- .../xtext/xtext/FlattenedGrammarAccess.xtend | 20 ++++- .../org/eclipse/xtext/xtext/RuleFilter.java | 18 ++++- .../serializer/ContextPDAProviderTest.java | 19 +++++ .../ContextTypePDAProviderTest.java | 21 ++++- .../GrammarConstraintProviderTest.java | 13 +++ .../serializer/GrammarPDAProviderTest.xtend | 70 +++++++++++++--- 10 files changed, 274 insertions(+), 31 deletions(-) diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/GrammarConstraintProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/GrammarConstraintProvider.java index 3438ae00b..dee949b37 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/GrammarConstraintProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/GrammarConstraintProvider.java @@ -23,10 +23,12 @@ import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.AbstractRule; import org.eclipse.xtext.Action; import org.eclipse.xtext.Assignment; import org.eclipse.xtext.Grammar; import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.Parameter; import org.eclipse.xtext.ParserRule; import org.eclipse.xtext.grammaranalysis.impl.GrammarElementTitleSwitch; import org.eclipse.xtext.serializer.analysis.ISemanticSequencerNfaProvider.ISemState; @@ -451,16 +453,29 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { } List actions = Lists.newArrayList(); List rules = Lists.newArrayList(); + List params = Lists.newArrayList(); for (Action a : relevantActions) actions.add(context2Name.getUniqueActionName(a)); for (ParserRule a : relevantRules) rules.add(context2Name.getContextName(grammar, a)); - Collections.sort(rules); + for (IContext ctx : constraint.getContexts()) { + Set values = ctx.getParameterValues(); + if (values != null) + for (Parameter param : values) { + AbstractRule rule = GrammarUtil.containingRule(param); + params.add(rule.getName() + "$" + param.getName()); + } + Collections.sort(rules); + } String result = Joiner.on("_").join(rules); if (!actions.isEmpty()) { Collections.sort(actions); result += "_" + Joiner.on('_').join(actions); } + if (!params.isEmpty()) { + Collections.sort(params); + result += "_" + Joiner.on('_').join(params); + } return result; } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/GrammarPDAProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/GrammarPDAProvider.java index 5d00d6cbe..53fec6850 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/GrammarPDAProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/GrammarPDAProvider.java @@ -8,21 +8,30 @@ package org.eclipse.xtext.serializer.analysis; import java.util.Map; +import java.util.Set; import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.AbstractRule; import org.eclipse.xtext.Grammar; import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.Parameter; import org.eclipse.xtext.ParserRule; import org.eclipse.xtext.RuleCall; import org.eclipse.xtext.grammaranalysis.impl.CfgAdapter; +import org.eclipse.xtext.serializer.analysis.SerializationContext.ParameterValueContext; import org.eclipse.xtext.serializer.analysis.SerializationContext.RuleContext; import org.eclipse.xtext.serializer.analysis.SerializerPDA.SerializerPDAElementFactory; import org.eclipse.xtext.util.formallang.FollowerFunctionImpl; import org.eclipse.xtext.util.formallang.Pda; +import org.eclipse.xtext.util.formallang.PdaFactory; import org.eclipse.xtext.util.formallang.PdaUtil; import org.eclipse.xtext.util.formallang.Production; +import org.eclipse.xtext.xtext.FlattenedGrammarAccess; +import org.eclipse.xtext.xtext.OriginalElement; +import org.eclipse.xtext.xtext.RuleFilter; +import org.eclipse.xtext.xtext.RuleNames; +import org.eclipse.xtext.xtext.RuleWithParameterValues; -import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -66,29 +75,83 @@ public class GrammarPDAProvider implements IGrammarPDAProvider { } + protected static class ToOriginal implements PdaFactory { + + private final SerializerPDAElementFactory delegate; + + public ToOriginal(SerializerPDAElementFactory delegate) { + super(); + this.delegate = delegate; + } + + @Override + public SerializerPDA create(AbstractElement start, AbstractElement stop) { + return delegate.create(original(start), original(stop)); + } + + @Override + public ISerState createPop(SerializerPDA pda, AbstractElement token) { + return delegate.createPop(pda, original(token)); + } + + @Override + public ISerState createPush(SerializerPDA pda, AbstractElement token) { + return delegate.createPush(pda, original(token)); + } + + @Override + public ISerState createState(SerializerPDA nfa, AbstractElement token) { + return delegate.createState(nfa, original(token)); + } + + protected AbstractElement original(AbstractElement ele) { + return ele != null ? OriginalElement.findInEmfObject(ele).getOriginal() : null; + } + + @Override + public void setFollowers(SerializerPDA nfa, ISerState owner, Iterable followers) { + delegate.setFollowers(nfa, owner, followers); + } + } + @Inject protected SerializerPDAElementFactory factory; @Inject protected PdaUtil pdaUtil; - protected Pda createPDA(Grammar grammar, ParserRule entryRule) { - Preconditions.checkArgument(isValidRule(entryRule)); - SerializerParserRuleCfg cfg = new SerializerParserRuleCfg(grammar, entryRule); + protected IContext createContext(ParserRule original, Set params) { + IContext context = new RuleContext(null, original); + if (params != null && !params.isEmpty()) + context = new ParameterValueContext(context, params); + return context; + } + + protected Pda createPDA(Grammar flattened, ParserRule entryRule) { + SerializerParserRuleCfg cfg = new SerializerParserRuleCfg(flattened, entryRule); SerializerParserRuleFollowerFunction ff = new SerializerParserRuleFollowerFunction(cfg); - SerializerPDA pda = pdaUtil.create(cfg, ff, factory); + SerializerPDA pda = pdaUtil.create(cfg, ff, new ToOriginal(factory)); return pda; } @Override public Map> getGrammarPDAs(Grammar grammar) { + RuleNames names = RuleNames.getRuleNames(grammar, true); + RuleFilter filter = new RuleFilter(); + filter.setDiscardTerminalRules(true); + filter.setDiscardUnreachableRules(false); + filter.setDiscardRuleTypeRef(false); + Grammar flattened = new FlattenedGrammarAccess(names, filter).getFlattenedGrammar(); Map> result = Maps.newLinkedHashMap(); - for (ParserRule rule : GrammarUtil.allParserRules(grammar)) - if (isValidRule(rule)) { - IContext context = new RuleContext(null, rule); + for (ParserRule rule : GrammarUtil.allParserRules(flattened)) { + RuleWithParameterValues withParams = RuleWithParameterValues.findInEmfObject(rule); + AbstractRule original = withParams.getOriginal(); + if (original instanceof ParserRule && isValidRule((ParserRule) original)) { + IContext context = createContext((ParserRule) original, withParams.getParamValues()); Pda pda = createPDA(grammar, rule); result.put(context, pda); } + } return result; } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/IContext.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/IContext.java index 5eb372f4b..e41e54298 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/IContext.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/IContext.java @@ -7,9 +7,12 @@ *******************************************************************************/ package org.eclipse.xtext.serializer.analysis; +import java.util.Set; + import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.Action; +import org.eclipse.xtext.Parameter; import org.eclipse.xtext.ParserRule; /** @@ -17,11 +20,13 @@ import org.eclipse.xtext.ParserRule; */ public interface IContext extends Comparable { - IContext getParent(); + EObject getActionOrRule(); Action getAssignedAction(); - EObject getActionOrRule(); + Set getParameterValues(); + + IContext getParent(); ParserRule getParserRule(); diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SerializationContext.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SerializationContext.java index 9090e0aa9..4b9c9211b 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SerializationContext.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SerializationContext.java @@ -14,12 +14,14 @@ import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.AbstractElement; import org.eclipse.xtext.Action; import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.Parameter; import org.eclipse.xtext.ParserRule; import org.eclipse.xtext.RuleCall; import org.eclipse.xtext.util.Pair; @@ -28,6 +30,7 @@ import org.eclipse.xtext.util.Tuples; import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; @@ -57,6 +60,30 @@ public abstract class SerializationContext implements IContext { } } + public static class ParameterValueContext extends SerializationContext { + + private final Set parameters; + + public ParameterValueContext(IContext parent, Set parameters) { + super(parent); + this.parameters = ImmutableSet.copyOf(parameters); + } + + @Override + protected String toStringInternal() { + List names = Lists.newArrayList(); + for (Parameter p : parameters) + names.add(p.getName()); + return Joiner.on("_").join(names); + } + + @Override + public Set getParameterValues() { + return parameters; + } + + } + public static class RuleContext extends SerializationContext { private final ParserRule rule; @@ -227,6 +254,8 @@ public abstract class SerializationContext implements IContext { return false; if (!Objects.equal(getAssignedAction(), other.getAssignedAction())) return false; + if (!Objects.equal(getParameterValues(), other.getParameterValues())) + return false; if (!Objects.equal(getType(), other.getType())) return false; return true; @@ -258,10 +287,16 @@ public abstract class SerializationContext implements IContext { return parent != null ? parent.getType() : null; } + @Override + public Set getParameterValues() { + return parent != null ? parent.getParameterValues() : null; + } + @Override public int hashCode() { ParserRule rule = getParserRule(); Action action = getAssignedAction(); + Set parameterValues = getParameterValues(); EClass type = getType(); int result = 1; if (rule != null) @@ -269,7 +304,9 @@ public abstract class SerializationContext implements IContext { if (action != null) result *= action.hashCode() * 7; if (type != null) - result *= type.hashCode() * 31; + result *= type.hashCode() * 19; + if (parameterValues != null) + result *= parameterValues.hashCode() * 31; return result; } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/FlattenedGrammarAccess.xtend b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/FlattenedGrammarAccess.xtend index a7e86b7e8..cfcef5c36 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/FlattenedGrammarAccess.xtend +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/FlattenedGrammarAccess.xtend @@ -37,6 +37,7 @@ import org.eclipse.xtext.XtextPackage import org.eclipse.xtext.util.internal.EmfAdaptable import static extension org.eclipse.xtext.xtext.RuleWithParameterValues.* +import org.eclipse.xtext.TypeRef /** * @author Sebastian Zarnekow - Initial contribution and API @@ -52,7 +53,7 @@ class FlattenedGrammarAccess { var flattenedGrammar = copy(grammar) flattenedGrammar.name = grammar.name var origToCopy = Maps.newLinkedHashMap() - val copies = copyRuleStubs(names, origToCopy, filter.getRules(grammar)) + val copies = copyRuleStubs(names, origToCopy, filter.getRules(grammar), filter.discardRuleTypeRef) flattenedGrammar.rules += copies var calledFrom = copyRuleBodies(copies, origToCopy) flattenedGrammar.setHiddenTokens(grammar, origToCopy) @@ -221,11 +222,20 @@ class FlattenedGrammarAccess { } return calledFrom } + + def private copyTypeRef(TypeRef ref) { + if (ref === null) + return null + val copy = copy(ref) + copy.classifier = ref.classifier + return copy + } def private copyRuleStubs( RuleNames names, Map origToCopy, - List rulesToCopy + List rulesToCopy, + boolean discardTypeRef ) { val result = newArrayList for (AbstractRule rule : rulesToCopy) { @@ -238,6 +248,9 @@ class FlattenedGrammarAccess { copy.name = ruleName copy.fragment = rule.isFragment copy.wildcard = rule.isWildcard + if (!discardTypeRef) { + copy.type = copyTypeRef(rule.type) + } copy.attachTo(rule, origToCopy) result += copy } else { @@ -247,6 +260,9 @@ class FlattenedGrammarAccess { copy.name = names.getAntlrRuleName(rule, i) copy.fragment = rule.isFragment copy.wildcard = rule.isWildcard + if (!discardTypeRef) { + copy.type = copyTypeRef(rule.type) + } origToCopy.put(parameterValues, copy) parameterValues.attachToEmfObject(copy) result += copy diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/RuleFilter.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/RuleFilter.java index 02eef4c77..73f2b153a 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/RuleFilter.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/RuleFilter.java @@ -19,9 +19,11 @@ import org.eclipse.xtext.GrammarUtil; public class RuleFilter { private boolean discardUnreachableRules; - + private boolean discardTerminalRules; - + + private boolean discardRuleTypeRef = true; + public List getRules(Grammar grammar) { return GrammarUtil.allRules(grammar); } @@ -29,7 +31,7 @@ public class RuleFilter { public boolean isDiscardUnreachableRules() { return discardUnreachableRules; } - + public void setDiscardUnreachableRules(boolean discardUnreachableRules) { this.discardUnreachableRules = discardUnreachableRules; } @@ -41,5 +43,13 @@ public class RuleFilter { public void setDiscardTerminalRules(boolean discardTerminalRules) { this.discardTerminalRules = discardTerminalRules; } - + + public boolean isDiscardRuleTypeRef() { + return discardRuleTypeRef; + } + + public void setDiscardRuleTypeRef(boolean discardRuleTypeRef) { + this.discardRuleTypeRef = discardRuleTypeRef; + } + } diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/ContextPDAProviderTest.java b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/ContextPDAProviderTest.java index 2097e5129..02f582c13 100644 --- a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/ContextPDAProviderTest.java +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/ContextPDAProviderTest.java @@ -524,4 +524,23 @@ public class ContextPDAProviderTest extends AbstractXtextTests { expected.append(" opt=ID -> stop"); assertEquals(expected.toString(), actual); } + + @Test + public void testParameters() throws Exception { + String actual = getParserRule("M: 'kw1' s=S | 'kw2' s=S; S

:

v1=ID | v2=ID; "); + StringBuilder expected = new StringBuilder(); + expected.append("M:\n"); + expected.append(" start -> 'kw1', 'kw2'\n"); + expected.append(" 'kw1' -> (s=S|)\n"); + expected.append(" 'kw2' -> (|s=S)\n"); + expected.append(" (s=S|) -> stop\n"); + expected.append(" (|s=S) -> stop\n"); + expected.append("S_P:\n"); + expected.append(" start -> v1=ID\n"); + expected.append(" v1=ID -> stop\n"); + expected.append("S:\n"); + expected.append(" start -> v2=ID\n"); + expected.append(" v2=ID -> stop"); + assertEquals(expected.toString(), actual); + } } diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/ContextTypePDAProviderTest.java b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/ContextTypePDAProviderTest.java index 6735e073f..30ce48d48 100644 --- a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/ContextTypePDAProviderTest.java +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/ContextTypePDAProviderTest.java @@ -349,7 +349,7 @@ public class ContextTypePDAProviderTest extends AbstractXtextTests { expected.append(" name=ID -> < 'kw1'"); assertEquals(expected.toString(), actual); } + + @Test + public void testParameters() throws Exception { + String actual = getParserRule("M: 'kw1' s=S | 'kw2' s=S; S

:

v1=ID | v2=ID; "); + StringBuilder expected = new StringBuilder(); + expected.append("M_M:\n"); + expected.append(" start -> 'kw1', 'kw2'\n"); + expected.append(" 'kw1' -> (s=S|)\n"); + expected.append(" 'kw2' -> (|s=S)\n"); + expected.append(" (s=S|) -> stop\n"); + expected.append(" (|s=S) -> stop\n"); + expected.append("S_P_S:\n"); + expected.append(" start -> v1=ID\n"); + expected.append(" v1=ID -> stop\n"); + expected.append("S_S:\n"); + expected.append(" start -> v2=ID\n"); + expected.append(" v2=ID -> stop"); + assertEquals(expected.toString(), actual); + } } diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarConstraintProviderTest.java b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarConstraintProviderTest.java index d50156448..c06ebf916 100644 --- a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarConstraintProviderTest.java +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarConstraintProviderTest.java @@ -431,4 +431,17 @@ public class GrammarConstraintProviderTest extends AbstractXtextTests { expected.append(" F_Rule returns Rule: name=ID;"); assertEquals(expected.toString(), actual); } + + @Test + public void testParameters() throws Exception { + String actual = getParserRule("M: 'kw1' s=S | 'kw2' s=S; S

:

v1=ID | v2=ID; "); + StringBuilder expected = new StringBuilder(); + expected.append("M_M:\n"); + expected.append(" M_M returns M: (s=S | s=S);\n"); + expected.append("S_P_S:\n"); + expected.append(" S_S$P_S returns S: v1=ID;\n"); + expected.append("S_S:\n"); + expected.append(" S_S returns S: v2=ID;"); + assertEquals(expected.toString(), actual); + } } diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarPDAProviderTest.xtend b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarPDAProviderTest.xtend index 3c285d42e..1d255db82 100644 --- a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarPDAProviderTest.xtend +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarPDAProviderTest.xtend @@ -25,6 +25,9 @@ import org.junit.Assert import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith +import org.eclipse.xtext.util.formallang.NfaUtil +import org.eclipse.xtext.GrammarUtil +import org.eclipse.xtext.util.EmfFormatter /** * @author Moritz Eysholdt - Initial contribution and API @@ -165,6 +168,34 @@ class GrammarPDAProviderTest { ''' Assert.assertEquals(expected, actual) } + + @Test def void testUnassignedDatatypeRule() { + val actual = ''' + Rule: val=ID Called; + Called: 'kw1'; + '''.toPda + val expected = ''' + Rule: + start -> val=ID + Called -> stop + val=ID -> Called + ''' + Assert.assertEquals(expected, actual) + } + + @Test def void testUnassignedTerminalRule() { + val actual = ''' + Rule: val=ID Called; + terminal Called: 'kw1'; + '''.toPda + val expected = ''' + Rule: + start -> val=ID + Called -> stop + val=ID -> Called + ''' + Assert.assertEquals(expected, actual) + } @Test def void testUnassignedParserRuleCall() { val actual = ''' @@ -243,7 +274,7 @@ class GrammarPDAProviderTest { ''' Assert.assertEquals(expected, actual) } - + @Test def void testUnorderedGroup2() { val actual = ''' Rule: {Rule} ('a' & 'b'? & 'c'* & 'd'+); @@ -259,7 +290,7 @@ class GrammarPDAProviderTest { ''' Assert.assertEquals(expected, actual) } - + @Test def void testTwoAssignedEObjectRuleCalls() { val actual = ''' Rule: foo1=Sub foo2=Sub; Sub: id='id'; @@ -374,21 +405,25 @@ class GrammarPDAProviderTest { ''' Assert.assertEquals(expected, actual) } - - @Test @Ignore def void testParameter1() { + + @Test def void testParameter1() { val actual = ''' M: "kw1" s=S | "kw2" s=S; S

:

v1=ID | v2=ID; '''.toPda val expected = ''' - Greeting: - start -> '(', val=ID - '(' -> >>Greeting - ')' -> {Foo.child=} - < ')' - >>Greeting -> '(', val=ID - val=ID -> < < 'kw1', 'kw2' + 'kw1' -> (s=S|) + 'kw2' -> (|s=S) + (s=S|) -> stop + (|s=S) -> stop + S_P: + start -> v1=ID + v1=ID -> stop + S: + start -> v2=ID + v2=ID -> stop ''' Assert.assertEquals(expected, actual) } @@ -403,6 +438,8 @@ class GrammarPDAProviderTest { ''') validator.assertNoErrors(grammar) val pdas = pdaProvider.getGrammarPDAs(grammar) + pdas.values.forEach[assertNoLeakedGrammarElements(grammar, it)] + // pdas.forEach[p1, p2|p2.toDot(p1.name)] return pdas.keySet.sort.map [ ''' @@ -412,6 +449,15 @@ class GrammarPDAProviderTest { ].join } + def private void assertNoLeakedGrammarElements(Grammar grammar, Pda pda) { + for (ele : new NfaUtil().collect(pda).map[grammarElement].filterNull) { + val actual = GrammarUtil.getGrammar(ele) + if (actual !== grammar) { + Assert.fail("Element " + EmfFormatter.objPath(ele) + " leaked!") + } + } + } + def protected toDot(Pda pda, String name) { val test = Thread.currentThread.stackTrace.get(6).methodName new PdaToDot().draw(pda, "dot2/" + test + "_" + name + ".pdf", "-T pdf")