From e4f327c00b0cf1405c860dbdb18863bd6a780023 Mon Sep 17 00:00:00 2001 From: Moritz Eysholdt Date: Thu, 17 Nov 2011 17:02:19 +0100 Subject: [PATCH] [serializer] fixed bug 363509 - NPE during serialization The problem was that some state machines referred to EStructuralFeatures that were not member of the to-be-serialized EObject's EClass. --- .../xtext/util/formallang/NfaUtil.java | 10 +- .../util/formallang/PdaListFormatter.java | 11 +- .../xtext/util/formallang/PdaUtil.java | 168 +++++++++- .../xtext/util/formallang/Traverser.java | 15 + .../analysis/ContextPDAProvider.java | 129 ++++++++ .../analysis/ContextTypePDAProvider.java | 191 +++++++++++ .../analysis/IContextPDAProvider.java | 23 ++ ...ider.java => IContextTypePDAProvider.java} | 21 +- .../xtext/serializer/analysis/ISerState.java | 24 ++ .../serializer/analysis/SerializerPDA.java | 186 +++++++++++ .../analysis/SerializerPDAProvider.java | 303 ------------------ .../SyntacticSequencerPDAProvider.java | 7 +- .../serializer/ContextPDAProviderTest.java | 287 +++++++++++++++++ ...t.java => ContextTypePDAProviderTest.java} | 135 ++++++-- .../SyntacticSequencerPDAProviderTest.java | 5 +- 15 files changed, 1142 insertions(+), 373 deletions(-) create mode 100644 plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/Traverser.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ContextPDAProvider.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ContextTypePDAProvider.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/IContextPDAProvider.java rename plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/{ISerializerPDAProvider.java => IContextTypePDAProvider.java} (64%) create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ISerState.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SerializerPDA.java delete mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SerializerPDAProvider.java create mode 100644 tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/ContextPDAProviderTest.java rename tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/{SerializerPDAProviderTest.java => ContextTypePDAProviderTest.java} (66%) diff --git a/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/NfaUtil.java b/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/NfaUtil.java index 5b556649c..076f6a862 100644 --- a/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/NfaUtil.java +++ b/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/NfaUtil.java @@ -19,15 +19,15 @@ import java.util.Stack; import com.google.common.base.Function; import com.google.common.base.Functions; +import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.google.common.collect.Sets; -import com.google.inject.internal.Lists; -import com.google.inject.internal.Maps; -import com.google.inject.internal.Objects; /** * @author Moritz Eysholdt - Initial contribution and API @@ -75,9 +75,9 @@ public class NfaUtil { } public static class MappedComparator> implements Comparator { - private final Map sortBy; + protected final Map sortBy; - private MappedComparator(Map sortBy) { + public MappedComparator(Map sortBy) { this.sortBy = sortBy; } diff --git a/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/PdaListFormatter.java b/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/PdaListFormatter.java index 00eccc18d..4710fb1ff 100644 --- a/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/PdaListFormatter.java +++ b/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/PdaListFormatter.java @@ -12,7 +12,7 @@ import java.util.List; import com.google.common.base.Function; import com.google.common.base.Joiner; -import com.google.inject.internal.Lists; +import com.google.common.collect.Lists; /** * @author Moritz Eysholdt - Initial contribution and API @@ -29,6 +29,13 @@ public class PdaListFormatter implements Function stateFormatter = new ObjToStrFunction(); + protected boolean sortFollowers = false; + + public PdaListFormatter sortFollowers() { + this.sortFollowers = true; + return this; + } + public String apply(Pda pda) { return format(pda); } @@ -73,6 +80,8 @@ public class PdaListFormatter implements Function followers = Lists.newArrayList(); for (STATE f : followers2) followers.add(title(pda, f)); + if (sortFollowers) + Collections.sort(followers); return title(pda, state) + " -> " + Joiner.on(", ").join(followers); } diff --git a/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/PdaUtil.java b/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/PdaUtil.java index b32b6d648..de7013d39 100644 --- a/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/PdaUtil.java +++ b/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/PdaUtil.java @@ -8,27 +8,71 @@ package org.eclipse.xtext.util.formallang; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import org.eclipse.xtext.util.formallang.NfaUtil.MappedComparator; + import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import com.google.inject.Inject; import com.google.inject.internal.Join; -import com.google.inject.internal.Lists; -import com.google.inject.internal.Maps; /** * @author Moritz Eysholdt - Initial contribution and API */ public class PdaUtil { + public static class HashStack implements Iterable { + + protected LinkedList list = Lists.newLinkedList(); + protected Set set = Sets.newLinkedHashSet(); + + public boolean contains(Object value) { + return set.contains(value); + } + + public boolean isEmpty() { + return list.isEmpty(); + } + + public Iterator iterator() { + return list.iterator(); + } + + public T peek() { + return list.getLast(); + } + + public T pop() { + T result = list.getLast(); + list.removeLast(); + set.remove(result); + return result; + } + + public boolean push(T value) { + list.addLast(value); + return set.add(value); + } + + @Override + public String toString() { + return list.toString(); + } + } + protected static class IsPop implements Predicate { private final Pda pda; @@ -137,6 +181,37 @@ public class PdaUtil { } + protected static class TraversalItem { + protected R data; + protected Iterator followers; + protected S state; + + public TraversalItem(S state, Iterable followers, R previous) { + super(); + this.state = state; + this.followers = followers.iterator(); + this.data = previous; + } + + @Override + @SuppressWarnings("rawtypes") + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != getClass()) + return false; + return data.equals(((TraversalItem) obj).data); + } + + @Override + public int hashCode() { + return data.hashCode(); + } + + @Override + public String toString() { + return state.toString(); + } + } + @Inject protected NfaUtil nfaUtil = new NfaUtil(); @@ -151,17 +226,6 @@ public class PdaUtil { return create(cfg, ff, Functions. identity(), fact); } - public > D create(Cfg cfg, FollowerFunction ff, - Function element2token, PdaFactory fact) { - D pda = fact.create(null, null); - Map states = Maps.newHashMap(); - Map stops = Maps.newHashMap(); - Multimap callers = new CfgUtil().getCallers(cfg); - create(cfg, pda, pda.getStart(), cfg.getRoot(), ff.getStarts(cfg.getRoot()), true, ff, element2token, fact, - states, stops, callers); - return pda; - } - protected > void create(Cfg cfg, D pda, S state, E ele, Iterable followerElements, boolean canEnter, FollowerFunction ff, Function tokens, PdaFactory fact, Map states, Map stops, Multimap callers) { @@ -203,6 +267,17 @@ public class PdaUtil { fact.setFollowers(pda, state, followerStates); } + public > D create(Cfg cfg, FollowerFunction ff, + Function element2token, PdaFactory fact) { + D pda = fact.create(null, null); + Map states = Maps.newHashMap(); + Map stops = Maps.newHashMap(); + Multimap callers = new CfgUtil().getCallers(cfg); + create(cfg, pda, pda.getStart(), cfg.getRoot(), ff.getStarts(cfg.getRoot()), true, ff, element2token, fact, + states, stops, callers); + return pda; + } + protected StackItem createStack(Iterator stack) { if (stack.hasNext()) return new StackItem(stack, stack.next()); @@ -217,6 +292,67 @@ public class PdaUtil { return UNREACHABLE; } + public > D filterEdges(Pda pda, Traverser, S, R> traverser, + PdaFactory factory) { + HashStack> trace = new HashStack>(); + R previous = traverser.enter(pda, pda.getStart(), null); + if (previous == null) + return factory.create(pda.getStart(), pda.getStop()); + Map distances = new NfaUtil().distanceToFinalStateMap(pda); + MappedComparator distanceComp = new MappedComparator(distances); + trace.push(newItem(pda, distanceComp, pda.getStart(), previous)); + Multimap edges = LinkedHashMultimap.create(); + HashSet states = Sets.newHashSet(); + HashSet success = Sets.newHashSet(); + states.add(pda.getStart()); + states.add(pda.getStop()); + ROOT: while (!trace.isEmpty()) { + TraversalItem current = trace.peek(); + while (current.followers.hasNext()) { + S next = current.followers.next(); + R item = traverser.enter(pda, next, current.data); + if (item != null) { + if (next == pda.getStop() || success.contains(item)) { + S s = null; + for (TraversalItem i : trace) { + if (s != null) + edges.put(s, i.state); + states.add(i.state); + success.add(i.data); + s = i.state; + } + edges.put(s, next); + } else { + if (trace.push(newItem(pda, distanceComp, next, item))) + continue ROOT; + } + } + } + trace.pop(); + } + D result = factory.create(pda.getStart(), pda.getStop()); + Map old2new = Maps.newHashMap(); + old2new.put(pda.getStart(), result.getStart()); + old2new.put(pda.getStop(), result.getStop()); + for (S old : states) { + if (old == pda.getStart() || old == pda.getStop()) + continue; + else if (pda.getPop(old) != null) + old2new.put(old, factory.createPop(result, old)); + else if (pda.getPush(old) != null) + old2new.put(old, factory.createPush(result, old)); + else + old2new.put(old, factory.createState(result, old)); + } + for (S old : states) { + List followers = Lists.newArrayList(); + for (S f : edges.get(old)) + followers.add(old2new.get(f)); + factory.setFollowers(result, old2new.get(old), followers); + } + return result; + } + public Nfa filterUnambiguousPaths(Pda pda) { Map> followers = Maps.newHashMap(); Map distanceMap = nfaUtil.distanceToFinalStateMap(pda); @@ -260,6 +396,12 @@ public class PdaUtil { filterUnambiguousPaths(pda, follower, dist, followers); } + protected TraversalItem newItem(Pda pda, MappedComparator comp, S next, R item) { + List followers = Lists.newArrayList(pda.getFollowers(next)); + Collections.sort(followers, comp); + return new TraversalItem(next, followers, item); + } + public List shortestPathTo(Pda pda, Iterable starts, Iterator

stack, Predicate matches, Predicate canPass) { TraceItem trace = trace(pda, starts, stack, matches, canPass); diff --git a/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/Traverser.java b/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/Traverser.java new file mode 100644 index 000000000..3d1525611 --- /dev/null +++ b/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/Traverser.java @@ -0,0 +1,15 @@ +/******************************************************************************* + * Copyright (c) 2011 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.util.formallang; + +/** + * @author Moritz Eysholdt - Initial contribution and API + */ +public interface Traverser, S, R> { + public R enter(G graph, S state, R previous); +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ContextPDAProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ContextPDAProvider.java new file mode 100644 index 000000000..ef802269d --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ContextPDAProvider.java @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright (c) 2011 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.serializer.analysis; + +import java.util.Map; +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.AbstractRule; +import org.eclipse.xtext.Action; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.grammaranalysis.impl.CfgAdapter; +import org.eclipse.xtext.serializer.analysis.SerializerPDA.SerializerPDAElementFactory; +import org.eclipse.xtext.util.formallang.FollowerFunctionImpl; +import org.eclipse.xtext.util.formallang.NfaUtil; +import org.eclipse.xtext.util.formallang.Pda; +import org.eclipse.xtext.util.formallang.PdaUtil; +import org.eclipse.xtext.util.formallang.Production; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.inject.Singleton; + +/** + * @author Moritz Eysholdt - Initial contribution and API + */ +@Singleton +public class ContextPDAProvider implements IContextPDAProvider { + + protected static class SerializerCfg extends CfgAdapter { + protected EObject context; + + public SerializerCfg(EObject context) { + super(GrammarUtil.getGrammar(context)); + this.context = context; + } + + @Override + public AbstractElement getCall(AbstractElement ele) { + if (ele instanceof RuleCall && !GrammarUtil.isAssigned(ele) + && ((RuleCall) ele).getRule().getType().getClassifier() instanceof EClass) + return ((RuleCall) ele).getRule().getAlternatives(); + return null; + } + + @Override + public AbstractElement getRoot() { + if (context instanceof AbstractRule) + return ((AbstractRule) context).getAlternatives(); + if (context instanceof Action) + return GrammarUtil.containingRule(context).getAlternatives(); + return super.getRoot(); + } + } + + protected static class SerializerFollowerFunction extends FollowerFunctionImpl { + + protected Action actionCtx; + protected AbstractRule ruleCtx; + + public SerializerFollowerFunction(Production production, EObject context) { + super(production); + this.actionCtx = context instanceof Action ? (Action) context : null; + this.ruleCtx = context instanceof AbstractRule ? (AbstractRule) context : null; + } + + @Override + public Iterable getFollowers(AbstractElement element) { + Set result = Sets.newLinkedHashSet(); + for (AbstractElement ele : super.getFollowers(element)) + if (ele == null) { + if (isStop(element)) + result.add(null); + } else if (actionCtx == ele) + result.add(null); + else if (!GrammarUtil.isAssignedAction(ele)) + result.add(ele); + return result; + } + + @Override + public Iterable getStarts(AbstractElement root) { + Set result = Sets.newLinkedHashSet(); + for (Action act : GrammarUtil.containedActions(root)) + if (act.getFeature() != null) + result.add(act); + for (AbstractElement ele : super.getStarts(root)) + if (ele == null) { + if (isStop(root)) + result.add(null); + } else if (actionCtx == ele) { + result.add(null); + } else if (!GrammarUtil.isAssignedAction(ele)) + result.add(ele); + return result; + } + + protected boolean isStop(AbstractElement element) { + return actionCtx == null || (GrammarUtil.containingRule(actionCtx) != GrammarUtil.containingRule(element)); + } + + } + + protected Map> cache = Maps.newHashMap(); + + protected Pda createPDA(EObject context) { + SerializerCfg cfg = new SerializerCfg(context); + SerializerFollowerFunction ff = new SerializerFollowerFunction(cfg, context); + Pda pda = new PdaUtil().create(cfg, ff, new SerializerPDAElementFactory()); + new NfaUtil().removeOrphans(pda); + return pda; + } + + public Pda getContextPDA(EObject context) { + Pda result = cache.get(context); + if (result == null) + cache.put(context, result = createPDA(context)); + return result; + } + +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ContextTypePDAProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ContextTypePDAProvider.java new file mode 100644 index 000000000..3681e7d58 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ContextTypePDAProvider.java @@ -0,0 +1,191 @@ +/******************************************************************************* + * Copyright (c) 2011 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.serializer.analysis; + +import java.util.Map; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.Action; +import org.eclipse.xtext.Assignment; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.serializer.analysis.SerializerPDA.SerializerPDACloneFactory; +import org.eclipse.xtext.util.Pair; +import org.eclipse.xtext.util.Tuples; +import org.eclipse.xtext.util.formallang.Pda; +import org.eclipse.xtext.util.formallang.PdaUtil; +import org.eclipse.xtext.util.formallang.Traverser; + +import com.google.common.collect.Maps; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * @author Moritz Eysholdt - Initial contribution and API + */ +@Singleton +public class ContextTypePDAProvider implements IContextTypePDAProvider { + + protected static class FilterState { + final protected FilterState previous; + final protected StackItem stack; + final protected ISerState state; + final protected EClass type; + + public FilterState(FilterState previous, EClass type, StackItem stack, ISerState state) { + super(); + this.previous = previous; + this.type = type; + this.stack = stack; + this.state = state; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != getClass()) + return false; + FilterState other = (FilterState) obj; + if (type != other.type || state != other.state) + return false; + if (stack == null) + return other.stack == null; + return stack.equals(other.stack); + } + + @Override + public int hashCode() { + int r = state.getType().hashCode(); + if (state.getGrammarElement() != null) + r *= state.getGrammarElement().hashCode(); + if (type != null) + r *= type.hashCode() * 7; + if (stack != null) + r *= stack.rc.hashCode() * 13; + return r; + } + } + + protected static class StackItem { + final protected StackItem parent; + final protected RuleCall rc; + + public StackItem(StackItem parent, RuleCall rc) { + super(); + this.parent = parent; + this.rc = rc; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != getClass()) + return false; + StackItem current1 = this; + StackItem current2 = (StackItem) obj; + int count = 0; + while (true) { + if (current1 == null && current2 == null) + return true; + if (current1 == null || current2 == null) + return false; + if (current1.rc != current2.rc) + return false; + if (current1 != this && current1.rc == this.rc) + count++; + if (count > 0) + return true; + current1 = current1.parent; + current2 = current2.parent; + } + } + + @Override + public int hashCode() { + return rc.hashCode(); + } + } + + protected static class TypeFilter implements Traverser, ISerState, FilterState> { + final protected EClass type; + + public TypeFilter(EClass type) { + super(); + this.type = type; + } + + public FilterState enter(Pda pda, ISerState state, FilterState previous) { + switch (state.getType()) { + case ELEMENT: + if (previous.type == null) { + Assignment ass = GrammarUtil.containingAssignment(state.getGrammarElement()); + if (ass != null) { + EClassifier cls = GrammarUtil.containingRule(ass).getType().getClassifier(); + if (cls == type) + return new FilterState(previous, type, previous.stack, state); + return null; + } + if (state.getGrammarElement() instanceof Action) { + EClassifier cls = ((Action) state.getGrammarElement()).getType().getClassifier(); + if (cls == type) + return new FilterState(previous, type, previous.stack, state); + return null; + } + } else if (state.getGrammarElement() instanceof Action) + return null; + return new FilterState(previous, previous.type, previous.stack, state); + case POP: + if (previous.stack != null && state.getGrammarElement() == previous.stack.rc) + return new FilterState(previous, previous.type, previous.stack.parent, state); + return null; + case PUSH: + RuleCall rc = (RuleCall) state.getGrammarElement(); + return new FilterState(previous, previous.type, new StackItem(previous.stack, rc), state); + case START: + return new FilterState(previous, null, null, state); + case STOP: + if (previous.type == type && previous.stack == null) + return previous; + return null; + } + return null; + } + + } + + protected Map, Pda> cache = Maps.newHashMap(); + @Inject + protected IContextProvider contextProvider; + + @Inject + protected IContextPDAProvider pdaProvider; + + protected Pda createPDA(EObject context, EClass type) { + Pda contextPda = pdaProvider.getContextPDA(context); + Pda contextTypePda = null; + if (contextProvider.getTypesForContext(context).size() > 1) { + TypeFilter typeFilter = newTypeFilter(type); + SerializerPDACloneFactory factory = new SerializerPDACloneFactory(); + contextTypePda = new PdaUtil().filterEdges(contextPda, typeFilter, factory); + } else + contextTypePda = contextPda; + return contextTypePda; + } + + public Pda getContextTypePDA(EObject context, EClass type) { + Pair key = Tuples.create(context, type); + Pda result = cache.get(key); + if (result == null) + cache.put(key, result = createPDA(context, type)); + return result; + } + + protected TypeFilter newTypeFilter(EClass type) { + return new TypeFilter(type); + } +} \ No newline at end of file diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/IContextPDAProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/IContextPDAProvider.java new file mode 100644 index 000000000..39a97700b --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/IContextPDAProvider.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2011 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.serializer.analysis; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.util.formallang.Pda; + +import com.google.inject.ImplementedBy; + +/** + * @author Moritz Eysholdt - Initial contribution and API + */ +@ImplementedBy(ContextPDAProvider.class) +public interface IContextPDAProvider { + + Pda getContextPDA(EObject context); +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ISerializerPDAProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/IContextTypePDAProvider.java similarity index 64% rename from plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ISerializerPDAProvider.java rename to plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/IContextTypePDAProvider.java index 9ea38f94f..f5721b53d 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ISerializerPDAProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/IContextTypePDAProvider.java @@ -7,11 +7,8 @@ *******************************************************************************/ package org.eclipse.xtext.serializer.analysis; -import java.util.List; - import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; -import org.eclipse.xtext.AbstractElement; import org.eclipse.xtext.RuleCall; import org.eclipse.xtext.util.formallang.Pda; @@ -20,20 +17,8 @@ import com.google.inject.ImplementedBy; /** * @author Moritz Eysholdt - Initial contribution and API */ -@ImplementedBy(SerializerPDAProvider.class) -public interface ISerializerPDAProvider { +@ImplementedBy(ContextTypePDAProvider.class) +public interface IContextTypePDAProvider { - public interface ISerState { - List getFollowers(); - - AbstractElement getGrammarElement(); - - SerStateType getType(); - } - - public enum SerStateType { - ELEMENT, POP, PUSH, START, STOP; - } - - Pda getPDA(EObject context, EClass type); + Pda getContextTypePDA(EObject context, EClass type); } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ISerState.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ISerState.java new file mode 100644 index 000000000..d6e06b9c4 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ISerState.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2011 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.serializer.analysis; + +import java.util.List; + +import org.eclipse.xtext.AbstractElement; + +public interface ISerState { + public enum SerStateType { + ELEMENT, POP, PUSH, START, STOP; + } + + List getFollowers(); + + AbstractElement getGrammarElement(); + + SerStateType getType(); +} \ No newline at end of file diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SerializerPDA.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SerializerPDA.java new file mode 100644 index 000000000..7b6ffbf0a --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SerializerPDA.java @@ -0,0 +1,186 @@ +/******************************************************************************* + * Copyright (c) 2011 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.serializer.analysis; + +import java.util.Collections; +import java.util.List; + +import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.grammaranalysis.impl.GrammarElementTitleSwitch; +import org.eclipse.xtext.serializer.analysis.ISerState.SerStateType; +import org.eclipse.xtext.util.formallang.NfaGraphFormatter; +import org.eclipse.xtext.util.formallang.NfaUtil; +import org.eclipse.xtext.util.formallang.Pda; +import org.eclipse.xtext.util.formallang.PdaFactory; + +import com.google.common.collect.Lists; + +public class SerializerPDA implements Pda { + + public static class SerializerPDACloneFactory implements PdaFactory { + + public SerializerPDA create(ISerState start, ISerState stop) { + SerializerPDA.SerializerPDAState s1 = new SerializerPDAState(start.getGrammarElement(), SerStateType.START); + SerializerPDA.SerializerPDAState s2 = new SerializerPDAState(stop.getGrammarElement(), SerStateType.STOP); + return new SerializerPDA(s1, s2); + } + + public ISerState createPop(SerializerPDA pda, ISerState token) { + return new SerializerPDAState(token.getGrammarElement(), SerStateType.POP); + } + + public ISerState createPush(SerializerPDA pda, ISerState token) { + return new SerializerPDAState(token.getGrammarElement(), SerStateType.PUSH); + } + + public ISerState createState(SerializerPDA nfa, ISerState token) { + return new SerializerPDAState(token.getGrammarElement(), SerStateType.ELEMENT); + } + + public void setFollowers(SerializerPDA nfa, ISerState owner, Iterable followers) { + ((SerializerPDA.SerializerPDAState) owner).followers = Lists.newArrayList(followers); + } + } + + public static class SerializerPDAElementFactory implements + PdaFactory { + + public SerializerPDA create(AbstractElement start, AbstractElement stop) { + SerializerPDA.SerializerPDAState s1 = new SerializerPDAState(start, SerStateType.START); + SerializerPDA.SerializerPDAState s2 = new SerializerPDAState(stop, SerStateType.STOP); + return new SerializerPDA(s1, s2); + } + + public ISerState createPop(SerializerPDA pda, AbstractElement token) { + return new SerializerPDAState(token, SerStateType.POP); + } + + public ISerState createPush(SerializerPDA pda, AbstractElement token) { + return new SerializerPDAState(token, SerStateType.PUSH); + } + + public ISerState createState(SerializerPDA nfa, AbstractElement token) { + return new SerializerPDAState(token, SerStateType.ELEMENT); + } + + public void setFollowers(SerializerPDA nfa, ISerState owner, Iterable followers) { + ((SerializerPDA.SerializerPDAState) owner).followers = Lists.newArrayList(followers); + } + } + + protected static class SerializerPDAState implements ISerState { + protected List followers = Collections.emptyList(); + protected AbstractElement grammarElement; + protected SerStateType type; + + public SerializerPDAState(AbstractElement grammarElement, SerStateType type) { + super(); + this.type = type; + this.grammarElement = grammarElement; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != getClass()) + return false; + SerializerPDA.SerializerPDAState s = (SerializerPDA.SerializerPDAState) obj; + return grammarElement == s.grammarElement && type == s.type; + } + + public List getFollowers() { + return followers; + } + + public AbstractElement getGrammarElement() { + return grammarElement; + } + + public SerStateType getType() { + return type; + } + + @Override + public int hashCode() { + return (grammarElement != null ? grammarElement.hashCode() : 1) * type.hashCode(); + } + + @Override + public String toString() { + GrammarElementTitleSwitch fmt = new GrammarElementTitleSwitch().hideCardinality().showQualified() + .showAssignments(); + switch (type) { + case ELEMENT: + return fmt.apply(grammarElement); + case POP: + return "<<" + fmt.apply(grammarElement); + case PUSH: + return ">>" + fmt.apply(grammarElement); + case START: + return "start"; + case STOP: + return "stop"; + } + return ""; + } + } + + protected SerializerPDA.SerializerPDAState start; + protected SerializerPDA.SerializerPDAState stop; + + public SerializerPDA(SerializerPDA.SerializerPDAState start, SerializerPDA.SerializerPDAState stop) { + super(); + this.start = start; + this.stop = stop; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != getClass()) + return false; + return new NfaUtil().equalsIgnoreOrder(this, (SerializerPDA) obj); + } + + public Iterable getFollowers(ISerState state) { + return ((SerializerPDA.SerializerPDAState) state).followers; + } + + public RuleCall getPop(ISerState state) { + SerializerPDA.SerializerPDAState s = (SerializerPDA.SerializerPDAState) state; + return s.type == SerStateType.POP ? (RuleCall) s.grammarElement : null; + } + + public RuleCall getPush(ISerState state) { + SerializerPDA.SerializerPDAState s = (SerializerPDA.SerializerPDAState) state; + return s.type == SerStateType.PUSH ? (RuleCall) s.grammarElement : null; + } + + public ISerState getStart() { + return start; + } + + public ISerState getStop() { + return stop; + } + + @Override + public int hashCode() { + int r = 0; + if (start != null && start.followers != null) + for (ISerState s : start.followers) + if (s != null) + r += s.hashCode(); + return r; + } + + @Override + public String toString() { + return new NfaGraphFormatter().format(this); + } + +} \ No newline at end of file diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SerializerPDAProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SerializerPDAProvider.java deleted file mode 100644 index 2e4b43008..000000000 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SerializerPDAProvider.java +++ /dev/null @@ -1,303 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2011 itemis AG (http://www.itemis.eu) and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - *******************************************************************************/ -package org.eclipse.xtext.serializer.analysis; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -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.AbstractRule; -import org.eclipse.xtext.Action; -import org.eclipse.xtext.GrammarUtil; -import org.eclipse.xtext.RuleCall; -import org.eclipse.xtext.grammaranalysis.impl.CfgAdapter; -import org.eclipse.xtext.grammaranalysis.impl.GrammarElementTitleSwitch; -import org.eclipse.xtext.util.Pair; -import org.eclipse.xtext.util.Tuples; -import org.eclipse.xtext.util.formallang.FollowerFunctionImpl; -import org.eclipse.xtext.util.formallang.NfaGraphFormatter; -import org.eclipse.xtext.util.formallang.NfaUtil; -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 com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.google.inject.Singleton; - -/** - * @author Moritz Eysholdt - Initial contribution and API - */ -@Singleton -public class SerializerPDAProvider implements ISerializerPDAProvider { - - protected static class SerializerCfg extends CfgAdapter { - protected EObject context; - - public SerializerCfg(EObject context) { - super(GrammarUtil.getGrammar(context)); - this.context = context; - } - - @Override - public AbstractElement getRoot() { - if (context instanceof AbstractRule) - return ((AbstractRule) context).getAlternatives(); - if (context instanceof Action) - return GrammarUtil.containingRule(context).getAlternatives(); - return super.getRoot(); - } - - @Override - public AbstractElement getCall(AbstractElement ele) { - if (ele instanceof RuleCall && !GrammarUtil.isAssigned(ele) - && ((RuleCall) ele).getRule().getType().getClassifier() instanceof EClass) - return ((RuleCall) ele).getRule().getAlternatives(); - return null; - } - } - - protected static class SerializerFollowerFunction extends FollowerFunctionImpl { - - protected Action actionCtx; - protected AbstractRule ruleCtx; - protected EClass type; - - public SerializerFollowerFunction(Production production, EObject context, - EClass type) { - super(production); - this.actionCtx = context instanceof Action ? (Action) context : null; - this.ruleCtx = context instanceof AbstractRule ? (AbstractRule) context : null; - this.type = type; - } - - @Override - public Iterable getFollowers(AbstractElement element) { - Set result = Sets.newLinkedHashSet(); - for (AbstractElement ele : super.getFollowers(element)) - if (ele == null) { - if (actionCtx == null - || (GrammarUtil.containingRule(actionCtx) != GrammarUtil.containingRule(element))) - result.add(null); - } else if (actionCtx == ele) - result.add(null); - else if (ele instanceof Action) { - Action a = (Action) ele; - if (type != null && a.getFeature() == null && a.getType().getClassifier() == type) - result.add(ele); - } else if (type != null || !GrammarUtil.isAssigned(ele)) - result.add(ele); - return result; - } - - @Override - public Iterable getStarts(AbstractElement root) { - Set result = Sets.newLinkedHashSet(); - for (Action act : GrammarUtil.containedActions(root)) - if (type != null && act.getFeature() != null && act.getType().getClassifier() == type) - result.add(act); - for (AbstractElement ele : super.getStarts(root)) - if (ele == null) { - if (actionCtx == null - || (GrammarUtil.containingRule(actionCtx) != GrammarUtil.containingRule(root))) - result.add(null); - } else if (actionCtx == ele) { - result.add(null); - } else if (!GrammarUtil.isAssignedAction(ele) - && typeMatches(ele, Sets. newHashSet()) != Boolean.FALSE) - result.add(ele); - return result; - } - - protected Boolean typeMatches(AbstractElement ele, Set visited) { - if (!visited.add(ele)) - return null; - if (ele instanceof Action) - return type != null && ((Action) ele).getType().getClassifier() == type; - if (GrammarUtil.isAssigned(ele)) - return type != null && GrammarUtil.containingRule(ele).getType().getClassifier() == type; - else if (GrammarUtil.isEObjectRuleCall(ele)) - for (Action act : GrammarUtil.containedActions(((RuleCall) ele).getRule().getAlternatives())) - if (act.getFeature() != null && act.getType().getClassifier() == type) - return true; - boolean allFalse = true; - for (AbstractElement f : GrammarUtil.isEObjectRuleCall(ele) && !GrammarUtil.isAssigned(ele) ? super - .getStarts(((RuleCall) ele).getRule().getAlternatives()) : super.getFollowers(ele)) - if (f != null) { - Boolean r = typeMatches(f, visited); - if (r == Boolean.TRUE) - return true; - if (r == null) - allFalse = false; - } else if (type == null) - allFalse = false; - return allFalse ? false : null; - } - } - - protected static class SerializerPDA implements Pda { - - protected SerializerPDAState start; - protected SerializerPDAState stop; - - public SerializerPDA(SerializerPDAState start, SerializerPDAState stop) { - super(); - this.start = start; - this.stop = stop; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != getClass()) - return false; - return new NfaUtil().equalsIgnoreOrder(this, (SerializerPDA) obj); - } - - public Iterable getFollowers(SerializerPDAState state) { - return state.followers; - } - - public RuleCall getPop(SerializerPDAState state) { - return state.type == SerStateType.POP ? (RuleCall) state.grammarElement : null; - } - - public RuleCall getPush(SerializerPDAState state) { - return state.type == SerStateType.PUSH ? (RuleCall) state.grammarElement : null; - } - - public SerializerPDAState getStart() { - return start; - } - - public SerializerPDAState getStop() { - return stop; - } - - @Override - public int hashCode() { - int r = 0; - if (start != null && start.followers != null) - for (SerializerPDAState s : start.followers) - if (s != null) - r += s.hashCode(); - return r; - } - - @Override - public String toString() { - return new NfaGraphFormatter().format(this); - } - - } - - protected static class SerializerPDAFactory implements - PdaFactory { - - public SerializerPDA create(AbstractElement start, AbstractElement stop) { - SerializerPDAState s1 = new SerializerPDAState(start, SerStateType.START); - SerializerPDAState s2 = new SerializerPDAState(stop, SerStateType.STOP); - return new SerializerPDA(s1, s2); - } - - public SerializerPDAState createPop(SerializerPDA pda, AbstractElement token) { - return new SerializerPDAState(token, SerStateType.POP); - } - - public SerializerPDAState createPush(SerializerPDA pda, AbstractElement token) { - return new SerializerPDAState(token, SerStateType.PUSH); - } - - public SerializerPDAState createState(SerializerPDA nfa, AbstractElement token) { - return new SerializerPDAState(token, SerStateType.ELEMENT); - } - - public void setFollowers(SerializerPDA nfa, SerializerPDAState owner, Iterable followers) { - owner.followers = Lists.newArrayList(followers); - } - } - - protected static class SerializerPDAState implements ISerState { - protected List followers = Collections.emptyList(); - protected AbstractElement grammarElement; - protected SerStateType type; - - public SerializerPDAState(AbstractElement grammarElement, SerStateType type) { - super(); - this.type = type; - this.grammarElement = grammarElement; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != getClass()) - return false; - SerializerPDAState s = (SerializerPDAState) obj; - return grammarElement == s.grammarElement && type == s.type; - } - - public List getFollowers() { - return followers; - } - - public AbstractElement getGrammarElement() { - return grammarElement; - } - - public SerStateType getType() { - return type; - } - - @Override - public int hashCode() { - return (grammarElement != null ? grammarElement.hashCode() : 1) * type.hashCode(); - } - - @Override - public String toString() { - GrammarElementTitleSwitch fmt = new GrammarElementTitleSwitch().hideCardinality().showQualified(); - switch (type) { - case ELEMENT: - return fmt.apply(grammarElement); - case POP: - return "<<" + fmt.apply(grammarElement); - case PUSH: - return ">>" + fmt.apply(grammarElement); - case START: - return "start"; - case STOP: - return "stop"; - } - return ""; - } - } - - protected Map, Pda> cache = Maps.newHashMap(); - - protected Pda createPDA(EObject context, EClass type) { - SerializerCfg cfg = new SerializerCfg(context); - SerializerFollowerFunction ff = new SerializerFollowerFunction(cfg, context, type); - Pda pda = new PdaUtil().create(cfg, ff, new SerializerPDAFactory()); - new NfaUtil().removeOrphans(pda); - return pda; - } - - public Pda getPDA(EObject context, EClass type) { - Pair key = Tuples.create(context, type); - Pda result = cache.get(key); - if (result == null) - cache.put(key, result = createPDA(context, type)); - return result; - } - -} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SyntacticSequencerPDAProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SyntacticSequencerPDAProvider.java index cc5f24eb3..058882c82 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SyntacticSequencerPDAProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SyntacticSequencerPDAProvider.java @@ -31,8 +31,7 @@ import org.eclipse.xtext.serializer.analysis.GrammarAlias.AlternativeAlias; import org.eclipse.xtext.serializer.analysis.GrammarAlias.GrammarAliasFactory; import org.eclipse.xtext.serializer.analysis.GrammarAlias.GroupAlias; import org.eclipse.xtext.serializer.analysis.GrammarAlias.TokenAlias; -import org.eclipse.xtext.serializer.analysis.ISerializerPDAProvider.ISerState; -import org.eclipse.xtext.serializer.analysis.ISerializerPDAProvider.SerStateType; +import org.eclipse.xtext.serializer.analysis.ISerState.SerStateType; import org.eclipse.xtext.serializer.sequencer.RuleCallStack; import org.eclipse.xtext.util.Pair; import org.eclipse.xtext.util.Tuples; @@ -505,7 +504,7 @@ public class SyntacticSequencerPDAProvider implements ISyntacticSequencerPDAProv // protected SequencerPDAProvider pdaProvider = createSequencerPDAProvider(); @Inject - protected SerializerPDAProvider pdaProvider; + protected ContextTypePDAProvider pdaProvider; protected boolean canReachAbsorber(ISerState from, ISerState to, Set visited) { if (isMandatoryAbsorber(from.getGrammarElement()) || !visited.add(from)) @@ -602,7 +601,7 @@ public class SyntacticSequencerPDAProvider implements ISyntacticSequencerPDAProv if (result == null) { Map absorbers = Maps.newHashMap(); Map> emitters = Maps.newHashMap(); - Pda pda = pdaProvider.getPDA(context, type); + Pda pda = pdaProvider.getContextTypePDA(context, type); result = createAbsorberState(pda.getStart(), absorbers, emitters, context, type); cache.put(key, result); } 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 new file mode 100644 index 000000000..b33174cef --- /dev/null +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/ContextPDAProviderTest.java @@ -0,0 +1,287 @@ +/******************************************************************************* + * Copyright (c) 2011 itemis AG (http://www.itemis.eu) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + *******************************************************************************/ +package org.eclipse.xtext.serializer; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.XtextStandaloneSetup; +import org.eclipse.xtext.grammaranalysis.impl.GrammarElementTitleSwitch; +import org.eclipse.xtext.junit.AbstractXtextTests; +import org.eclipse.xtext.serializer.analysis.Context2NameFunction; +import org.eclipse.xtext.serializer.analysis.IContextPDAProvider; +import org.eclipse.xtext.serializer.analysis.IContextProvider; +import org.eclipse.xtext.serializer.analysis.ISerState; +import org.eclipse.xtext.util.Pair; +import org.eclipse.xtext.util.Tuples; +import org.eclipse.xtext.util.formallang.Pda; +import org.eclipse.xtext.util.formallang.PdaListFormatter; + +import com.google.common.base.Function; +import com.google.common.collect.Lists; +import com.google.inject.internal.Join; + +/** + * @author Moritz Eysholdt - Initial contribution and API + */ +public class ContextPDAProviderTest extends AbstractXtextTests { + private static class ToStr implements Function { + private Function ts = new GrammarElementTitleSwitch().showAssignments() + .hideCardinality().showQualified(); + + public String apply(ISerState from) { + switch (from.getType()) { + case START: + return "start"; + case STOP: + return "stop"; + default: + return ts.apply(from.getGrammarElement()); + } + } + } + + final static String HEADER = "grammar org.eclipse.xtext.serializer.SequenceParserPDAProviderTestLanguage" + + " with org.eclipse.xtext.common.Terminals " + + "generate sequenceParserPDAProviderTest \"http://www.eclipse.org/2010/tmf/xtext/SequenceParserPDAProvider\" "; + + private List> getContexts(Grammar grammar) { + final Context2NameFunction ctx2name = get(Context2NameFunction.class); + final IContextProvider contextProvider = get(IContextProvider.class); + List> result = Lists.newArrayList(); + for (EObject ctx : contextProvider.getAllContexts(grammar)) + result.add(Tuples.create(ctx, ctx2name.getContextName(ctx))); + Collections.sort(result, new Comparator>() { + public int compare(Pair o1, Pair o2) { + return o1.getSecond().compareTo(o2.getSecond()); + } + }); + return result; + } + + protected String getParserRule(String body) throws Exception { + Grammar grammar = (Grammar) getModel(HEADER + body); + List result = Lists.newArrayList(); + PdaListFormatter formatter = new PdaListFormatter(); + formatter.setStateFormatter(new ToStr()); + formatter.setStackitemFormatter(new GrammarElementTitleSwitch().showAssignments().hideCardinality()); + for (Pair ctx : getContexts(grammar)) { + result.add(ctx.getSecond() + ":"); + Pda pda = get(IContextPDAProvider.class).getContextPDA(ctx.getFirst()); + result.add(" " + formatter.format(pda).replace("\n", "\n ")); + } + return Join.join("\n", result); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + with(XtextStandaloneSetup.class); + } + + public void testKeywordAlternative() throws Exception { + String actual = getParserRule("Rule: a1=ID ('kw1' | 'kw2') a2=ID;"); + StringBuilder expected = new StringBuilder(); + expected.append("Rule:\n"); + expected.append(" start -> a1=ID\n"); + expected.append(" 'kw1' -> a2=ID\n"); + expected.append(" 'kw2' -> a2=ID\n"); + expected.append(" a1=ID -> 'kw1', 'kw2'\n"); + expected.append(" a2=ID -> stop"); + assertEquals(expected.toString(), actual); + } + + public void testDelegation1() throws Exception { + String actual = getParserRule("Rule: Delegate; Delegate: val=ID;"); + StringBuilder expected = new StringBuilder(); + expected.append("Delegate:\n"); + expected.append(" start -> val=ID\n"); + expected.append(" val=ID -> stop\n"); + expected.append("Rule:\n"); + expected.append(" start -> >>Delegate\n"); + expected.append(" < stop\n"); + expected.append(" >>Delegate -> val=ID\n"); + expected.append(" val=ID -> < 'del'\n"); + expected.append(" 'del' -> >>Delegate2\n"); + expected.append(" < bar=ID\n"); + expected.append(" >>Delegate2 -> val=ID\n"); + expected.append(" bar=ID -> stop\n"); + expected.append(" val=ID -> < val=ID\n"); + expected.append(" val=ID -> stop\n"); + expected.append("Foo:\n"); + expected.append(" start -> val2=ID\n"); + expected.append(" val2=ID -> stop\n"); + expected.append("Rule:\n"); + expected.append(" start -> >>Foo, >>Delegate1\n"); + expected.append(" 'del' -> >>Delegate2\n"); + expected.append(" < stop\n"); + expected.append(" < bar=ID\n"); + expected.append(" < stop\n"); + expected.append(" >>Delegate1 -> 'del'\n"); + expected.append(" >>Delegate2 -> val=ID\n"); + expected.append(" >>Foo -> val2=ID\n"); + expected.append(" bar=ID -> < < < {Act.val2=}\n"); + expected.append(" val3=ID -> stop\n"); + expected.append(" {Act.val2=} -> val3=ID\n"); + expected.append("Rule_Act_1:\n"); + expected.append(" start -> val1=ID\n"); + expected.append(" val1=ID -> stop"); + assertEquals(expected.toString(), actual); + } + + public void testActionOptional() throws Exception { + String actual = getParserRule("Rule: val1=ID ({Act.val2=current} val3=ID)?;"); + StringBuilder expected = new StringBuilder(); + expected.append("Rule:\n"); + expected.append(" start -> {Act.val2=}, val1=ID\n"); + expected.append(" val1=ID -> stop\n"); + expected.append(" val3=ID -> stop\n"); + expected.append(" {Act.val2=} -> val3=ID\n"); + expected.append("Rule_Act_1_0:\n"); + expected.append(" start -> val1=ID\n"); + expected.append(" val1=ID -> stop"); + assertEquals(expected.toString(), actual); + } + + public void testActionManyMandatory() throws Exception { + String actual = getParserRule("Rule: val1=ID ({Act.val2=current} val3=ID)+;"); + StringBuilder expected = new StringBuilder(); + expected.append("Rule:\n"); + expected.append(" start -> {Act.val2=}\n"); + expected.append(" val3=ID -> stop\n"); + expected.append(" {Act.val2=} -> val3=ID\n"); + expected.append("Rule_Act_1_0:\n"); + expected.append(" start -> {Act.val2=}, val1=ID\n"); + expected.append(" val1=ID -> stop\n"); + expected.append(" val3=ID -> stop\n"); + expected.append(" {Act.val2=} -> val3=ID"); + assertEquals(expected.toString(), actual); + } + + public void testActionManyOptional() throws Exception { + String actual = getParserRule("Rule: val1=ID ({Act.val2=current} val3=ID)*;"); + StringBuilder expected = new StringBuilder(); + expected.append("Rule:\n"); + expected.append(" start -> {Act.val2=}, val1=ID\n"); + expected.append(" val1=ID -> stop\n"); + expected.append(" val3=ID -> stop\n"); + expected.append(" {Act.val2=} -> val3=ID\n"); + expected.append("Rule_Act_1_0:\n"); + expected.append(" start -> {Act.val2=}, val1=ID\n"); + expected.append(" val1=ID -> stop\n"); + expected.append(" val3=ID -> stop\n"); + expected.append(" {Act.val2=} -> val3=ID"); + assertEquals(expected.toString(), actual); + } + + public void testActionTwoMandatory() throws Exception { + String actual = getParserRule("Rule: val1=ID {Act1.val2=current} val3=ID {Act2.val2=current} val4=ID;"); + StringBuilder expected = new StringBuilder(); + expected.append("Rule:\n"); + expected.append(" start -> {Act2.val2=}\n"); + expected.append(" val4=ID -> stop\n"); + expected.append(" {Act2.val2=} -> val4=ID\n"); + expected.append("Rule_Act1_1:\n"); + expected.append(" start -> val1=ID\n"); + expected.append(" val1=ID -> stop\n"); + expected.append("Rule_Act2_3:\n"); + expected.append(" start -> {Act1.val2=}\n"); + expected.append(" val3=ID -> stop\n"); + expected.append(" {Act1.val2=} -> val3=ID"); + assertEquals(expected.toString(), actual); + } + + public void testExpression1() throws Exception { + String actual = getParserRule("Exp: 'kw' Addit; Addit returns Exp: Prim ({Add.left=current} '+' right=Prim)*; Prim returns Exp: {Val} val=ID;"); + StringBuilder expected = new StringBuilder(); + expected.append("Addit:\n"); + expected.append(" start -> {Add.left=}, >>Prim\n"); + expected.append(" '+' -> right=Prim\n"); + expected.append(" < stop\n"); + expected.append(" >>Prim -> {Val}\n"); + expected.append(" right=Prim -> stop\n"); + expected.append(" val=ID -> < '+'\n"); + expected.append(" {Val} -> val=ID\n"); + expected.append("Addit_Add_1_0:\n"); + expected.append(" start -> {Add.left=}, >>Prim\n"); + expected.append(" '+' -> right=Prim\n"); + expected.append(" < stop\n"); + expected.append(" >>Prim -> {Val}\n"); + expected.append(" right=Prim -> stop\n"); + expected.append(" val=ID -> < '+'\n"); + expected.append(" {Val} -> val=ID\n"); + expected.append("Exp:\n"); + expected.append(" start -> 'kw'\n"); + expected.append(" '+' -> right=Prim\n"); + expected.append(" 'kw' -> >>Addit\n"); + expected.append(" < stop\n"); + expected.append(" < <>Addit -> {Add.left=}, >>Prim\n"); + expected.append(" >>Prim -> {Val}\n"); + expected.append(" right=Prim -> < < '+'\n"); + expected.append(" {Val} -> val=ID\n"); + expected.append("Prim:\n"); + expected.append(" start -> {Val}\n"); + expected.append(" val=ID -> stop\n"); + expected.append(" {Val} -> val=ID"); + assertEquals(expected.toString(), actual); + } + + public void testOptionalDelegate() throws Exception { + String actual = getParserRule("Rule: Mand | Opt; Mand: 'm' mand=ID; Opt: 'o' opt=ID?;"); + StringBuilder expected = new StringBuilder(); + expected.append("Mand:\n"); + expected.append(" start -> 'm'\n"); + expected.append(" 'm' -> mand=ID\n"); + expected.append(" mand=ID -> stop\n"); + expected.append("Opt:\n"); + expected.append(" start -> 'o'\n"); + expected.append(" 'o' -> opt=ID, stop\n"); + expected.append(" opt=ID -> stop\n"); + expected.append("Rule:\n"); + expected.append(" start -> >>Mand, >>Opt\n"); + expected.append(" 'm' -> mand=ID\n"); + expected.append(" 'o' -> opt=ID, < stop\n"); + expected.append(" < stop\n"); + expected.append(" >>Mand -> 'm'\n"); + expected.append(" >>Opt -> 'o'\n"); + expected.append(" mand=ID -> < < { private Function ts = new GrammarElementTitleSwitch().showAssignments() .hideCardinality().showQualified(); @@ -59,21 +56,6 @@ public class SerializerPDAProviderTest extends AbstractXtextTests { + " with org.eclipse.xtext.common.Terminals " + "generate sequenceParserPDAProviderTest \"http://www.eclipse.org/2010/tmf/xtext/SequenceParserPDAProvider\" "; - // public void drawGrammar(String path, Grammar grammar) { - // try { - // IContextProvider contexts = get(IContextProvider.class); - // SyntacticSequencerPDA2ExtendedDot seq2dot = get(SyntacticSequencerPDA2ExtendedDot.class); - // for (EObject ctx : contexts.getAllContexts(grammar)) - // for (EClass type : contexts.getTypesForContext(ctx)) - // seq2dot.draw( - // new Pair(ctx, type), - // path + "-" + new Context2NameFunction().apply(ctx) + "_" - // + (type == null ? "null" : type.getName()) + "-PDA.pdf", "-T pdf"); - // } catch (IOException e) { - // e.printStackTrace(); - // } - // } - private List> getContexts(Grammar grammar) { final Context2NameFunction ctx2name = get(Context2NameFunction.class); final IContextProvider contextProvider = get(IContextProvider.class); @@ -101,11 +83,14 @@ public class SerializerPDAProviderTest extends AbstractXtextTests { PdaListFormatter formatter = new PdaListFormatter(); formatter.setStateFormatter(new ToStr()); formatter.setStackitemFormatter(new GrammarElementTitleSwitch().showAssignments().hideCardinality()); + formatter.sortFollowers(); for (Triple ctx : getContexts(grammar)) { + // System.out.println(); String t = ctx.getFirst() == null ? "null" : ctx.getFirst().getName(); + // System.out.println(t + "_" + ctx.getThird() + ":"); result.add(t + "_" + ctx.getThird() + ":"); - Pda pda = get(ISerializerPDAProvider.class).getPDA(ctx.getSecond(), - ctx.getFirst()); + Pda pda = get(IContextTypePDAProvider.class).getContextTypePDA( + ctx.getSecond(), ctx.getFirst()); result.add(" " + formatter.format((Pda) pda).replace("\n", "\n ")); } return Join.join("\n", result); @@ -143,6 +128,35 @@ public class SerializerPDAProviderTest extends AbstractXtextTests { assertEquals(expected.toString(), actual); } + public void testLoop1() throws Exception { + String actual = getParserRule("Rule: ('x' x=ID*)*;"); + StringBuilder expected = new StringBuilder(); + expected.append("Rule_Rule:\n"); + expected.append(" start -> 'x'\n"); + expected.append(" 'x' -> 'x', stop, x=ID\n"); + expected.append(" x=ID -> 'x', stop, x=ID\n"); + expected.append("null_Rule:\n"); + expected.append(" start -> 'x', stop\n"); + expected.append(" 'x' -> 'x', stop"); + assertEquals(expected.toString(), actual); + } + + public void testLoop2() throws Exception { + String actual = getParserRule("Model: (('x' x+=ID*) | ('y' y+=ID*))*;"); + StringBuilder expected = new StringBuilder(); + expected.append("Model_Model:\n"); + expected.append(" start -> 'x', 'y'\n"); + expected.append(" 'x' -> 'x', 'y', stop, x+=ID\n"); + expected.append(" 'y' -> 'x', 'y', stop, y+=ID\n"); + expected.append(" x+=ID -> 'x', 'y', stop, x+=ID\n"); + expected.append(" y+=ID -> 'x', 'y', stop, y+=ID\n"); + expected.append("null_Model:\n"); + expected.append(" start -> 'x', 'y', stop\n"); + expected.append(" 'x' -> 'x', 'y', stop\n"); + expected.append(" 'y' -> 'x', 'y', stop"); + assertEquals(expected.toString(), actual); + } + public void testDelegation2() throws Exception { String actual = getParserRule("Rule: Foo | Delegate1; Delegate1: 'del' Delegate2 bar=ID; Delegate2: val=ID; Foo: val2=ID;"); StringBuilder expected = new StringBuilder(); @@ -224,7 +238,76 @@ public class SerializerPDAProviderTest extends AbstractXtextTests { expected.append(" {Val} -> val=ID"); assertEquals(expected.toString(), actual); } - + + public void testExpression2() throws Exception { + String actual = getParserRule("Addition returns Expr: Prim ({Add.left=current} '+' right=Prim)*; Prim returns Expr: {Val} name=ID | '(' Addition ')';"); + StringBuilder expected = new StringBuilder(); + expected.append("Add_Addition:\n"); + expected.append(" start -> >>Prim, {Add.left=}\n"); + expected.append(" '(' -> >>Addition\n"); + expected.append(" ')' -> < right=Prim\n"); + expected.append(" < ')'\n"); + expected.append(" < <>Addition -> >>Prim, {Add.left=}\n"); + expected.append(" >>Prim -> '('\n"); + expected.append(" right=Prim -> < '+'\n"); + expected.append("Add_Addition_Add_1_0:\n"); + expected.append(" start -> >>Prim, {Add.left=}\n"); + expected.append(" '(' -> >>Addition\n"); + expected.append(" ')' -> < right=Prim\n"); + expected.append(" < ')'\n"); + expected.append(" < <>Addition -> >>Prim, {Add.left=}\n"); + expected.append(" >>Prim -> '('\n"); + expected.append(" right=Prim -> < '+'\n"); + expected.append("Add_Prim:\n"); + expected.append(" start -> '('\n"); + expected.append(" '(' -> >>Addition\n"); + expected.append(" ')' -> < right=Prim\n"); + expected.append(" < ')'\n"); + expected.append(" < <>Addition -> >>Prim, {Add.left=}\n"); + expected.append(" >>Prim -> '('\n"); + expected.append(" right=Prim -> < '+'\n"); + expected.append("Val_Addition:\n"); + expected.append(" start -> >>Prim\n"); + expected.append(" '(' -> >>Addition\n"); + expected.append(" ')' -> < ')'\n"); + expected.append(" < <>Addition -> >>Prim\n"); + expected.append(" >>Prim -> '(', {Val}\n"); + expected.append(" name=ID -> < name=ID\n"); + expected.append("Val_Addition_Add_1_0:\n"); + expected.append(" start -> >>Prim\n"); + expected.append(" '(' -> >>Addition\n"); + expected.append(" ')' -> < ')'\n"); + expected.append(" < <>Addition -> >>Prim\n"); + expected.append(" >>Prim -> '(', {Val}\n"); + expected.append(" name=ID -> < name=ID\n"); + expected.append("Val_Prim:\n"); + expected.append(" start -> '(', {Val}\n"); + expected.append(" '(' -> >>Addition\n"); + expected.append(" ')' -> < ')'\n"); + expected.append(" < <>Addition -> >>Prim\n"); + expected.append(" >>Prim -> '(', {Val}\n"); + expected.append(" name=ID -> < name=ID"); + assertEquals(expected.toString(), actual); + } + public void testOptionalDelegate() throws Exception { String actual = getParserRule("Rule: Mand | Opt; Mand: 'm' mand=ID; Opt: 'o' opt=ID?;"); StringBuilder expected = new StringBuilder(); @@ -240,11 +323,11 @@ public class SerializerPDAProviderTest extends AbstractXtextTests { expected.append(" mand=ID -> < 'o'\n"); - expected.append(" 'o' -> opt=ID, stop\n"); + expected.append(" 'o' -> opt=ID\n"); expected.append(" opt=ID -> stop\n"); expected.append("Opt_Rule:\n"); expected.append(" start -> >>Opt\n"); - expected.append(" 'o' -> opt=ID, < opt=ID\n"); expected.append(" < stop\n"); expected.append(" >>Opt -> 'o'\n"); expected.append(" opt=ID -> < result = Lists.newArrayList(); for (Triple ctx : getContexts(grammar)) { String t = ctx.getFirst() == null ? "null" : ctx.getFirst().getName(); @@ -540,7 +540,6 @@ public class SyntacticSequencerPDAProviderTest extends AbstractXtextTests { String actual = getParserRule(grammar.toString()); StringBuilder expected = new StringBuilder(); expected.append("Optional_Optional:\n"); - expected.append(" start stop\n"); expected.append(" start val1=ID\n"); expected.append(" start val2=ID\n"); expected.append(" start val3=ID\n"); @@ -609,7 +608,7 @@ public class SyntacticSequencerPDAProviderTest extends AbstractXtextTests { String actual = getParserRule(grammar.toString()); StringBuilder expected = new StringBuilder(); expected.append("Model_Model:\n"); - expected.append(" start ('x' | 'y')* stop\n"); + expected.append(" start ('x' | 'y')+ stop\n"); expected.append(" start ('x'* 'y')+ y+=ID\n"); expected.append(" start ('y'* 'x')+ x+=ID\n"); expected.append(" x+=ID ('x' | 'y')* stop\n");