hooked the FlattenedGrammarAccess before the serializer

Signed-off-by: Moritz Eysholdt <moritz.eysholdt@itemis.de>
This commit is contained in:
Moritz Eysholdt 2015-10-15 17:51:43 +02:00
parent 4a27b82568
commit d86e9e6442
10 changed files with 274 additions and 31 deletions

View file

@ -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<String> actions = Lists.newArrayList();
List<String> rules = Lists.newArrayList();
List<String> 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<Parameter> 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;
}

View file

@ -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<SerializerPDA, ISerState, RuleCall, AbstractElement> {
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<ISerState> followers) {
delegate.setFollowers(nfa, owner, followers);
}
}
@Inject
protected SerializerPDAElementFactory factory;
@Inject
protected PdaUtil pdaUtil;
protected Pda<ISerState, RuleCall> createPDA(Grammar grammar, ParserRule entryRule) {
Preconditions.checkArgument(isValidRule(entryRule));
SerializerParserRuleCfg cfg = new SerializerParserRuleCfg(grammar, entryRule);
protected IContext createContext(ParserRule original, Set<Parameter> params) {
IContext context = new RuleContext(null, original);
if (params != null && !params.isEmpty())
context = new ParameterValueContext(context, params);
return context;
}
protected Pda<ISerState, RuleCall> 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<IContext, Pda<ISerState, RuleCall>> 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<IContext, Pda<ISerState, RuleCall>> 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<ISerState, RuleCall> pda = createPDA(grammar, rule);
result.put(context, pda);
}
}
return result;
}

View file

@ -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> {
IContext getParent();
EObject getActionOrRule();
Action getAssignedAction();
EObject getActionOrRule();
Set<Parameter> getParameterValues();
IContext getParent();
ParserRule getParserRule();

View file

@ -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<Parameter> parameters;
public ParameterValueContext(IContext parent, Set<Parameter> parameters) {
super(parent);
this.parameters = ImmutableSet.copyOf(parameters);
}
@Override
protected String toStringInternal() {
List<String> names = Lists.newArrayList();
for (Parameter p : parameters)
names.add(p.getName());
return Joiner.on("_").join(names);
}
@Override
public Set<Parameter> 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<Parameter> getParameterValues() {
return parent != null ? parent.getParameterValues() : null;
}
@Override
public int hashCode() {
ParserRule rule = getParserRule();
Action action = getAssignedAction();
Set<Parameter> 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;
}

View file

@ -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<RuleWithParameterValues, AbstractRule> origToCopy,
List<AbstractRule> rulesToCopy
List<AbstractRule> rulesToCopy,
boolean discardTypeRef
) {
val result = <AbstractRule>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

View file

@ -19,9 +19,11 @@ import org.eclipse.xtext.GrammarUtil;
public class RuleFilter {
private boolean discardUnreachableRules;
private boolean discardTerminalRules;
private boolean discardRuleTypeRef = true;
public List<AbstractRule> 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;
}
}

View file

@ -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<true> | 'kw2' s=S<false>; S <P>: <P> v1=ID | <!P> 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);
}
}

View file

@ -349,7 +349,7 @@ public class ContextTypePDAProviderTest extends AbstractXtextTests {
expected.append(" name=ID -> <<F");
assertEquals(expected.toString(), actual);
}
@Test
public void testActionFragment() throws Exception {
StringBuilder grammar = new StringBuilder();
@ -374,4 +374,23 @@ public class ContextTypePDAProviderTest extends AbstractXtextTests {
expected.append(" val1=ID -> 'kw1'");
assertEquals(expected.toString(), actual);
}
@Test
public void testParameters() throws Exception {
String actual = getParserRule("M: 'kw1' s=S<true> | 'kw2' s=S<false>; S <P>: <P> v1=ID | <!P> 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);
}
}

View file

@ -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<true> | 'kw2' s=S<false>; S <P>: <P> v1=ID | <!P> 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);
}
}

View file

@ -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<true> | "kw2" s=S<false>;
S <P>: <P> v1=ID | <!P> v2=ID;
'''.toPda
val expected = '''
Greeting:
start -> '(', val=ID
'(' -> >>Greeting
')' -> {Foo.child=}
<<Greeting -> ')'
>>Greeting -> '(', val=ID
val=ID -> <<Greeting, stop
{Foo.child=} -> <<Greeting, stop
M:
start -> '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<ISerState, RuleCall> 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<ISerState, RuleCall> pda, String name) {
val test = Thread.currentThread.stackTrace.get(6).methodName
new PdaToDot().draw(pda, "dot2/" + test + "_" + name + ".pdf", "-T pdf")