From ce58e99eaa071313f5cfcef44137e3820b967b48 Mon Sep 17 00:00:00 2001 From: Moritz Eysholdt Date: Tue, 6 Oct 2015 12:34:16 +0200 Subject: [PATCH] [serializer] re-implemented grammar constraint provider It's now based on the NFAs from SemanticSequencerNFAProvider. Therefore, it will now benefit from all NFA/PDA processing that's happening up the chain. Signed-off-by: Moritz Eysholdt --- .../util/formallang/NfaToProduction.java | 142 +- .../xtext/util/formallang/NfaUtil.java | 87 +- .../analysis/ActionFilterNFAProvider.java | 112 -- .../analysis/GrammarConstraintProvider.java | 1580 ++++------------- .../GrammarElementDeclarationOrder.java | 105 ++ .../analysis/IGrammarConstraintProvider.java | 170 +- .../ISemanticSequencerNfaProvider.java | 10 +- .../SemanticSequencerNfaProvider.java | 50 +- .../analysis/TypeFinderNFAProvider.java | 81 - .../serializer/sequencer/ContextFinder.java | 37 +- .../sequencer/GenericSemanticSequencer.java | 931 ---------- .../GenericSemanticSequencerTest.java | 190 -- ...rConstraintProviderAssignedActionTest.java | 20 +- ...mmarConstraintProviderAssignmentsTest.java | 253 --- ...GrammarConstraintProviderFeatureTest.xtend | 111 ++ .../GrammarConstraintProviderTest.java | 33 +- .../util/formallang/NfaToProductionTest.java | 2 +- 17 files changed, 911 insertions(+), 3003 deletions(-) delete mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ActionFilterNFAProvider.java create mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/GrammarElementDeclarationOrder.java delete mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/TypeFinderNFAProvider.java delete mode 100644 plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/sequencer/GenericSemanticSequencer.java delete mode 100644 tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GenericSemanticSequencerTest.java delete mode 100644 tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarConstraintProviderAssignmentsTest.java create mode 100644 tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarConstraintProviderFeatureTest.xtend diff --git a/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/NfaToProduction.java b/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/NfaToProduction.java index 8ec8e19ca..56e6f5bea 100644 --- a/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/NfaToProduction.java +++ b/plugins/org.eclipse.xtext.util/src/org/eclipse/xtext/util/formallang/NfaToProduction.java @@ -9,6 +9,7 @@ package org.eclipse.xtext.util.formallang; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -50,6 +51,12 @@ public class NfaToProduction { protected abstract int getElementCount(); + protected abstract void sort(Comparator> comparator); + + protected abstract T getFirstElement(); + + public abstract Collection> getChildren(); + public boolean isMany() { return many; } @@ -78,6 +85,23 @@ public class NfaToProduction { } + protected class ElementAliasComparator implements Comparator> { + + protected final Comparator delegate; + + public ElementAliasComparator(Comparator delegate) { + super(); + this.delegate = delegate; + } + + @Override + public int compare(AbstractElementAlias o1, AbstractElementAlias o2) { + T e1 = o1.getFirstElement(); + T e2 = o2.getFirstElement(); + return delegate.compare(e1, e2); + } + } + protected static class AliasGrammarProvider implements Production, TOKEN> { protected AbstractElementAlias root; @@ -145,6 +169,7 @@ public class NfaToProduction { children.add(child); } + @Override public Set> getChildren() { return children; } @@ -157,6 +182,20 @@ public class NfaToProduction { return result; } + @Override + protected void sort(Comparator> comparator) { + for (AbstractElementAlias child : this.children) + child.sort(comparator); + List> sorting = Lists.newArrayList(this.children); + Collections.sort(sorting, comparator); + this.children = Sets.newLinkedHashSet(sorting); + } + + @Override + protected T getFirstElement() { + return this.children.iterator().next().getFirstElement(); + } + } protected static class ElementAlias extends AbstractElementAlias { @@ -181,6 +220,20 @@ public class NfaToProduction { return 1; } + @Override + protected void sort(Comparator> comparator) { + } + + @Override + protected T getFirstElement() { + return element; + } + + @Override + public Collection> getChildren() { + return Collections.emptyList(); + } + } protected static class GroupAlias extends AbstractElementAlias { @@ -201,6 +254,7 @@ public class NfaToProduction { children.add(child); } + @Override public List> getChildren() { return children; } @@ -212,6 +266,18 @@ public class NfaToProduction { result += child.getElementCount(); return result; } + + @Override + protected void sort(Comparator> comparator) { + for (AbstractElementAlias child : this.children) + child.sort(comparator); + } + + @Override + protected T getFirstElement() { + return this.children.get(0).getFirstElement(); + } + } protected static class StateAlias { @@ -610,7 +676,8 @@ public class NfaToProduction { return result; } - protected boolean isPreferredSplitState(Pair> state1, Pair> state2) { + protected boolean isPreferredSplitState(Pair> state1, + Pair> state2) { int count1 = state1.getSecond().getElement().getElementCount(); int count2 = state2.getSecond().getElement().getElementCount(); if (count1 != count2) @@ -624,6 +691,11 @@ public class NfaToProduction { public ELEMENT nfaToGrammar(Nfa nfa, Function state2token, ProductionFactory grammarFactory) { + return nfaToGrammar(nfa, state2token, null, grammarFactory); + } + + public ELEMENT nfaToGrammar(Nfa nfa, Function state2token, + Comparator order, ProductionFactory grammarFactory) { StateAliasNfa states = createNfa(nfa, state2token); boolean changed = true; // System.out.println("init: " + Joiner.on(" ").join(getAllStates(start))); @@ -660,14 +732,80 @@ public class NfaToProduction { // e.printStackTrace(); // } // } - AliasGrammarProvider production = new AliasGrammarProvider(states.getStart().getElement()); + AbstractElementAlias root = states.getStart().getElement(); + if (excludeStartAndStop) { + root = removeStartAndStop(nfa, state2token, root); + } + normalize(root); + if (order != null) + root.sort(new ElementAliasComparator(order)); + AliasGrammarProvider production = new AliasGrammarProvider(root); return new ProductionUtil().clone(production, grammarFactory); } + protected AbstractElementAlias removeStartAndStop(Nfa nfa, + Function state2token, AbstractElementAlias root) { + if (excludeStartAndStop && root instanceof GroupAlias) { + GroupAlias group = (GroupAlias) root; + List> children = group.getChildren(); + if (children.size() > 1) { + AbstractElementAlias first = children.get(0); + AbstractElementAlias last = children.get(children.size() - 1); + if (first instanceof ElementAlias && last instanceof ElementAlias) { + TOKEN startToken = state2token.apply(nfa.getStart()); + TOKEN stopToken = state2token.apply(nfa.getStop()); + TOKEN firstToken = ((ElementAlias) first).getElement(); + TOKEN lastToken = ((ElementAlias) last).getElement(); + if (firstToken == startToken && lastToken == stopToken) { + if (children.size() == 3) { + return children.get(1); + } else { + children.remove(children.size() - 1); + children.remove(0); + return root; + } + } + } + } + } + return root; + } + + protected boolean collectMergeableOptions(boolean root, AbstractElementAlias alt, + List> result) { + boolean optional = alt.optional; + if ((root || !alt.isMany()) && alt instanceof AlternativeAlias) { + for (AbstractElementAlias child : ((AlternativeAlias) alt).getChildren()) + optional |= collectMergeableOptions(false, child, result); + } else { + result.add(alt); + alt.optional = false; + } + return optional; + } + + protected void normalize(AbstractElementAlias element) { + if (element instanceof AlternativeAlias) { + AlternativeAlias alt = (AlternativeAlias) element; + List> mergeable = Lists.newArrayList(); + alt.optional = collectMergeableOptions(true, element, mergeable); + alt.children = Sets.newLinkedHashSet(mergeable); + } + for (AbstractElementAlias child : element.getChildren()) + normalize(child); + } + public ELEMENT nfaToGrammar(Nfa nfa, ProductionFactory grammarFactory) { return nfaToGrammar(nfa, Functions. identity(), grammarFactory); } + private boolean excludeStartAndStop = false; + + public NfaToProduction excludeStartAndStop() { + excludeStartAndStop = true; + return this; + } + protected void splitState(StateAlias state) { if (state.getIncoming().size() >= state.getOutgoing().size()) { for (StateAlias in : Lists.newArrayList(state.getIncoming())) { 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 96e1a0d30..2b3f452b3 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 @@ -8,10 +8,10 @@ package org.eclipse.xtext.util.formallang; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -19,14 +19,13 @@ import java.util.Stack; import com.google.common.base.Function; import com.google.common.base.Functions; +import com.google.common.base.Joiner; 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; /** @@ -171,7 +170,7 @@ public class NfaUtil { public > boolean canReach(Nfa nfa, Predicate matcher) { return find(nfa, Collections.singleton(nfa.getStart()), matcher) != null; } - + public > boolean canReach(Nfa nfa, S state, Predicate matcher) { return find(nfa, Collections.singleton(state), matcher) != null; } @@ -321,13 +320,45 @@ public class NfaUtil { }); } - public boolean equalsIgnoreOrder(Nfa nfa1, Nfa nfa2, Function keyFunc) { + /** + * returns the sum of all edge-hashes. + * + * An edge-hash is computed as precedingStateKey.hashCode * (followingStateKey.hashCode + 1). Adding 1 ensures the + * direction of edges is considered. + * + * Disadvantage of this implementation: it calls keyFunc and key.hashCode twice on each state. + */ + public int hashCodeIgnoreOrder(Nfa nfa, Function keyFunc) { + int result = 0; + LinkedList remaining = new LinkedList(); + Set visited = Sets.newHashSet(); + remaining.add(nfa.getStart()); + while (!remaining.isEmpty()) { + S state = remaining.removeFirst(); + Object stateKey = keyFunc.apply(state); + int stateHash = stateKey == null ? 0 : stateKey.hashCode(); + for (S follower : nfa.getFollowers(state)) { + Object followerKey = keyFunc.apply(follower); + int followerHash = followerKey == null ? 0 : followerKey.hashCode(); + int edgeHash = stateHash * (followerHash + 1); + result += edgeHash; + if (visited.add(follower)) { + remaining.add(follower); + } + } + } + return result; + } + + public boolean equalsIgnoreOrder(Nfa nfa1, Nfa nfa2, Function keyFunc) { + if (nfa1 == nfa2) + return true; if (!Objects.equal(keyFunc.apply(nfa1.getStart()), keyFunc.apply(nfa2.getStart()))) return false; return equalsIgnoreOrder(nfa1, nfa2, nfa1.getStart(), nfa2.getStart(), keyFunc, Sets. newHashSet()); } - public boolean equalsIgnoreOrder(Nfa nfa1, Nfa nfa2, S s1, S s2, Function keyFunc, + public boolean equalsIgnoreOrder(Nfa nfa1, Nfa nfa2, S s1, S s2, Function keyFunc, Set visited) { if (!visited.add(s1)) return true; @@ -335,18 +366,50 @@ public class NfaUtil { Iterable followers2 = nfa1.getFollowers(s2); if (Iterables.size(followers1) != Iterables.size(followers2)) return false; - Multimap index = Multimaps.index(followers1, keyFunc); + Map index = Maps.newHashMap(); + for (S f1 : followers1) + if (index.put(keyFunc.apply(f1), f1) != null) + return false; for (S f : followers2) { Object key2 = keyFunc.apply(f); - Collection key1s = index.get(key2); - if (key1s.size() != 1) - return false; - if (!equalsIgnoreOrder(nfa1, nfa2, key1s.iterator().next(), f, keyFunc, visited)) + S key1s = index.get(key2); + if (!equalsIgnoreOrder(nfa1, nfa2, key1s, f, keyFunc, visited)) return false; } return true; } + public String identityString(Nfa nfa, Function idFunc) { + Map names = Maps.newHashMap(); + Map ids = Maps.newHashMap(); + for (S s : collect(nfa)) { + String name = idFunc.apply(s); + if (name == null) { + name = "(null)"; + } + if (s == nfa.getStart()) + name = "start:" + name; + else if (s == nfa.getStop()) + name = "stop:" + name; + names.put(name, s); + } + List sorted = Lists.newArrayList(names.keySet()); + Collections.sort(sorted); + for (int i = 0; i < sorted.size(); i++) + ids.put(names.get(sorted.get(i)), i); + List result = Lists.newArrayListWithExpectedSize(sorted.size()); + for (String name : sorted) { + S state = names.get(name); + Integer id = ids.get(state); + List followers = Lists.newArrayList(); + for (S f : nfa.getFollowers(state)) + followers.add(ids.get(f)); + Collections.sort(followers); + result.add(id + ":" + name + "->" + Joiner.on(",").join(followers)); + } + return Joiner.on("\n").join(result); + } + public Nfa filter(final Nfa nfa, final Predicate filter) { return new Nfa() { @@ -517,5 +580,5 @@ public class NfaUtil { public > Nfa sort(Nfa nfa, Map comparator) { return sort(nfa, new MappedComparator(comparator)); } - + } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ActionFilterNFAProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ActionFilterNFAProvider.java deleted file mode 100644 index ab0adf34c..000000000 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ActionFilterNFAProvider.java +++ /dev/null @@ -1,112 +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 org.eclipse.emf.common.util.TreeIterator; -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.CompoundElement; -import org.eclipse.xtext.GrammarUtil; -import org.eclipse.xtext.Keyword; -import org.eclipse.xtext.RuleCall; -import org.eclipse.xtext.TypeRef; -import org.eclipse.xtext.grammaranalysis.impl.AbstractCachingNFABuilder; -import org.eclipse.xtext.grammaranalysis.impl.AbstractNFAProvider; -import org.eclipse.xtext.grammaranalysis.impl.AbstractNFAState; -import org.eclipse.xtext.grammaranalysis.impl.AbstractNFATransition; -import org.eclipse.xtext.serializer.analysis.ActionFilterNFAProvider.ActionFilterState; -import org.eclipse.xtext.serializer.analysis.ActionFilterNFAProvider.ActionFilterTransition; - -import com.google.inject.Singleton; - -@Singleton -public class ActionFilterNFAProvider extends AbstractNFAProvider { - public static class ActionFilterState extends AbstractNFAState { - - public ActionFilterState(AbstractElement element, NFABuilder builder) { - super(element, builder); - } - - } - - public static class ActionFilterTransition extends AbstractNFATransition { - - public ActionFilterTransition(ActionFilterState source, ActionFilterState target, boolean ruleCall, - AbstractElement loopCenter) { - super(source, target, ruleCall, loopCenter); - } - } - - public static class ActionFilterNFABuilder extends - AbstractCachingNFABuilder { - - @Override - public ActionFilterState createState(AbstractElement ele) { - return new ActionFilterState(ele, this); - } - - @Override - protected ActionFilterTransition createTransition(ActionFilterState source, ActionFilterState target, - boolean isRuleCall, AbstractElement loopCenter) { - return new ActionFilterTransition(source, target, isRuleCall, loopCenter); - } - - @Override - public boolean filter(AbstractElement ele) { - - // never filter root elements - if (!(ele.eContainer() instanceof AbstractElement)) - return false; - - // filter unassigned keywords and token rule calls - if (!GrammarUtil.isAssigned(ele)) { - if (ele instanceof Keyword) - return true; - if (ele instanceof RuleCall) { - AbstractRule rule = ((RuleCall) ele).getRule(); - TypeRef ruleType = rule.getType(); - if (!(ruleType == null || ruleType.getClassifier() instanceof EClass)) - return true; - } - } - - // filter groups and alternatives, if they contain assigned actions - if (ele instanceof CompoundElement) { - TreeIterator ti = ele.eAllContents(); - while (ti.hasNext()) { - EObject obj = ti.next(); - if (obj instanceof Action && ((Action) obj).getFeature() != null) - return true; - } - } - - // don't filter, if there is a child or a sibling that is or contains an assigned action. - TreeIterator ti = ele.eContainer().eAllContents(); - while (ti.hasNext()) { - EObject obj = ti.next(); - if (obj instanceof Action && ((Action) obj).getFeature() != null) - return false; - } - return true; - } - - @Override - public NFADirection getDirection() { - return NFADirection.BACKWARD; - } - } - - @Override - protected NFABuilder createBuilder() { - return new ActionFilterNFABuilder(); - } - -} \ No newline at end of file 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 206a53a85..5e145b89d 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2010 itemis AG (http://www.itemis.eu) and others. + * Copyright (c) 2015 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 @@ -7,208 +7,96 @@ *******************************************************************************/ package org.eclipse.xtext.serializer.analysis; +import static org.eclipse.xtext.serializer.analysis.IGrammarConstraintProvider.ConstraintElementType.*; +import static org.eclipse.xtext.serializer.analysis.ISemanticSequencerNfaProvider.*; + +import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; -import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.AbstractElement; import org.eclipse.xtext.Action; -import org.eclipse.xtext.Alternatives; import org.eclipse.xtext.Assignment; -import org.eclipse.xtext.CompoundElement; -import org.eclipse.xtext.CrossReference; -import org.eclipse.xtext.EnumRule; import org.eclipse.xtext.Grammar; import org.eclipse.xtext.GrammarUtil; -import org.eclipse.xtext.Group; -import org.eclipse.xtext.Keyword; import org.eclipse.xtext.ParserRule; import org.eclipse.xtext.RuleCall; -import org.eclipse.xtext.TerminalRule; -import org.eclipse.xtext.TypeRef; -import org.eclipse.xtext.UnorderedGroup; import org.eclipse.xtext.grammaranalysis.impl.GrammarElementTitleSwitch; -import org.eclipse.xtext.serializer.analysis.ActionFilterNFAProvider.ActionFilterState; -import org.eclipse.xtext.serializer.analysis.ActionFilterNFAProvider.ActionFilterTransition; -import org.eclipse.xtext.util.EmfFormatter; +import org.eclipse.xtext.serializer.analysis.ISemanticSequencerNfaProvider.ISemState; import org.eclipse.xtext.util.Pair; import org.eclipse.xtext.util.Tuples; +import org.eclipse.xtext.util.formallang.Nfa; +import org.eclipse.xtext.util.formallang.NfaToProduction; +import org.eclipse.xtext.util.formallang.NfaUtil; +import org.eclipse.xtext.util.formallang.ProductionFactory; import org.eclipse.xtext.util.formallang.ProductionFormatter; -import org.eclipse.xtext.xtext.RuleNames; import com.google.common.base.Function; import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.inject.Inject; -import com.google.inject.Singleton; /** * @author Moritz Eysholdt - Initial contribution and API */ -@Singleton -@SuppressWarnings("deprecation") public class GrammarConstraintProvider implements IGrammarConstraintProvider { - protected abstract static class AbstractConstraintContext implements IConstraintContext { + protected static class Constraint implements IGrammarConstraintProvider.IConstraint { - protected List constraints = Lists.newArrayList(); + private IConstraintElement body = UNINITIALIZED; - protected String name; + private final List contexts = Lists.newArrayList(); - private AbstractConstraintContext(String name) { - super(); - this.name = name; - } - - protected void addConstraint(Constraint constraint) { - constraints.add(constraint); - } - - @Override - public List getConstraints() { - return constraints; - } - - @Override - public String getName() { - return name; - } - - protected void initConstraints() { - Collections.sort(constraints, new Comparator() { - @Override - public int compare(IConstraint o1, IConstraint o2) { - return o1.getName().compareTo(o2.getName()); - } - }); - for (IConstraint ele : constraints) - ((Constraint) ele).initLists(); - } - - @Override - public String toString() { - Iterable constraintNames = Iterables.transform(constraints, new Function() { - @Override - public String apply(IConstraint from) { - return from.getName(); - } - }); - return getName() + ": " + Joiner.on(" | ").join(constraintNames) + ";"; - } - } - - protected static class ActionConstraint extends Constraint { - - protected Action actionContext; - - public ActionConstraint(Action context, EClass type, ConstraintElement body, GrammarConstraintProvider provider) { - super(type, body, provider); - this.actionContext = context; - } - - @Override - protected EObject getMostSpecificContext() { - return body == null ? actionContext : body.getContext(); - } - - } - - protected static class AssignedActionConstraintContext extends AbstractConstraintContext { - - protected Action action; - - public AssignedActionConstraintContext(Action action, String name) { - super(name); - this.action = action; - } - - @Override - public EClass getCommonType() { - return (EClass) action.getType().getClassifier(); - } - - @Override - public EObject getContext() { - return action; - } - - } - - protected static abstract class Constraint implements IConstraint { - - protected IConstraintElement[] assignments; - - protected ConstraintElement body; - - protected GrammarConstraintProvider provider; - - protected IConstraintElement[] elements; - - protected IFeatureInfo[] features; - - protected String name; - - protected EObject specificContext; - - protected EClass type; - - public Constraint(EClass type, ConstraintElement body, GrammarConstraintProvider provider) { + private IFeatureInfo[] features = null; + + private final Grammar grammar; + + private String identity; + + private String name; + + private final Nfa nfa; + + private final EClass type; + + public Constraint(Grammar grammar, EClass type, Nfa nfa) { super(); + this.grammar = grammar; this.type = type; - this.body = body; - if (this.body != null) - this.body.setContainingConstraint(this); + this.nfa = nfa; } - protected void collectElements(ConstraintElement ele, List elements, - List assignments, List[] assignmentsByFeature) { - ele.setElementId(elements.size()); - elements.add(ele); - switch (ele.getType()) { - case ASSIGNED_ACTION_CALL: - case ASSIGNED_CROSSREF_DATATYPE_RULE_CALL: - case ASSIGNED_CROSSREF_ENUM_RULE_CALL: - case ASSIGNED_CROSSREF_TERMINAL_RULE_CALL: - case ASSIGNED_CROSSREF_KEYWORD: - case ASSIGNED_DATATYPE_RULE_CALL: - case ASSIGNED_ENUM_RULE_CALL: - case ASSIGNED_KEYWORD: - case ASSIGNED_PARSER_RULE_CALL: - case ASSIGNED_TERMINAL_RULE_CALL: - EClass type = ele.getContainingConstraint().getType(); - EStructuralFeature feature = type.getEStructuralFeature(ele.getFeatureName()); - if (feature == null) { - // TODO find a meaningful way to handle this - System.err.println("Feature " + ele.getFeatureName() + " not found in " - + type.getName()); - } else { - int featureID = type.getFeatureID(feature); - List assignmentByFeature = assignmentsByFeature[featureID]; - if (assignmentByFeature == null) - assignmentsByFeature[featureID] = assignmentByFeature = Lists.newArrayList(); - ele.setFeatureAssignmentId(assignmentByFeature.size()); - assignmentByFeature.add(ele); - ele.setAssignmentId(assignments.size()); - assignments.add(ele); - } - return; - case ALTERNATIVE: - case GROUP: - for (IConstraintElement e : ele.getChildren()) - collectElements((ConstraintElement) e, elements, assignments, assignmentsByFeature); + protected void collectBounds(ISemState state, int[] current, Set visited, int[] min, int[] max) { + int featureID = state.getFeatureID(); + if (featureID >= 0) { + if (current[featureID] == IGrammarConstraintProvider.MAX) return; + if (visited.add(state)) + current[featureID]++; + else + current[featureID] = IGrammarConstraintProvider.MAX; + } else if (state.getFollowers().isEmpty()) { + for (int i = 0; i < current.length; i++) { + max[i] = Math.max(current[i], max[i]); + min[i] = Math.min(current[i], min[i]); + } + return; } + for (ISemState follower : state.getFollowers()) + collectBounds(follower, current.clone(), Sets.newHashSet(visited), min, max); } @Override @@ -218,70 +106,71 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { @Override public boolean equals(Object obj) { - if (!(obj instanceof Constraint)) + if (obj == null || obj.getClass() != getClass()) return false; - Constraint c = (Constraint) obj; - if (type != null && c.type != null) { - if (!type.getName().equals(c.type.getName())) - return false; - if (!type.getEPackage().getNsURI().equals(c.type.getEPackage().getNsURI())) - return false; - } else if (type != null || c.type != null) - return false; - return (body == null && c.body == null) || (body != null && body.equals(c.body)); - } - - @Override - public IConstraintElement[] getAssignments() { - return assignments; + if (this == obj) + return true; + return getIdentity().equals(((Constraint) obj).getIdentity()); } @Override public IConstraintElement getBody() { + if (body == UNINITIALIZED) { + ConstraintElementFactory factory = new ConstraintElementFactory(this); + NfaToProduction n2p = new NfaToProduction().excludeStartAndStop(); + GrammarElementDeclarationOrder order = GrammarElementDeclarationOrder.get(grammar); + IConstraintElement element = n2p.nfaToGrammar(nfa, GET_ASSIGNED_GRAMMAR_ELEMENT, order, factory); + if (element.getType() == GROUP && element.getChildren().isEmpty()) + body = null; + else + body = element; + } return body; } - protected Object getCacheKey() { - return Tuples.create(body.getContext(), type); - } - @Override - public IConstraintElement[] getElements() { - return elements; + public List getContexts() { + return contexts; } @Override public IFeatureInfo[] getFeatures() { - return features; - } - - protected abstract EObject getMostSpecificContext(); - - protected Collection getAllContext() { - Set result = Sets.newLinkedHashSet(); - result.add(getMostSpecificContext()); - collectContexts(body, result); - return result; - } - - protected void collectContexts(ConstraintElement ele, Set result) { - if (ele != null) { - EObject context = ele.getContext(); - if (context != null) - result.add(context); - if (ele.getChildren() != null) - for (IConstraintElement child : ele.getChildren()) - collectContexts((ConstraintElement) child, result); + if (features == null) { + if (type == null) { + features = new IFeatureInfo[0]; + } else { + int count = type.getFeatureCount(); + features = new IFeatureInfo[count]; + int[] current = new int[count]; + int[] min = new int[count]; + int[] max = new int[count]; + Arrays.fill(current, 0); + Arrays.fill(min, IGrammarConstraintProvider.MAX); + Arrays.fill(max, -1); + collectBounds(nfa.getStart(), current, Sets. newHashSet(), min, max); + for (int i = 0; i < count; i++) { + EStructuralFeature feature = type.getEStructuralFeature(i); + features[i] = new FeatureInfo(this, feature, max[i], min[i]); + } + } } + return features; + } - @Override - public Iterable getMultiAssignementFeatures() { - List result = Lists.newArrayList(); - for (IFeatureInfo info : features) - if (info != null && info.getAssignments().length > 1) - result.add(info); - return result; + protected String getIdentity() { + if (identity == null) { + String nfaString = new NfaUtil().identityString(nfa, new Function() { + @Override + public String apply(ISemState input) { + AbstractElement element = input.getAssignedGrammarElement(); + return element == null ? null : EcoreUtil.getURI(element).toString(); + } + }); + String typeString = type == null ? "null" : type.getName() + "@" + type.getEPackage().getNsURI(); + identity = typeString + "\n" + nfaString; + } + return identity; } @Override @@ -290,17 +179,13 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { } @Override - public String getSimpleName() { - return name; + public Nfa getNfa() { + return nfa; } @Override - public Iterable getSingleAssignementFeatures() { - List result = Lists.newArrayList(); - for (IFeatureInfo info : features) - if (info != null && info.getAssignments().length == 1) - result.add(info); - return result; + public String getSimpleName() { + return name; } @Override @@ -310,30 +195,7 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { @Override public int hashCode() { - int result = body == null ? 0 : body.hashCode(); - if (type != null) { - result += 7 * type.getName().hashCode(); - result += 13 * type.getEPackage().getNsURI().hashCode(); - } - return result; - } - - protected void initLists() { - List ele = Lists.newArrayList(); - List ass = Lists.newArrayList(); - @SuppressWarnings("unchecked") - List[] feat = new List[getType() == null ? 0 : getType().getFeatureCount()]; - if (body != null) - collectElements(body, ele, ass, feat); - elements = ele.toArray(new IConstraintElement[ele.size()]); - assignments = ass.toArray(new IConstraintElement[ass.size()]); - features = new IFeatureInfo[feat.length]; - for (int i = 0; i < feat.length; i++) - if (feat[i] != null) { - EStructuralFeature feature = getType().getEStructuralFeature(i); - IConstraintElement[] fass = feat[i].toArray(new IConstraintElement[feat[i].size()]); - features[i] = new FeatureInfo(this, feature, fass); - } + return getIdentity().hashCode(); } protected void setName(String name) { @@ -349,608 +211,220 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { } - protected static class ConstraintElement implements IConstraintElement { + protected static class ConstraintContext implements IConstraintContext { - protected int assignmentId = -1; + private final List constraints = Lists.newArrayList(); + private final EObject context; + private final String name; - // protected Boolean cardinalityOneForFeature; - - protected List children; - - protected List containedAssignments = null; - - // protected IConstraintElement excludingAlternative = UNINTITIALIZED; - - protected ConstraintElement container; - - protected IConstraint containingConstraint; - - protected EObject context; - - protected List> dependingAssignments; - - protected AbstractElement element; - - protected URI elementURI; - - protected int elementId = -1; - - protected int featureAssignmentId = -1; - - protected IFeatureInfo featureInfo; - - protected boolean many; - - protected boolean optional; - - protected ConstraintElementType type; - - protected boolean typeMatch = false; - - protected ConstraintElement() { - } - - protected ConstraintElement(EObject context, ConstraintElementType type) { - this(context, type, null, false, false); - } - - protected ConstraintElement(EObject context, ConstraintElementType type, AbstractElement element) { - this(context, type, element, type != ConstraintElementType.ASSIGNED_ACTION_CALL - && GrammarUtil.isMultipleCardinality(element), type != ConstraintElementType.ASSIGNED_ACTION_CALL - && GrammarUtil.isOptionalCardinality(element)); - } - - protected ConstraintElement(EObject context, ConstraintElementType type, AbstractElement element, boolean many, - boolean optional) { + public ConstraintContext(EObject context, String name) { super(); this.context = context; + this.name = name; + } + + @Override + public EClass getCommonType() { + throw new UnsupportedOperationException(); + } + + @Override + public List getConstraints() { + return constraints; + } + + @Override + public EObject getContext() { + return context; + } + + @Override + public String getName() { + return name; + } + + @Override + public String toString() { + Iterable constraintNames = Iterables.transform(constraints, new Function() { + @Override + public String apply(IConstraint from) { + return from.getName(); + } + }); + return getName() + ": " + Joiner.on(" | ").join(constraintNames) + ";"; + } + } + + protected static class ConstraintElement implements IGrammarConstraintProvider.IConstraintElement { + + private final Collection children; + private final IConstraint constraint; + + private final AbstractElement element; + + private final boolean many; + + private final boolean optional; + private IConstraintElement parent; + private final ConstraintElementType type; + + public ConstraintElement(IConstraint constraint, ConstraintElementType type, AbstractElement element, + boolean many, boolean optional) { + super(); + this.constraint = constraint; this.type = type; this.element = element; + this.children = null; this.many = many; this.optional = optional; - if (type == ConstraintElementType.ALTERNATIVE || type == ConstraintElementType.GROUP) - children = Lists.newArrayList(); } - protected void addAllChilden(ConstraintElement childrenOwner) { - for (IConstraintElement c : childrenOwner.children) - addChild((ConstraintElement) c); - childrenOwner.getChildren().clear(); - if (childrenOwner.isTypeMatch()) - typeMatch(); - } - - protected void addChild(ConstraintElement child) { - child.container = this; - if (child == INVALID || child == TYPEMATCH) - throw new RuntimeException("This is not a valid child: '" + child + "'"); - this.children.add(child); - if (child.isTypeMatch()) - typeMatch(); - } - - protected void collectDependingAssignmentsByContainer(IConstraintElement child, - List> result, boolean childMany, - boolean childOptional) { - IConstraintElement container = child.getContainer(); - if (container == null) - return; - boolean cntOptional = container.isOptionalRecursive(null); - boolean cntMany = container.isManyRecursive(null); - switch (container.getType()) { - case ALTERNATIVE: - if (!container.isManyRecursive(null)) - for (IConstraintElement choice : container.getChildren()) - if (choice != child) - for (IConstraintElement ass : choice.getContainedAssignments()) - result.add(Tuples.create(ass, RelationalDependencyType.EXCLUDE_IF_SET)); - break; - case GROUP: - if (!cntOptional && !cntMany) - return; - for (IConstraintElement choice : container.getChildren()) - if (choice != child) - for (IConstraintElement ass : choice.getContainedAssignments()) { - boolean assMany = ass.isManyRecursive(container); - boolean assOptional = ass.isOptionalRecursive(container); - boolean exclude_if_unset = !assOptional; - boolean mandatory_if_set = !childOptional; - boolean same = false, same_or_less = false, same_or_more = false; - if (cntMany) { - if (!assMany && !assOptional && !childMany && !childOptional) - same = true; - else if ((childOptional && !childMany && !assOptional) - || (!assOptional && !childMany && !assOptional && assMany)) - same_or_less = true; - else if ((!childOptional && !childMany && assOptional && !assMany) - || (!childOptional && childMany && !assOptional && !assMany) - || (!childOptional && childMany && assOptional && !assMany)) - same_or_more = true; - } - if (exclude_if_unset && !same_or_less && !same) - result.add(Tuples.create(ass, RelationalDependencyType.EXCLUDE_IF_UNSET)); - if (mandatory_if_set && !same_or_more && !same) - result.add(Tuples.create(ass, RelationalDependencyType.MANDATORY_IF_SET)); - if (same) - result.add(Tuples.create(ass, RelationalDependencyType.SAME)); - if (same_or_less) - result.add(Tuples.create(ass, RelationalDependencyType.SAME_OR_LESS)); - if (same_or_more) - result.add(Tuples.create(ass, RelationalDependencyType.SAME_OR_MORE)); - } - break; - default: - } - childMany = childMany || container.isMany(); - childOptional = childOptional || container.isOptional() - || container.getType() == ConstraintElementType.ALTERNATIVE; - collectDependingAssignmentsByContainer(container, result, childMany, childOptional); - } - - protected boolean containsChild(IConstraintElement child) { - if (children == null) - return false; - for (IConstraintElement c : children) - if (c.equals(child)) - return true; - return false; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof ConstraintElement)) - return false; - ConstraintElement ce = (ConstraintElement) obj; - switch (type) { - case ALTERNATIVE: { - if (children == null || ce.children == null || children.size() != ce.children.size()) - return false; - for (IConstraintElement child : children) - if (!ce.containsChild(child)) - return false; - return true; - } - case GROUP: - return children.equals(ce.children); - default: - return getElementURI().equals(ce.getElementURI()); - } - } - - protected IConstraintElement findCommonContainer(List elements) { - if (elements.size() == 0) - return null; - if (elements.size() == 1) - return elements.get(0); - IConstraintElement result = elements.get(0); - for (int i = 1; i < elements.size(); i++) { - boolean found = false; - while (!found && result != null) { - IConstraintElement cand = elements.get(i); - while (!found && cand != null) - if (cand == result) - found = true; - else - cand = cand.getContainer(); - if (!found) - result = result.getContainer(); - } - } - return result; - } - - @Override - public Action getAction() { - return element instanceof Action ? (Action) element : null; - } - - @Override - public int getAssignmentID() { - return this.assignmentId; - } - - protected String getAssignmentOperator() { - if (element instanceof Action) - return ((Action) element).getOperator(); - Assignment ass = GrammarUtil.containingAssignment(element); - if (ass != null) - return ass.getOperator(); - return null; + public ConstraintElement(IConstraint constraint, ConstraintElementType type, + Collection children, boolean many, boolean optional) { + super(); + this.constraint = constraint; + this.type = type; + this.element = null; + this.children = children; + this.many = many; + this.optional = optional; + for (IConstraintElement child : children) + ((ConstraintElement) child).parent = this; } @Override public EObject getCallContext() { switch (type) { case ASSIGNED_ACTION_CALL: - return getAction(); + return element; case ASSIGNED_PARSER_RULE_CALL: - return getRuleCall().getRule(); + return ((RuleCall) element).getRule(); default: return null; } } @Override - public String getCardinality() { - return isMany() ? (isOptional() ? "*" : "+") : (isOptional() ? "?" : ""); - } - - @Override - public List getChildren() { - return children; - } - - @Override - public List getContainedAssignments() { - if (containedAssignments == null) { - containedAssignments = Lists.newArrayList(); - if (assignmentId >= 0) - containedAssignments.add(this); - if (getChildren() != null) - for (IConstraintElement child : getChildren()) - containedAssignments.addAll(child.getContainedAssignments()); - } - return containedAssignments; + public Collection getChildren() { + return children == null ? Collections. emptyList() : children; } @Override public IConstraintElement getContainer() { - return container; + return parent; } @Override public IConstraint getContainingConstraint() { - if (containingConstraint == null) - containingConstraint = getContainer().getContainingConstraint(); - return containingConstraint; + return constraint; } - protected EObject getContext() { - return context; - } - - @Override - public CrossReference getCrossReference() { - if (element == null) - return null; - return GrammarUtil.containingCrossReference(element); - } - - @Override - public EClass getCrossReferenceType() { - if (element == null) - return null; - CrossReference cr = GrammarUtil.containingCrossReference(element); - if (cr == null) - return null; - return (EClass) cr.getType().getClassifier(); - } - - @Override - public List> getDependingAssignment() { - if (assignmentId < 0) - return null; - if (dependingAssignments == null) { - dependingAssignments = Lists.newArrayList(); - collectDependingAssignmentsByContainer(this, dependingAssignments, isMany(), isOptional()); - } - return dependingAssignments; - } - - @Override - public int getElementID() { - return elementId; - } - - @Override - public EStructuralFeature getFeature() { - return getFeatureInfo().getFeature(); - } - - @Override - public int getFeatureAssignmentID() { - return featureAssignmentId; - } - - @Override - public IFeatureInfo getFeatureInfo() { - return featureInfo; - } - - protected String getFeatureName() { + public String getFeatureName() { if (element instanceof Action) return ((Action) element).getFeature(); - Assignment ass = GrammarUtil.containingAssignment(element); - if (ass != null) - return ass.getFeature(); + Assignment assignment = GrammarUtil.containingAssignment(element); + if (assignment != null) + return assignment.getFeature(); return null; } - // protected void insertChild(ConstraintElement child) { - // child.container = this; - // this.children.add(0, child); - // } - @Override public AbstractElement getGrammarElement() { return element; } - @Override - public Keyword getKeyword() { - return element instanceof Keyword ? (Keyword) element : null; - } - - @Override - public RuleCall getRuleCall() { - return element instanceof RuleCall ? (RuleCall) element : null; - } - @Override public ConstraintElementType getType() { return type; } - protected URI getElementURI() { - if (elementURI == null) - elementURI = element == null ? URI.createURI("null") : EcoreUtil.getURI(element); - return elementURI; - } - - @Override - public int hashCode() { - switch (type) { - case ALTERNATIVE: { - // has code must be independent of children's order - int result = 0; - for (IConstraintElement child : children) - result += child.hashCode(); - return result; - } - case GROUP: { - // has code must depend of children's order - int result = 0; - for (int i = 0; i < children.size(); i++) - result += children.get(i).hashCode() + i; - return result; - } - default: - return getElementURI().hashCode(); - } - } - - @Override - public boolean isCardinalityOneAmongAssignments(List assignments) { - if (assignments.size() < 2) - return false; - IConstraintElement commonContainer = findCommonContainer(assignments); - return commonContainer.getType() != ConstraintElementType.ALTERNATIVE - && !isOptionalRecursive(commonContainer) && !isManyRecursive(commonContainer); - } - @Override public boolean isMany() { return many; } - @Override - public boolean isManyRecursive(IConstraintElement root) { - return isMany() || (container != null && container != root && container.isManyRecursive(root)); - } - @Override public boolean isOptional() { - return optional && !typeMatch; - } - - @Override - public boolean isOptionalRecursive(IConstraintElement root) { - if (isOptional()) - return true; - if (getContainer() != root) { - if (getContainer().getType() == ConstraintElementType.ALTERNATIVE) - return true; - if (getContainer().isOptionalRecursive(root)) - return true; - } - return false; - } - - @Override - public boolean isRoot() { - return container == null; - } - - protected boolean isTypeMatch() { - return this.typeMatch; - } - - protected void setAssignmentId(int id) { - this.assignmentId = id; - } - - protected void setContainingConstraint(IConstraint containingConstraint) { - this.containingConstraint = containingConstraint; - } - - protected void setElementId(int id) { - this.elementId = id; - } - - protected void setFeatureAssignmentId(int id) { - this.featureAssignmentId = id; - } - - protected void setFeatureInfo(IFeatureInfo featureInfo) { - this.featureInfo = featureInfo; - } - - // - // public IConstraintElement getExcludingAlternative() { - // if (excludingAlternative == UNINTITIALIZED) { - // IConstraintElement ele = getContainer(); - // while (ele != null) { - // if (ele.getType() == ConstraintElementType.ALTERNATIVE) { - // if (ele.isManyRecursive(null)) - // return excludingAlternative = null; - // else - // return excludingAlternative = ele; - // - // } else - // ele = ele.getContainer(); - // } - // return excludingAlternative = null; - // } - // return excludingAlternative; - // } - - protected void setMany(boolean many) { - this.many = many; - } - - protected void setOptional(boolean optional) { - this.optional = optional; + return optional; } @Override public String toString() { - if (this == INVALID) - return "INVALID"; - if (this == TYPEMATCH) - return "TYPEMATCH"; - if (type == null) - return "error(type is null)"; GrammarElementTitleSwitch t2s = new GrammarElementTitleSwitch().hideCardinality().showActionsAsRuleCalls() .showAssignments(); ProductionFormatter formatter = new ProductionFormatter(); formatter.setTokenToString(t2s); return formatter.format(new ConstraintElementProduction(getContainingConstraint()), this, true); } + } - protected void typeMatch() { - this.typeMatch = true; + protected static class ConstraintElementFactory implements ProductionFactory { + + private final IConstraint constraint; + + public ConstraintElementFactory(IConstraint constraint) { + super(); + this.constraint = constraint; + } + + @Override + public IConstraintElement createForAlternativeChildren(boolean m, boolean o, Iterable c) { + + return new ConstraintElement(constraint, ALTERNATIVE, ImmutableSet.copyOf(c), m, o); + } + + @Override + public IConstraintElement createForSequentialChildren(boolean m, boolean o, Iterable c) { + return new ConstraintElement(constraint, GROUP, ImmutableList.copyOf(c), m, o); + } + + @Override + public IConstraintElement createForToken(boolean many, boolean optional, AbstractElement token) { + ConstraintElementType type = token == null ? null : getConstraintElementType(token); + return new ConstraintElement(constraint, type, token, many, optional); + } + + @Override + public IConstraintElement createForUnordertedChildren(boolean m, boolean o, Iterable c) { + return new ConstraintElement(constraint, UNORDERED_GROUP, ImmutableSet.copyOf(c), m, o); } } protected static class FeatureInfo implements IFeatureInfo { + private List assignments = null; + private final Constraint constraint; + private final EStructuralFeature feature; + private final int lowerBound; + private final int upperBound; - protected IConstraintElement[] assignments; - - protected IConstraint constraint; - - protected Boolean contentValidationNeeded; - - protected List> dependingFeatures; - - protected EStructuralFeature feature; - - public FeatureInfo(IConstraint constraint, EStructuralFeature feature, IConstraintElement[] assignments) { + public FeatureInfo(Constraint constraint, EStructuralFeature feature, int upperBound, int lowerBound) { super(); this.constraint = constraint; this.feature = feature; - this.assignments = assignments; - for (IConstraintElement ass : assignments) - ((ConstraintElement) ass).setFeatureInfo(this); - } - - public int getAssignmentCount() { - return assignments.length; + this.upperBound = upperBound; + this.lowerBound = lowerBound; } @Override - public IConstraintElement[] getAssignments() { - return assignments; - } - - @Override - public IConstraint getContainingConstraint() { - return constraint; - } - - @Override - public List> getDependingFeatures() { - if (dependingFeatures == null) { - dependingFeatures = Lists.newArrayList(); - for (Pair p : getRelationalAssignemntConstraintIntersection()) - dependingFeatures.add(Tuples.create(p.getFirst().getFeatureInfo(), p.getSecond())); - } - return dependingFeatures; - } - - @Override - public EStructuralFeature getFeature() { - return feature; - } - - @Override - public int getLowerBound() { - int result = 0; - for (IConstraintElement ass : getAssignments()) - if (!ass.isOptionalRecursive(null)) - result++; - return result; // TODO: consider assignments excluding each other - } - - protected List> getRelationalAssignemntConstraintIntersection() { - List> r = getAssignments()[0].getDependingAssignment(); - if (getAssignmentCount() == 1) - return r; - r = Lists.newArrayList(r); - for (int i = 1; i < getAssignments().length; i++) - for (int j = r.size() - 1; j >= 0; j--) - if (getAssignments()[i] == r.get(j).getFirst() - || !getAssignments()[i].getDependingAssignment().contains(r.get(j))) - r.remove(j); - return r; - } - - @Override - public int getUpperBound() { - for (IConstraintElement ass : getAssignments()) - if (ass.isManyRecursive(null)) - return -1; - return getAssignmentCount(); // TODO: consider assignments excluding each other - } - - @Override - public boolean isContentValidationNeeded() { - if (contentValidationNeeded != null) - return contentValidationNeeded; - contentValidationNeeded = false; - if (assignments.length >= 2) { - IConstraintElement first = assignments[0]; - if (first.getType() == ConstraintElementType.ASSIGNED_ACTION_CALL) - contentValidationNeeded = true; - else - for (int i = 1; i < assignments.length; i++) { - IConstraintElement a = assignments[i]; - if (a.getType() == ConstraintElementType.ASSIGNED_ACTION_CALL - || first.getCrossReferenceType() != a.getCrossReferenceType() - || !EcoreUtil.equals(first.getGrammarElement(), a.getGrammarElement())) { - contentValidationNeeded = true; - break; - } + public List getAssignments() { + if (assignments == null) { + IConstraintElement body = constraint.getBody(); + if (body == null) { + assignments = Collections.emptyList(); + } else { + assignments = Lists.newArrayList(); + LinkedList stack = new LinkedList(); + stack.push(body); + while (!stack.isEmpty()) { + IConstraintElement element = stack.pop(); + String name = ((ConstraintElement) element).getFeatureName(); + if (feature.getName().equals(name)) + assignments.add(element); + stack.addAll(element.getChildren()); } + } } - return contentValidationNeeded; - } - - @Override - public String toString() { - StringBuilder b = new StringBuilder(); - b.append(feature.getName()); - b.append("["); - b.append(getLowerBound()); - b.append(", "); - b.append(getUpperBound() == -1 ? "*" : getUpperBound()); - b.append("]"); - for (Pair rel : getDependingFeatures()) { - b.append("\n "); - b.append(rel.getSecond()); - b.append(" "); - b.append(rel.getFirst().getFeature().getName()); - } - return b.toString(); + return assignments; } @Override @@ -963,420 +437,93 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { } return result; } - } - protected static class ParserRuleConstraintContext extends AbstractConstraintContext { - - protected ParserRule rule; - - public ParserRuleConstraintContext(ParserRule rule, String name) { - super(name); - this.rule = rule; + @Override + public IConstraint getContainingConstraint() { + return constraint; } @Override - public EClass getCommonType() { - return (EClass) rule.getType().getClassifier(); + public EStructuralFeature getFeature() { + return feature; } @Override - public EObject getContext() { - return rule; + public int getLowerBound() { + return lowerBound; + } + + @Override + public int getUpperBound() { + return upperBound; } } - protected static class RuleConstraint extends Constraint { - - protected ParserRule context; - - public RuleConstraint(ParserRule context, EClass type, ConstraintElement body, - GrammarConstraintProvider provider) { - super(type, body, provider); - this.context = context; - } - - @Override - protected EObject getMostSpecificContext() { - return body == null ? context : body.getContext(); - } - - } - - protected final static ConstraintElement INVALID = new ConstraintElement(); - - protected final static ConstraintElement TYPEMATCH = new ConstraintElement() { - @Override - protected boolean isTypeMatch() { - return true; - } - }; - - protected final static ConstraintElement UNINTITIALIZED = new ConstraintElement(); - - protected Map> cache = Maps.newHashMap(); + private final static IConstraintElement UNINITIALIZED = new ConstraintElement(null, null, (AbstractElement) null, + false, false); @Inject protected Context2NameFunction context2Name; - - @Inject - protected RuleNames ruleNames; @Inject - protected IContextProvider contextProvider; + private ISemanticSequencerNfaProvider nfaProvider; @Inject - protected ActionFilterNFAProvider nfaProvider; + private NfaUtil nfaUtil; - protected ConstraintElement createConstraintElement(EObject context, AbstractElement ele, EClass requiredType, - Set visited) { - boolean isOptional = GrammarUtil.isOptionalCardinality(ele); - if (ele instanceof Action) { - if (requiredType == null) - return INVALID; - Action action = (Action) ele; - if (action.getFeature() == null) { - if (action.getType().getClassifier() == requiredType) - return TYPEMATCH; - else - return isOptional ? null : INVALID; - } else - return null; //new ConstraintElement(context, getConstraintElementType(ele), ele); - } else if (ele instanceof RuleCall) { - RuleCall rc = (RuleCall) ele; - if (GrammarUtil.isUnassignedEObjectRuleCall(rc)) { - ParserRule rule = (ParserRule) rc.getRule(); - if (!rule.isFragment()) { - if (!visited.add(rc)) - return null; - ConstraintElement result = createConstraintElement(rule, requiredType, visited); - if (result != null && result != INVALID) { - TypeRef returnType = rc.getRule().getType(); - if (returnType.getClassifier() == requiredType) - result.typeMatch(); - if (result.isTypeMatch()) - return result; - } - return isOptional ? null : INVALID; - } else { - ConstraintElement result = createConstraintElement(context, rule.getAlternatives(), requiredType, visited); - if (result != null && result != INVALID) { - result.setMany(result.isMany() || GrammarUtil.isMultipleCardinality(ele)); - result.setOptional(result.isOptional() || GrammarUtil.isOptionalCardinality(ele)); - } - return result; - } - } else if (GrammarUtil.containingAssignment(ele) != null) { - return new ConstraintElement(context, getConstraintElementType(ele), ele); - } else { - return null; - } - } else if (ele instanceof Keyword) { - if (GrammarUtil.containingAssignment(ele) != null) - return new ConstraintElement(context, getConstraintElementType(ele), ele); - return null; - } else if (ele instanceof Assignment) { - if (requiredType == null) - return INVALID; - Assignment ass = (Assignment) ele; - ConstraintElement result = createConstraintElement(context, ass.getTerminal(), requiredType, visited); - if (result != null && result != INVALID) { - result.setMany(result.isMany() || GrammarUtil.isMultipleCardinality(ele)); - result.setOptional(result.isOptional() || GrammarUtil.isOptionalCardinality(ele)); - } - return result; - } else if (ele instanceof CrossReference) { - CrossReference cr = (CrossReference) ele; - ConstraintElement result = createConstraintElement(context, cr.getTerminal(), requiredType, visited); - if (result != null && result != INVALID) { - result.setMany(result.isMany() || GrammarUtil.isMultipleCardinality(ele)); - result.setOptional(result.isOptional() || GrammarUtil.isOptionalCardinality(ele)); - } - return result; - } else if (ele instanceof Alternatives) { - Alternatives alt = (Alternatives) ele; - List children = Lists.newArrayList(); - List typeMatchChildren = Lists.newArrayList(); - boolean allInvalid = true; - boolean nullExists = false; - boolean typeMatch = false; - for (AbstractElement ae : alt.getElements()) { - ConstraintElement ce = createConstraintElement(context, ae, requiredType, visited); - if (ce != INVALID) { - allInvalid = false; - if (ce != TYPEMATCH) { - if (ce != null) { - if (ce.isTypeMatch()) - typeMatchChildren.add(ce); - else - children.add(ce); - } else - nullExists = true; - } else - typeMatch = true; - } - } - if (allInvalid) - return isOptional ? null : INVALID; - if (typeMatch || !typeMatchChildren.isEmpty()) { - children = typeMatchChildren; - nullExists = typeMatch; - } - if (children.isEmpty()) - return typeMatch ? TYPEMATCH : null; - else if (children.size() == 1) { - ConstraintElement result = children.get(0); - result.setMany(result.isMany() || GrammarUtil.isMultipleCardinality(ele)); - result.setOptional(result.isOptional() || nullExists || GrammarUtil.isOptionalCardinality(ele)); - return result; - } else { - ConstraintElement result = new ConstraintElement(context, getConstraintElementType(ele), ele); - result.setOptional(result.isOptional() || nullExists); - for (ConstraintElement child : children) - if (child.getType() == ConstraintElementType.ALTERNATIVE && !child.isOptional() && !child.isMany()) - result.addAllChilden(child); - else - result.addChild(child); - return result; - } - } else if (ele instanceof Group || ele instanceof UnorderedGroup) { - CompoundElement group = (CompoundElement) ele; - List children = Lists.newArrayList(); - boolean typeMatch = false; - for (AbstractElement ae : group.getElements()) { - ConstraintElement ce = createConstraintElement(context, ae, requiredType, visited); - if (ce == INVALID && !ce.isOptional()) - return isOptional ? null : INVALID; - if (ce != INVALID && ce != null && ce != TYPEMATCH) - children.add(ce); - else if (ce == TYPEMATCH) - typeMatch = true; - } - if (children.isEmpty()) - return typeMatch ? TYPEMATCH : null; - else if (children.size() == 1 && (!children.get(0).isOptional() || !typeMatch)) { - ConstraintElement result = children.get(0); - result.setMany(result.isMany() || GrammarUtil.isMultipleCardinality(ele)); - result.setOptional(result.isOptional() || GrammarUtil.isOptionalCardinality(ele)); - if (typeMatch) - result.typeMatch(); - return result; - } else { - ConstraintElement result = new ConstraintElement(context, getConstraintElementType(ele), ele); - for (ConstraintElement child : children) - if (child.getType() == ConstraintElementType.GROUP && !child.isOptional() && !child.isMany()) - result.addAllChilden(child); - else - result.addChild(child); - if (typeMatch) - result.typeMatch(); - return result; - } - } - return null; - } + @Inject + private IContextPDAProvider pdaProvider; - protected ConstraintElement createConstraintElement(EObject context, ActionFilterState state, EClass requiredType, - boolean allowLocal, Set visited) { - if (allowLocal && GrammarUtil.isAssignedAction(state.getGrammarElement())) { - if (((Action) state.getGrammarElement()).getType().getClassifier() == requiredType) { - ConstraintElement result = new ConstraintElement(context, ConstraintElementType.ASSIGNED_ACTION_CALL, - state.getGrammarElement()); - result.typeMatch(); - return result; - } else - return INVALID; - } - if (!visited.add(state)) - return INVALID; - List followers = Lists.newArrayList(); - boolean allInvalid = true, containsOne = false, typematch = false; - for (ActionFilterTransition t : state.getAllOutgoing()) - if (!t.isRuleCall() /* && t.getTarget() != state */) { - containsOne = true; - ConstraintElement f = createConstraintElement(context, t.getTarget(), requiredType, true, visited); - if (f == TYPEMATCH) { - allInvalid = false; - typematch = true; - } else if (f != INVALID) { - allInvalid = false; - if (f != null) - followers.add(f); - } - } - if (containsOne && allInvalid) - return INVALID; - ConstraintElement local = allowLocal ? createConstraintElement(context, state.getGrammarElement(), - requiredType, visited) : null; - if (typematch && local != null) - local.typeMatch(); - if (allowLocal && state.isEndState() && !GrammarUtil.isUnassignedEObjectRuleCall(state.getGrammarElement())) { - if (GrammarUtil.containingRule(state.getGrammarElement()).getType().getClassifier() != requiredType - && (local == null || !local.isTypeMatch())) - return INVALID; - } - if (local == INVALID && !GrammarUtil.isOptionalCardinality(state.getGrammarElement())) - return INVALID; - if (followers.isEmpty()) - return local == INVALID || local == null ? (typematch ? TYPEMATCH : null) : local; - if (local != INVALID && local != null) - for (int i = 0; i < followers.size(); i++) { - ConstraintElement follower = followers.get(i); - if (follower.getType() == ConstraintElementType.GROUP && !follower.isMany() && !follower.isOptional()) - follower.addChild(local); - else { - ConstraintElement group = new ConstraintElement(context, ConstraintElementType.GROUP); - group.addChild(follower); - group.addChild(local); - followers.set(i, group); - } - } - followers = filterDuplicates(followers); - if (followers.size() == 1) - return followers.get(0); - ConstraintElement alt = null; - for (ConstraintElement f : followers) - if (f.getType() == ConstraintElementType.ALTERNATIVE && !f.isMany() && !f.isOptional()) { - alt = f; - break; - } - if (alt != null) - followers.remove(alt); - else - alt = new ConstraintElement(context, ConstraintElementType.ALTERNATIVE); - for (ConstraintElement f : followers) - alt.addChild(f); - return alt; - } + @Inject + private IContextTypePDAProvider typeProvider; - protected ConstraintElement createConstraintElement(ParserRule rule, EClass requiredType, Set visited) { - if (!visited.add(rule)) - return INVALID; - if (GrammarUtil.containsAssignedAction(rule)) { // also check actions in used fragments - ActionFilterState start = nfaProvider.getNFA(rule.getAlternatives()); - return createConstraintElement(rule, start, requiredType, false, visited); - } else { - return createConstraintElement(rule, rule.getAlternatives(), requiredType, visited); - } - } - - protected void filterDuplicateConstraintsAndSetNames(Grammar grammar, List contexts) { - Map> equalConstraints = Maps.newHashMap(); - for (IConstraintContext context : contexts) - for (IConstraint constraint : context.getConstraints()) { - List same = equalConstraints.get(constraint); - if (same == null) - equalConstraints.put(constraint, same = Lists.newArrayList()); - same.add(constraint); - } - Map allConstraints = Maps.newIdentityHashMap(); - for (Collection equal : equalConstraints.values()) { - // Collection equal = filterConstraintsFromSubGrammars(grammar, allEqual); - IConstraint representative = findRepresentativeConstraint(equal); - ((Constraint) representative).setName(findBestConstraintName(grammar, equal)); - for (IConstraint constraint : equal) - allConstraints.put(constraint, representative); - } - for (IConstraintContext context : contexts) { - for (int i = 0; i < context.getConstraints().size(); i++) { - IConstraint replacement = allConstraints.get(context.getConstraints().get(i)); - context.getConstraints().set(i, replacement); - } - ((AbstractConstraintContext) context).initConstraints(); - } - } - - protected List filterDuplicates(List list) { - List result = Lists.newArrayList(); - for (ConstraintElement ce : list) { - boolean found = false; - for (ConstraintElement r : result) - if (ce.equals(r)) { - found = true; - break; - } - if (!found) - result.add(ce); - } - return result; - } - - protected Map getInheritanceDistance(Grammar grammar) { - Map result = Maps.newHashMap(); - Grammar current = grammar; - int distance = 0; - while (current != null) { - result.put(current, distance); - current = current.getUsedGrammars().isEmpty() ? null : current.getUsedGrammars().get(0); - distance++; - } - return result; - } - - // protected Collection filterConstraintsFromSubGrammars(Grammar grammar, - // Collection constraints) { - // if (constraints.size() <= 1) - // return constraints; - // Map inheritanceDistance = getInheritanceDistance(grammar); - // int maxDistance = 0; - // for (IConstraint c : constraints) - // maxDistance = Math.max(maxDistance, inheritanceDistance.get(c.getDeclaringGrammar())); - // Collection filteredConstraints = Lists.newArrayList(); - // for (IConstraint c : constraints) { - // if (inheritanceDistance.get(c.getDeclaringGrammar()) == maxDistance) - // filteredConstraints.add(c); - // } - // return filteredConstraints; - // } - - protected void collectElements(IConstraintElement ele, List result) { - if (ele.getGrammarElement() != null) - result.add(ele.getGrammarElement()); - if (ele.getChildren() != null) - for (IConstraintElement e : ele.getChildren()) - collectElements(e, result); - } - - protected String findBestConstraintName(Grammar grammar, Collection equalConstraints) { + protected String findBestConstraintName(Grammar grammar, IConstraint constraint) { + EClass type = constraint.getType(); Set relevantRules = Sets.newLinkedHashSet(); Set relevantActions = Sets.newLinkedHashSet(); Set contextRules = Sets.newLinkedHashSet(); - for (IConstraint c : equalConstraints) - for (EObject ctx : ((Constraint) c).getAllContext()) - if (ctx instanceof ParserRule) - contextRules.add((ParserRule) ctx); - List ele = Lists.newArrayList(); - IConstraint first = equalConstraints.iterator().next(); - if (first.getBody() != null) - collectElements(first.getBody(), ele); - for (AbstractElement e : ele) - relevantRules.add(GrammarUtil.containingParserRule(e)); - for (IConstraint c : equalConstraints) - for (EObject ctx : ((Constraint) c).getAllContext()) - if (ctx instanceof Action) { - Action action = (Action) ctx; - ParserRule rule = GrammarUtil.containingParserRule(action); - if (!contextRules.contains(rule)) { - relevantActions.add(action); - relevantRules.add(rule); + for (IConstraintContext ctx : constraint.getContexts()) { + EObject obj = ctx.getContext(); + if (obj instanceof ParserRule) + contextRules.add((ParserRule) obj); + } + // 1. find relevant rules based on assignments + for (ISemState s : nfaUtil.collect(constraint.getNfa())) { + AbstractElement element = s.getAssignedGrammarElement(); + if (element != null) + relevantRules.add(GrammarUtil.containingParserRule(element)); + } + if (relevantRules.isEmpty()) { + Set allRules = Sets.newHashSet(contextRules); + for (IConstraintContext ctx : constraint.getContexts()) { + EObject obj = ctx.getContext(); + if (obj instanceof Action) + allRules.add(GrammarUtil.containingParserRule(obj)); + } + if (type != null) { + // 2a. find relevant rules based on unassigned actions + for (ParserRule context : allRules) { + for (Action a : GrammarUtil.containedActions(context)) { + if (a.getFeature() == null && a.getType().getClassifier() == type) { + relevantRules.add(context); + } } } - if (relevantRules.isEmpty()) { - EClass type = first.getType(); - if (type != null) { - for (ParserRule context : contextRules) - for (Action a : GrammarUtil.containedActions(context)) - if (a.getType().getClassifier() == type) { - relevantRules.add(context); - } - if (relevantRules.isEmpty()) - for (ParserRule context : contextRules) - if (context.getType().getClassifier() == type) { - relevantRules.add(context); - } + } else { + // 2b. use all rules, because the constraint returns null. + relevantRules.addAll(allRules); + } + } + for (IConstraintContext ctx : constraint.getContexts()) { + EObject obj = ctx.getContext(); + if (obj instanceof Action) { + Action action = (Action) obj; + ParserRule rule = GrammarUtil.containingParserRule(action); + if (!contextRules.contains(rule) && relevantRules.contains(rule)) { + relevantActions.add(action); + } } - if (relevantRules.isEmpty()) - relevantRules.addAll(contextRules); } List actions = Lists.newArrayList(); List rules = Lists.newArrayList(); @@ -1393,119 +540,38 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { return result; } - protected IConstraint findRepresentativeConstraint(Collection equalConstraints) { - for (IConstraint c : equalConstraints) - if (((Constraint) c).getMostSpecificContext() instanceof ParserRule) - return c; - return equalConstraints.iterator().next(); - } - - protected ConstraintElementType getConstraintElementType(AbstractElement ele) { - if (ele instanceof Action) { - if (((Action) ele).getFeature() != null) - return ConstraintElementType.ASSIGNED_ACTION_CALL; - } else if (ele instanceof Alternatives) { - return ConstraintElementType.ALTERNATIVE; - } else if (ele instanceof Group || ele instanceof UnorderedGroup) { - return ConstraintElementType.GROUP; - } else if (GrammarUtil.containingCrossReference(ele) != null) { - if (ele instanceof RuleCall) { - RuleCall rc = (RuleCall) ele; - if (rc.getRule() instanceof ParserRule) - return ConstraintElementType.ASSIGNED_CROSSREF_DATATYPE_RULE_CALL; - if (rc.getRule() instanceof TerminalRule) - return ConstraintElementType.ASSIGNED_CROSSREF_TERMINAL_RULE_CALL; - if (rc.getRule() instanceof EnumRule) - return ConstraintElementType.ASSIGNED_CROSSREF_ENUM_RULE_CALL; - } else if (ele instanceof Keyword) - return ConstraintElementType.ASSIGNED_CROSSREF_KEYWORD; - } else if (GrammarUtil.containingAssignment(ele) != null) { - if (ele instanceof RuleCall) { - RuleCall rc = (RuleCall) ele; - if (rc.getRule() instanceof ParserRule) { - if (rc.getRule().getType().getClassifier() instanceof EClass) - return ConstraintElementType.ASSIGNED_PARSER_RULE_CALL; - return ConstraintElementType.ASSIGNED_DATATYPE_RULE_CALL; - } - if (rc.getRule() instanceof TerminalRule) - return ConstraintElementType.ASSIGNED_TERMINAL_RULE_CALL; - if (rc.getRule() instanceof EnumRule) - return ConstraintElementType.ASSIGNED_ENUM_RULE_CALL; - - } else if (ele instanceof Keyword) { - return ConstraintElementType.ASSIGNED_KEYWORD; - } - } - throw new RuntimeException("Unknown Grammar Element: " + EmfFormatter.objPath(ele)); - } - - protected IConstraintContext getConstraints(Grammar grammar, Action context) { - AssignedActionConstraintContext result = new AssignedActionConstraintContext(context, - context2Name.getContextName(grammar, context)); - ActionFilterState start = nfaProvider.getNFA(context); - Set types = contextProvider.getTypesForContext(grammar, context); - for (EClass type : types) { - if (type == null) { - Constraint constraint = new ActionConstraint(context, null, null, this); - result.addConstraint(constraint); - } else { - ConstraintElement ce = createConstraintElement(context, start, type, false, Sets.newLinkedHashSet()); - if (ce == TYPEMATCH) { - Constraint constraint = new ActionConstraint(context, type, null, this); - result.addConstraint(constraint); - } else if (ce != null && ce != INVALID) { - Constraint constraint = new ActionConstraint(context, type, ce, this); - result.addConstraint(constraint); - } else - // TODO find a meaningful way to handle this - System.err.println("constraint is " + ce + " for context " + context2Name.getContextName(grammar, context) - + " and type " + type.getName()); - } - } - return result; - } - @Override - public List getConstraints(Grammar context) { - List result = cache.get(context); - if (result == null) { - result = Lists.newArrayList(); - for (ParserRule parserRule : GrammarUtil.allParserRules(context)) { - if (parserRule.getType() != null && parserRule.getType().getClassifier() instanceof EClass && !parserRule.isFragment()) { - result.add(getConstraints(context, parserRule)); - for (Action action : GrammarUtil.containedActions(parserRule)) - if (action.getFeature() != null) - result.add(getConstraints(context, action)); + public List getConstraints(Grammar grammar) { + GrammarElementDeclarationOrder.get(grammar); + List result = Lists.newArrayList(); + Map>, Constraint> constraints = Maps.newHashMap(); + for (EObject context : pdaProvider.getAllContexts(grammar)) { + String contextName = context2Name.getContextName(grammar, context); + ConstraintContext constraintContext = new ConstraintContext(context, contextName); + for (EClass type : typeProvider.getTypesForContext(grammar, context)) { + Nfa nfa = nfaProvider.getNFA(context, type); + Pair> key = Tuples.create(type, nfa); + Constraint constraint = constraints.get(key); + if (constraint == null) { + constraint = new Constraint(grammar, type, nfa); + constraints.put(key, constraint); } + constraintContext.constraints.add(constraint); + constraint.contexts.add(constraintContext); } - filterDuplicateConstraintsAndSetNames(context, result); - cache.put(context, result); + result.add(constraintContext); } - return result; - } - - protected IConstraintContext getConstraints(Grammar grammar, ParserRule context) { - ParserRuleConstraintContext result = new ParserRuleConstraintContext(context, - context2Name.getContextName(grammar, context)); - Set types = contextProvider.getTypesForContext(grammar, context); - for (EClass type : types) { - if (type == null) { - Constraint constraint = new RuleConstraint(context, null, null, this); - result.addConstraint(constraint); - } else { - ConstraintElement ce = createConstraintElement(context, type, Sets.newLinkedHashSet()); - if (ce == TYPEMATCH) { - Constraint constraint = new RuleConstraint(context, type, null, this); - result.addConstraint(constraint); - } else if (ce != null && ce != INVALID) { - Constraint constraint = new RuleConstraint(context, type, ce, this); - result.addConstraint(constraint); - } else - // TODO find a meaningful way to handle this - System.err.println("constraint is " + ce + " for context " + context2Name.getContextName(grammar, context) - + " and type " + type.getName()); + for (Constraint constraint : constraints.values()) + constraint.setName(findBestConstraintName(grammar, constraint)); + for (IConstraintContext ctx : result) + Collections.sort(ctx.getConstraints()); + Function function = new Function() { + @Override + public EObject apply(IConstraintContext input) { + return input.getContext(); } - } + }; + Collections.sort(result, GrammarElementDeclarationOrder.get(grammar).toComparator(function)); return result; } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/GrammarElementDeclarationOrder.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/GrammarElementDeclarationOrder.java new file mode 100644 index 000000000..96f1bae78 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/GrammarElementDeclarationOrder.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2015 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.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.eclipse.emf.common.notify.Adapter; +import org.eclipse.emf.common.notify.impl.AdapterImpl; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.common.util.TreeIterator; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.AbstractRule; +import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.util.EmfFormatter; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +/** + * @author Moritz Eysholdt - Initial contribution and API + */ +public class GrammarElementDeclarationOrder extends AdapterImpl implements Comparator { + + public static GrammarElementDeclarationOrder get(Grammar grammar) { + for (Adapter a : grammar.eAdapters()) + if (a instanceof GrammarElementDeclarationOrder) + return (GrammarElementDeclarationOrder) a; + GrammarElementDeclarationOrder result = new GrammarElementDeclarationOrder(grammar); + grammar.eAdapters().add(result); + for (Grammar g : GrammarUtil.allUsedGrammars(grammar)) { + EList adapters = g.eAdapters(); + Iterator it = adapters.iterator(); + while (it.hasNext()) + if (it.next() instanceof GrammarElementDeclarationOrder) + it.remove(); + adapters.add(result); + } + return result; + } + + protected Map elementIDCache; + + protected GrammarElementDeclarationOrder(Grammar grammar) { + elementIDCache = Maps.newHashMap(); + List grammars = Lists.newArrayList(grammar); + grammars.addAll(GrammarUtil.allUsedGrammars(grammar)); + int counter = 0; + for (Grammar g : grammars) { + elementIDCache.put(g, counter++); + for (AbstractRule rule : g.getRules()) { + elementIDCache.put(rule, counter++); + TreeIterator iterator = rule.eAllContents(); + while (iterator.hasNext()) { + elementIDCache.put(iterator.next(), counter++); + } + } + } + } + + @Override + public int compare(EObject o1, EObject o2) { + Integer i1 = elementIDCache.get(o1); + Integer i2 = elementIDCache.get(o2); + return i1.compareTo(i2); + } + + public int getElementID(EObject ele) { + Integer result = elementIDCache.get(ele); + if (result == null) { + Grammar grammar = GrammarUtil.getGrammar(ele); + if (!elementIDCache.containsKey(grammar)) { + String grammarName = grammar.getName() + "#" + System.identityHashCode(grammar); + List indexed = Lists.newArrayList(); + for (EObject o : elementIDCache.keySet()) + if (o instanceof Grammar) + indexed.add(((Grammar) o).getName() + "#" + System.identityHashCode(o)); + throw new IllegalStateException("No ID found. Wrong grammar. \nRequested: " + grammarName + + "\nAvailable: " + Joiner.on(", ").join(indexed)); + } else + throw new IllegalStateException("No ID found. Not indexed. \nElement: " + EmfFormatter.objPath(ele)); + } + return result; + } + + public Comparator toComparator(final Function elementAccess) { + return new Comparator() { + @Override + public int compare(T o1, T o2) { + return GrammarElementDeclarationOrder.this.compare(elementAccess.apply(o1), elementAccess.apply(o2)); + } + }; + + } +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/IGrammarConstraintProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/IGrammarConstraintProvider.java index e5dafd2e9..e102f530c 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/IGrammarConstraintProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/IGrammarConstraintProvider.java @@ -1,5 +1,6 @@ package org.eclipse.xtext.serializer.analysis; +import java.util.Collection; import java.util.List; import org.eclipse.emf.ecore.EClass; @@ -7,11 +8,19 @@ import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.xtext.AbstractElement; import org.eclipse.xtext.Action; -import org.eclipse.xtext.CrossReference; +import org.eclipse.xtext.Alternatives; +import org.eclipse.xtext.EnumRule; import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.Group; import org.eclipse.xtext.Keyword; +import org.eclipse.xtext.ParserRule; import org.eclipse.xtext.RuleCall; -import org.eclipse.xtext.util.Pair; +import org.eclipse.xtext.TerminalRule; +import org.eclipse.xtext.UnorderedGroup; +import org.eclipse.xtext.serializer.analysis.ISemanticSequencerNfaProvider.ISemState; +import org.eclipse.xtext.util.EmfFormatter; +import org.eclipse.xtext.util.formallang.Nfa; import org.eclipse.xtext.util.formallang.Production; import com.google.inject.ImplementedBy; @@ -49,7 +58,49 @@ public interface IGrammarConstraintProvider { ASSIGNED_KEYWORD, // ASSIGNED_PARSER_RULE_CALL, // ASSIGNED_TERMINAL_RULE_CALL, // - GROUP, + GROUP, // + UNORDERED_GROUP; + + public static ConstraintElementType getConstraintElementType(AbstractElement ele) { + if (ele instanceof Action) { + if (((Action) ele).getFeature() != null) + return ConstraintElementType.ASSIGNED_ACTION_CALL; + } else if (ele instanceof Alternatives) { + return ConstraintElementType.ALTERNATIVE; + } else if (ele instanceof Group) { + return ConstraintElementType.GROUP; + } else if (ele instanceof UnorderedGroup) { + return ConstraintElementType.UNORDERED_GROUP; + } else if (GrammarUtil.containingCrossReference(ele) != null) { + if (ele instanceof RuleCall) { + RuleCall rc = (RuleCall) ele; + if (rc.getRule() instanceof ParserRule) + return ConstraintElementType.ASSIGNED_CROSSREF_DATATYPE_RULE_CALL; + if (rc.getRule() instanceof TerminalRule) + return ConstraintElementType.ASSIGNED_CROSSREF_TERMINAL_RULE_CALL; + if (rc.getRule() instanceof EnumRule) + return ConstraintElementType.ASSIGNED_CROSSREF_ENUM_RULE_CALL; + } else if (ele instanceof Keyword) + return ConstraintElementType.ASSIGNED_CROSSREF_KEYWORD; + } else if (GrammarUtil.containingAssignment(ele) != null) { + if (ele instanceof RuleCall) { + RuleCall rc = (RuleCall) ele; + if (rc.getRule() instanceof ParserRule) { + if (rc.getRule().getType().getClassifier() instanceof EClass) + return ConstraintElementType.ASSIGNED_PARSER_RULE_CALL; + return ConstraintElementType.ASSIGNED_DATATYPE_RULE_CALL; + } + if (rc.getRule() instanceof TerminalRule) + return ConstraintElementType.ASSIGNED_TERMINAL_RULE_CALL; + if (rc.getRule() instanceof EnumRule) + return ConstraintElementType.ASSIGNED_ENUM_RULE_CALL; + + } else if (ele instanceof Keyword) { + return ConstraintElementType.ASSIGNED_KEYWORD; + } + } + throw new RuntimeException("Unknown Grammar Element: " + EmfFormatter.objPath(ele)); + } } /** @@ -61,26 +112,11 @@ public interface IGrammarConstraintProvider { */ public interface IConstraint extends Comparable { - /** - * @return a list of all assignments represented by this constraint. - * {@link IConstraintElement#getAssignmentID()} returns an Assignment's index in this list. The order of - * the list reflects the order of the assignments in the constraint. Assignments are - * {@link IConstraintElement}s with {@link IConstraintElement#getType()} == ASSIGNED_* - */ - IConstraintElement[] getAssignments(); - /** * @return the root of the tree of {@link IConstraintElement} that defines this constraint. */ IConstraintElement getBody(); - /** - * @return a list of all elements represented by this constraint. This is a flattened version of the tree - * returned by {@link #getBody()}. {@link IConstraintElement#getElementID()} returns an Assignment's - * index in this list. The order of the list reflects the order of the elements in the constraint. - */ - IConstraintElement[] getElements(); - /** * @return a list of {@link IFeatureInfo} for all {@link EStructuralFeature}s from the {@link EClass} returned * by {@link #getType()} that have an assignment if this constraint. If there is no assignment for an @@ -88,10 +124,6 @@ public interface IGrammarConstraintProvider { */ IFeatureInfo[] getFeatures(); - Iterable getSingleAssignementFeatures(); - - Iterable getMultiAssignementFeatures(); - /** * @return a name that is unique for a grammar and that aims to be human-readable. */ @@ -103,6 +135,10 @@ public interface IGrammarConstraintProvider { * @return This constraint only applies to EObjects of this type. */ EClass getType(); + + List getContexts(); + + Nfa getNfa(); } /** @@ -192,77 +228,33 @@ public interface IGrammarConstraintProvider { */ public interface IConstraintElement { - // valid for *_ACTION_CALL - Action getAction(); - - int getAssignmentID(); - EObject getCallContext(); - String getCardinality(); - // valid for GROUP and ALTERNATIVE, null otherwise - List getChildren(); + Collection getChildren(); IConstraintElement getContainer(); IConstraint getContainingConstraint(); - // valid for *_CROSSREF_* - CrossReference getCrossReference(); - - // valid for *_CROSSREF_* - EClass getCrossReferenceType(); - - int getElementID(); - // valid for ASSIGNED_* - EStructuralFeature getFeature(); - - int getFeatureAssignmentID(); - - // valid for ASSIGNED_* - IFeatureInfo getFeatureInfo(); + // IFeatureInfo getFeatureInfo(); // returns a RuleCall, Keyword or Action. But never an Assignment or // Cross Reference. AbstractElement getGrammarElement(); - // valid for *_KEYWORD - Keyword getKeyword(); - - // valid for *_RULE_CALL - RuleCall getRuleCall(); - ConstraintElementType getType(); - List> getDependingAssignment(); - - List getContainedAssignments(); - - boolean isCardinalityOneAmongAssignments(List assignments); - boolean isMany(); - /** - * @return true, if this element or one of its containers is isMany(). - */ - boolean isManyRecursive(IConstraintElement root); - boolean isOptional(); - boolean isRoot(); - - /** - * @return true, if this element or one of its containers is optional. Also true, if one of the containers is an - * alternative. - */ - boolean isOptionalRecursive(IConstraintElement root); } public interface IFeatureInfo { - IConstraintElement[] getAssignments(); + List getAssignments(); List getCalledContexts(); @@ -270,51 +262,11 @@ public interface IGrammarConstraintProvider { EStructuralFeature getFeature(); - /** - * @return true for ASSIGNED_*, if there are multiple IConstraintELements for the same EStructuralFeature, which - * refer to different keywords, rulecalls or cross references. - */ - boolean isContentValidationNeeded(); - - List> getDependingFeatures(); - int getUpperBound(); int getLowerBound(); } - public enum RelationalDependencyType { - /** - * (b >= 1) => (a == 0) - */ - EXCLUDE_IF_SET, - - /** - * (b == 0) => (a == 0) - */ - EXCLUDE_IF_UNSET, - - /** - * (b >= 1) => (a >= 0) - */ - MANDATORY_IF_SET, - - /** - * a == b - */ - SAME, - - /** - * a >= b - */ - SAME_OR_MORE, - - /** - * a <= b - */ - SAME_OR_LESS - } - final int MAX = Integer.MAX_VALUE; /** diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ISemanticSequencerNfaProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ISemanticSequencerNfaProvider.java index 1b8eeff49..761a4a2cc 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ISemanticSequencerNfaProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ISemanticSequencerNfaProvider.java @@ -16,6 +16,7 @@ import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.xtext.AbstractElement; import org.eclipse.xtext.util.formallang.Nfa; +import com.google.common.base.Function; import com.google.inject.ImplementedBy; /** @@ -24,6 +25,13 @@ import com.google.inject.ImplementedBy; @ImplementedBy(SemanticSequencerNfaProvider.class) public interface ISemanticSequencerNfaProvider { + public Function GET_ASSIGNED_GRAMMAR_ELEMENT = new Function() { + @Override + public AbstractElement apply(ISemState input) { + return input.getAssignedGrammarElement(); + } + }; + public interface ISemState { BitSet getAllFollowerFeatures(); @@ -38,7 +46,7 @@ public interface ISemanticSequencerNfaProvider { int getOrderID(); List getToBeValidatedAssignedElements(); - + boolean isBooleanAssignment(); } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SemanticSequencerNfaProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SemanticSequencerNfaProvider.java index 1e65affd5..5068cdf5a 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SemanticSequencerNfaProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/SemanticSequencerNfaProvider.java @@ -23,9 +23,8 @@ import org.eclipse.xtext.AbstractElement; import org.eclipse.xtext.Action; import org.eclipse.xtext.Assignment; import org.eclipse.xtext.CrossReference; -import org.eclipse.xtext.EcoreUtil2; +import org.eclipse.xtext.Grammar; import org.eclipse.xtext.GrammarUtil; -import org.eclipse.xtext.ParserRule; import org.eclipse.xtext.grammaranalysis.impl.GrammarElementTitleSwitch; import org.eclipse.xtext.serializer.analysis.ISyntacticSequencerPDAProvider.ISynAbsorberState; import org.eclipse.xtext.serializer.analysis.ISyntacticSequencerPDAProvider.SynAbsorberNfaAdapter; @@ -34,8 +33,8 @@ import org.eclipse.xtext.util.Pair; import org.eclipse.xtext.util.Tuples; import org.eclipse.xtext.util.formallang.Nfa; import org.eclipse.xtext.util.formallang.NfaFactory; +import org.eclipse.xtext.util.formallang.NfaGraphFormatter; import org.eclipse.xtext.util.formallang.NfaUtil; -import org.eclipse.xtext.xtext.RuleNames; import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; @@ -55,6 +54,7 @@ public class SemanticSequencerNfaProvider implements ISemanticSequencerNfaProvid protected final ISemState start; protected final ISemState stop; + protected int hashCode = -1; public SemNfa(ISemState starts, ISemState stops) { super(); @@ -62,6 +62,22 @@ public class SemanticSequencerNfaProvider implements ISemanticSequencerNfaProvid this.stop = stops; } + @Override + public int hashCode() { + if (hashCode == -1) + hashCode = new NfaUtil().hashCodeIgnoreOrder(this, GET_ASSIGNED_GRAMMAR_ELEMENT); + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != getClass()) + return false; + if (obj == this) + return true; + return new NfaUtil().equalsIgnoreOrder(this, (SemNfa) obj, GET_ASSIGNED_GRAMMAR_ELEMENT); + } + @Override public List getFollowers(ISemState node) { return node.getFollowers(); @@ -77,6 +93,11 @@ public class SemanticSequencerNfaProvider implements ISemanticSequencerNfaProvid return stop; } + @Override + public String toString() { + return new NfaGraphFormatter().format(this); + } + } protected static class SemState implements ISemState { @@ -176,11 +197,6 @@ public class SemanticSequencerNfaProvider implements ISemanticSequencerNfaProvid } - protected Map elementIDCache; - - @Inject - protected RuleNames ruleNames; - @Inject protected ISyntacticSequencerPDAProvider pdaProvider; @@ -196,17 +212,6 @@ public class SemanticSequencerNfaProvider implements ISemanticSequencerNfaProvid return true; } - protected int getElementID(AbstractElement ele) { - if (elementIDCache == null) { - elementIDCache = Maps.newHashMap(); - int counter = 0; - for (ParserRule pr : ruleNames.getAllParserRules()) - for (AbstractElement e : EcoreUtil2.getAllContentsOfType(pr, AbstractElement.class)) - elementIDCache.put(e, counter++); - } - return elementIDCache.get(ele); - } - @Override public Nfa getNFA(EObject context, EClass type) { Pair key = Tuples.create(context, type); @@ -222,7 +227,7 @@ public class SemanticSequencerNfaProvider implements ISemanticSequencerNfaProvid if (type != null) initContentValidationNeeded(type, nfa); initRemainingFeatures(nfa.getStop(), util.inverse(nfa), Sets. newHashSet()); - initOrderIDs(nfa); + initOrderIDs(GrammarUtil.getGrammar(context), nfa); // System.out.println(new NfaFormatter().format(nfa)); resultCache.put(key, nfa); return nfa; @@ -244,10 +249,11 @@ public class SemanticSequencerNfaProvider implements ISemanticSequencerNfaProvid ((SemState) state).contentValidationNeeded = Collections.emptyList(); } - protected void initOrderIDs(Nfa nfa) { + protected void initOrderIDs(Grammar grammar, Nfa nfa) { + GrammarElementDeclarationOrder order = GrammarElementDeclarationOrder.get(grammar); for (ISemState state : new NfaUtil().collect(nfa)) if (state.getAssignedGrammarElement() != null) - ((SemState) state).orderID = getElementID(state.getAssignedGrammarElement()); + ((SemState) state).orderID = order.getElementID((state.getAssignedGrammarElement())); } protected void initRemainingFeatures(ISemState state, Nfa inverseNfa, Set visited) { diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/TypeFinderNFAProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/TypeFinderNFAProvider.java deleted file mode 100644 index 0a779c3a2..000000000 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/TypeFinderNFAProvider.java +++ /dev/null @@ -1,81 +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 org.eclipse.xtext.AbstractElement; -import org.eclipse.xtext.Action; -import org.eclipse.xtext.Assignment; -import org.eclipse.xtext.GrammarUtil; -import org.eclipse.xtext.grammaranalysis.impl.AbstractCachingNFABuilder; -import org.eclipse.xtext.grammaranalysis.impl.AbstractNFAProvider; -import org.eclipse.xtext.grammaranalysis.impl.AbstractNFAState; -import org.eclipse.xtext.grammaranalysis.impl.AbstractNFATransition; -import org.eclipse.xtext.serializer.analysis.TypeFinderNFAProvider.TypeFinderState; -import org.eclipse.xtext.serializer.analysis.TypeFinderNFAProvider.TypeFinderTransition; - -import com.google.inject.Singleton; - -@Singleton -public class TypeFinderNFAProvider extends AbstractNFAProvider { - public static class TypeFinderState extends AbstractNFAState { - - public TypeFinderState(AbstractElement element, NFABuilder builder) { - super(element, builder); - } - - } - - public static class TypeFinderTransition extends AbstractNFATransition { - - public TypeFinderTransition(TypeFinderState source, TypeFinderState target, boolean ruleCall, - AbstractElement loopCenter) { - super(source, target, ruleCall, loopCenter); - } - } - - public static class TypeFinderNFABuilder extends AbstractCachingNFABuilder { - - @Override - public TypeFinderState createState(AbstractElement ele) { - return new TypeFinderState(ele, this); - } - - @Override - protected TypeFinderTransition createTransition(TypeFinderState source, TypeFinderState target, - boolean isRuleCall, AbstractElement loopCenter) { - return new TypeFinderTransition(source, target, isRuleCall, loopCenter); - } - - @Override - public boolean filter(AbstractElement ele) { - - // never filter root elements - if (!(ele.eContainer() instanceof AbstractElement)) - return false; - - if (ele instanceof Assignment || ele instanceof Action) - return false; - - if (GrammarUtil.isUnassignedEObjectRuleCall(ele)) - return false; - - return true; - } - - @Override - public NFADirection getDirection() { - return NFADirection.BACKWARD; - } - } - - @Override - protected NFABuilder createBuilder() { - return new TypeFinderNFABuilder(); - } - -} \ No newline at end of file diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/sequencer/ContextFinder.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/sequencer/ContextFinder.java index 24c2f5aac..acbfbd434 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/sequencer/ContextFinder.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/sequencer/ContextFinder.java @@ -62,10 +62,10 @@ public class ContextFinder implements IContextFinder { @Inject protected TransientValueUtil transientValueUtil; - + @Inject protected ITransientValueService transientValues; - + @Inject protected ISemanticNodeProvider nodesProvider; @@ -208,7 +208,8 @@ public class ContextFinder implements IContextFinder { return result; } - protected Map> getConstraints(EObject semanticObject, Iterable contextCandidates) { + protected Map> getConstraints(EObject semanticObject, + Iterable contextCandidates) { Map> result = Maps.newLinkedHashMap(); for (EObject ctx : contextCandidates) { IConstraint constraint = constraints.get(Tuples.create(ctx, semanticObject.eClass())); @@ -248,26 +249,30 @@ public class ContextFinder implements IContextFinder { return false; } - protected boolean isMandatory(IFeatureInfo feature) { - if (feature == null) - return false; - for (IConstraintElement ce : feature.getAssignments()) - if (!ce.isOptionalRecursive(null)) - return true; - return false; - } - protected boolean isValidValueQuantity(IConstraint constraint, EObject semanicObj) { if (constraint == null) return false; for (int featureID = 0; featureID < semanicObj.eClass().getFeatureCount(); featureID++) { IFeatureInfo featureInfo = constraint.getFeatures()[featureID]; EStructuralFeature structuralFeature = semanicObj.eClass().getEStructuralFeature(featureID); + // TODO validated bounds of lists properly ValueTransient trans = transientValueUtil.isTransient(semanicObj, structuralFeature); - if (trans == ValueTransient.NO && featureInfo == null) - return false; - if (trans == ValueTransient.YES && isMandatory(featureInfo)) - return false; + switch (trans) { + case NO: + if (featureInfo == null) + return false; + if (featureInfo.getUpperBound() <= 0) + return false; + break; + case YES: + if (featureInfo == null) + break; + if (featureInfo.getLowerBound() > 0) + return false; + break; + case PREFERABLY: + break; + } } return true; } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/sequencer/GenericSemanticSequencer.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/sequencer/GenericSemanticSequencer.java deleted file mode 100644 index 91a32c6c9..000000000 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/sequencer/GenericSemanticSequencer.java +++ /dev/null @@ -1,931 +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.sequencer; - -import java.util.Arrays; -import java.util.BitSet; -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.Action; -import org.eclipse.xtext.GrammarUtil; -import org.eclipse.xtext.IGrammarAccess; -import org.eclipse.xtext.Keyword; -import org.eclipse.xtext.RuleCall; -import org.eclipse.xtext.nodemodel.ICompositeNode; -import org.eclipse.xtext.nodemodel.ILeafNode; -import org.eclipse.xtext.nodemodel.INode; -import org.eclipse.xtext.serializer.analysis.IGrammarConstraintProvider; -import org.eclipse.xtext.serializer.analysis.IGrammarConstraintProvider.IConstraint; -import org.eclipse.xtext.serializer.analysis.IGrammarConstraintProvider.IConstraintContext; -import org.eclipse.xtext.serializer.analysis.IGrammarConstraintProvider.IConstraintElement; -import org.eclipse.xtext.serializer.analysis.IGrammarConstraintProvider.IFeatureInfo; -import org.eclipse.xtext.serializer.analysis.IGrammarConstraintProvider.RelationalDependencyType; -import org.eclipse.xtext.serializer.sequencer.ISemanticNodeProvider.INodesForEObjectProvider; -import org.eclipse.xtext.serializer.sequencer.ITransientValueService.ValueTransient; -import org.eclipse.xtext.serializer.tokens.ICrossReferenceSerializer; -import org.eclipse.xtext.serializer.tokens.IEnumLiteralSerializer; -import org.eclipse.xtext.serializer.tokens.IKeywordSerializer; -import org.eclipse.xtext.serializer.tokens.IValueSerializer; -import org.eclipse.xtext.util.EmfFormatter; -import org.eclipse.xtext.util.Pair; -import org.eclipse.xtext.util.Tuples; - -import com.google.common.base.Joiner; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.google.inject.Inject; - -/** - * @author Moritz Eysholdt - Initial contribution and API - * @deprecated use {@link BacktrackingSemanticSequencer} - */ -@Deprecated -public class GenericSemanticSequencer extends AbstractSemanticSequencer { - - protected abstract class Allocation { - - public Allocation() { - super(); - } - - public abstract void accept(EObject semanticObj, IConstraintElement constraint); - - @Override - public String toString() { - return toString(""); - } - - public abstract String toString(String prefix); - } - - protected class AllocationValue extends Allocation { - protected int index; - - protected INode node; - - protected Object value; - - protected boolean optional; - - public AllocationValue(Object value, int index, boolean optional, INode node) { - super(); - this.value = value; - this.index = index; - this.optional = optional; - this.node = node; - } - - @Override - public void accept(EObject semanticObj, IConstraintElement constraint) { - acceptSemantic(semanticObj, constraint, value, index, node); - } - - public INode getNode() { - return node; - } - - public Object getValue() { - return value; - } - - @Override - public String toString(String prefix) { - return value instanceof EObject ? EmfFormatter.objPath((EObject) value) : value.toString(); - } - } - - protected class AlternativeAllocation extends Allocation { - protected Quantity child; - - public AlternativeAllocation(Quantity child) { - super(); - this.child = child; - } - - @Override - public void accept(EObject semanticObj, IConstraintElement constraint) { - child.accept(semanticObj); - } - - protected Quantity getChild() { - return child; - } - - @Override - public String toString(String prefix) { - String newPrefix = " " + prefix; - return "Alt-Choice {\n" + newPrefix + child.toString(newPrefix) + "\n" + prefix + "}"; - } - } - - protected abstract class Feature2Assignment { - public abstract IFeatureInfo getFeature(); - - public abstract List getValuesFor(IConstraintElement assignment); - - public abstract boolean isAmbiguous(); - - @Override - public String toString() { - List result = Lists.newArrayList(); - for (IConstraintElement assign : getFeature().getAssignments()) { - result.add(assign + "=>(" + Joiner.on(", ").join(getValuesFor(assign)) + ")"); - } - return Joiner.on(", ").join(result); - } - } - - protected class GroupAllocation extends Allocation { - protected List children = Lists.newArrayList(); - - public GroupAllocation() { - super(); - } - - public GroupAllocation(List children) { - super(); - this.children = children; - } - - @Override - public void accept(EObject semanticObj, IConstraintElement constraint) { - for (Quantity q : children) - q.accept(semanticObj); - } - - public void addChild(Quantity quantity) { - children.add(quantity); - } - - public List getChildren() { - return children; - } - - @Override - public String toString(String prefix) { - String newPrefix = " " + prefix; - StringBuilder r = new StringBuilder(); - r.append("Group {"); - for (Quantity child : children) { - r.append("\n"); - r.append(newPrefix); - r.append(child.getConstraintElement()); - r.append(" => "); - r.append(child.toString(newPrefix)); - } - r.append("\n"); - r.append(prefix); - r.append("}"); - return r.toString(); - } - - } - - protected class MVFeature2AssignmentAmbiguous extends Feature2Assignment { - protected List assignments; - - protected int[] quantities; //TODO: implement - - protected List values; - - public MVFeature2AssignmentAmbiguous(List assignments, List values) { - super(); - this.assignments = assignments; - this.values = values; - this.quantities = new int[assignments.get(0).getFeatureInfo().getAssignments().length]; - } - - @Override - public IFeatureInfo getFeature() { - return assignments.get(0).getFeatureInfo(); - } - - @Override - public List getValuesFor(IConstraintElement assignment) { - return assignments.contains(assignment) ? values : Collections. emptyList(); - } - - @Override - public boolean isAmbiguous() { - int undefs = 0; - for (IConstraintElement ass : assignments) - if (quantities[ass.getFeatureAssignmentID()] == UNDEFINED_QUANTITY) - undefs++; - return undefs > 1; - } - - } - - protected class MVFeature2AssignmentUnambiguous extends Feature2Assignment { - - protected IConstraintElement assignment; - - protected List values; - - public MVFeature2AssignmentUnambiguous(IConstraintElement assignment, List values) { - super(); - this.assignment = assignment; - this.values = values; - } - - @Override - public IFeatureInfo getFeature() { - return assignment.getFeatureInfo(); - } - - @Override - public List getValuesFor(IConstraintElement assignment) { - return assignment == this.assignment ? values : Collections. emptyList(); - } - - @Override - public boolean isAmbiguous() { - return false; - } - - } - - protected static class Quantity { - - protected IConstraintElement constraintElement; - - protected List instances; - - public Quantity(IConstraintElement constraintElement, Allocation allocation) { - this.instances = Collections.singletonList(allocation); - this.constraintElement = constraintElement; - } - - public Quantity(IConstraintElement constraintElement, List allocation) { - this.instances = allocation; - this.constraintElement = constraintElement; - } - - public void accept(EObject semanticObj) { - if (instances != null) - for (Allocation a : instances) - a.accept(semanticObj, constraintElement); - } - - public List getAllocations() { - return instances; - } - - public IConstraintElement getConstraintElement() { - return constraintElement; - } - - @Override - public String toString() { - return toString(""); - } - - public String toString(String prefix) { - if (instances == null) - return "(null)"; - if (instances.isEmpty()) - return "(empty)"; - else if (!constraintElement.isMany() && instances.size() < 2) { - return instances.get(0).toString(prefix); - } else { - StringBuilder buf = new StringBuilder(); - buf.append("["); - for (Allocation a : instances) { - buf.append("\n"); - buf.append(prefix + " "); - buf.append(a.toString(prefix + " ")); - } - buf.append("\n"); - buf.append(prefix); - buf.append("]"); - return buf.toString(); - } - } - } - - protected abstract class SVFeature2Assignment extends Feature2Assignment { - - protected boolean optional; - - protected AllocationValue value; - - public SVFeature2Assignment(boolean optional, AllocationValue value) { - super(); - this.optional = optional; - this.value = value; - } - - } - - protected class SVFeature2AssignmentAmbiguous extends SVFeature2Assignment { - - protected List assignments; - - protected Boolean[] enabled; - - public SVFeature2AssignmentAmbiguous(List assignments, boolean optional, - AllocationValue value) { - super(optional, value); - this.assignments = assignments; - this.enabled = new Boolean[assignments.get(0).getFeatureInfo().getAssignments().length]; - Arrays.fill(this.enabled, null); - } - - @Override - public IFeatureInfo getFeature() { - return assignments.get(0).getFeatureInfo(); - } - - @Override - public List getValuesFor(IConstraintElement assignment) { - if (assignments.contains(assignment)) { - Boolean en = enabled[assignment.getFeatureAssignmentID()]; - if (en == null && !isAmbiguous()) { - for (IConstraintElement ass : assignments) - if (enabled[ass.getFeatureAssignmentID()] == Boolean.TRUE) - return Collections.emptyList(); - return Collections.singletonList(value); - } - if (Boolean.TRUE.equals(en)) - return Collections.singletonList(value); - } - return Collections.emptyList(); - } - - @Override - public boolean isAmbiguous() { - int undefined = 0; - for (IConstraintElement ass : assignments) - if (enabled[ass.getFeatureAssignmentID()] == null) - undefined++; - return undefined > 1; - } - - } - - protected class SVFeature2AssignmentUnambiguous extends SVFeature2Assignment { - - protected IConstraintElement assignment; - - public SVFeature2AssignmentUnambiguous(IConstraintElement assignment, boolean optional, AllocationValue value) { - super(optional, value); - this.assignment = assignment; - } - - @Override - public IFeatureInfo getFeature() { - return assignment.getFeatureInfo(); - } - - @Override - public List getValuesFor(IConstraintElement assignment) { - if (assignment == this.assignment) - return Collections.singletonList(value); - return Collections.emptyList(); - } - - @Override - public boolean isAmbiguous() { - return false; - } - - } - - public final static int MAX = Integer.MAX_VALUE; - - protected final static int NO_ASSIGNMENT = -2; - - public final static int UNDEF = -1; - - protected final static int UNDEFINED_QUANTITY = -1; - - protected List constraintContexts; - - protected Map, IConstraint> constraints; - - @Inject - protected ICrossReferenceSerializer crossRefSerializer; - - @Inject - protected IEnumLiteralSerializer enumLiteralSerializer; - - @Inject - protected IGrammarAccess grammarAccess; - - @Inject - protected IGrammarConstraintProvider grammarConstraintProvider; - - @Inject - protected IKeywordSerializer keywordSerializer; - - @Inject - protected ITransientValueService transientValueService; - - @Inject - protected IValueSerializer valueSerializer; - - protected void acceptAction(Action action, EObject semanticChild, ICompositeNode node) { - if (sequenceAcceptor.enterAssignedAction(action, semanticChild, node)) { - masterSequencer.createSequence(action, semanticChild); - sequenceAcceptor.leaveAssignedAction(action, semanticChild); - } - } - - protected void acceptEObjectRuleCall(RuleCall ruleCall, EObject semanticChild, ICompositeNode node) { - if (sequenceAcceptor.enterAssignedParserRuleCall(ruleCall, semanticChild, node)) { - masterSequencer.createSequence(ruleCall.getRule(), semanticChild); - sequenceAcceptor.leaveAssignedParserRuleCall(ruleCall, semanticChild); - } - } - - protected boolean acceptSemantic(EObject semanticObj, IConstraintElement constr, Object value, int index, INode node) { - switch (constr.getType()) { - case ASSIGNED_ACTION_CALL: - acceptAction(constr.getAction(), (EObject) value, (ICompositeNode) node); - return true; - case ASSIGNED_PARSER_RULE_CALL: - acceptEObjectRuleCall(constr.getRuleCall(), (EObject) value, (ICompositeNode) node); - return true; - case ASSIGNED_CROSSREF_DATATYPE_RULE_CALL: - RuleCall datatypeRC = constr.getRuleCall(); - EObject value1 = (EObject) value; - ICompositeNode node1 = (ICompositeNode) node; - String token1 = crossRefSerializer.serializeCrossRef(semanticObj, - GrammarUtil.containingCrossReference(datatypeRC), value1, node1, errorAcceptor); - sequenceAcceptor.acceptAssignedCrossRefDatatype(datatypeRC, token1, value1, index, node1); - return true; - case ASSIGNED_CROSSREF_TERMINAL_RULE_CALL: - RuleCall terminalRC = constr.getRuleCall(); - EObject value2 = (EObject) value; - ILeafNode node2 = (ILeafNode) node; - String token2 = crossRefSerializer.serializeCrossRef(semanticObj, - GrammarUtil.containingCrossReference(terminalRC), value2, node2, errorAcceptor); - sequenceAcceptor.acceptAssignedCrossRefTerminal(terminalRC, token2, value2, index, node2); - return true; - case ASSIGNED_CROSSREF_ENUM_RULE_CALL: - RuleCall enumRC = constr.getRuleCall(); - ICompositeNode node3 = (ICompositeNode) node; - EObject target3 = (EObject) value; - String token3 = crossRefSerializer.serializeCrossRef(semanticObj, - GrammarUtil.containingCrossReference(enumRC), target3, node3, errorAcceptor); - sequenceAcceptor.acceptAssignedCrossRefEnum(enumRC, token3, target3, index, node3); - return true; - case ASSIGNED_CROSSREF_KEYWORD: - Keyword kw0 = constr.getKeyword(); - ILeafNode node0 = (ILeafNode) node; - EObject target0 = (EObject) value; - String token0 = crossRefSerializer.serializeCrossRef(semanticObj, - GrammarUtil.containingCrossReference(kw0), target0, node0, errorAcceptor); - sequenceAcceptor.acceptAssignedCrossRefKeyword(kw0, token0, target0, index, node0); - return true; - case ASSIGNED_DATATYPE_RULE_CALL: - RuleCall datatypeRC1 = constr.getRuleCall(); - ICompositeNode node4 = (ICompositeNode) node; - String token4 = valueSerializer.serializeAssignedValue(semanticObj, datatypeRC1, value, node4, - errorAcceptor); - sequenceAcceptor.acceptAssignedDatatype(datatypeRC1, token4, value, index, node4); - return true; - case ASSIGNED_ENUM_RULE_CALL: - RuleCall enumRC1 = constr.getRuleCall(); - ICompositeNode node5 = (ICompositeNode) node; - String token5 = enumLiteralSerializer.serializeAssignedEnumLiteral(semanticObj, enumRC1, value, node5, - errorAcceptor); - sequenceAcceptor.acceptAssignedEnum(enumRC1, token5, value, index, node5); - return true; - case ASSIGNED_TERMINAL_RULE_CALL: - RuleCall terminalRC1 = constr.getRuleCall(); - ILeafNode node6 = (ILeafNode) node; - String token6 = valueSerializer.serializeAssignedValue(semanticObj, terminalRC1, value, node6, - errorAcceptor); - sequenceAcceptor.acceptAssignedTerminal(terminalRC1, token6, value, index, node6); - return true; - case ASSIGNED_KEYWORD: - Keyword keyword = constr.getKeyword(); - ILeafNode node7 = (ILeafNode) node; - String token7 = keywordSerializer.serializeAssignedKeyword(semanticObj, keyword, value, node7, - errorAcceptor); - sequenceAcceptor.acceptAssignedKeyword(keyword, token7, value, index, node7); - return true; - case ALTERNATIVE: - case GROUP: - return false; - } - return false; - } - - // protected void applydeterministicQuantities(IConstraint constraint, Feature2Assignment[] values) { - // boolean changed; - // do { - // changed = false; - // for (IConstraintElement assignment : constraint.getAssignments()) - // if (values[assignment.getAssignmentID()] != null && values[assignment.getAssignmentID()].isAmbiguous()) { - // int min = getMin(values, assignment); - // int max = getMax(values, assignment); - // if (min == max && min != UNDEF) { - // values[assignment.getAssignmentID()].setQuantity(assignment, min); - // changed = true; - // // System.out.println("Setting quantity of " + assignment + " to " + min); - // } - // } - // } while (changed); - // } - - protected boolean containsUnavailableFeature(Feature2Assignment[] values, IConstraintElement element, - IConstraintElement excludeAssignment) { - if (element.isOptional()) - return false; - switch (element.getType()) { - case GROUP: - for (IConstraintElement a : element.getChildren()) - if (containsUnavailableFeature(values, a, excludeAssignment)) - return true; - return false; - case ALTERNATIVE: - for (IConstraintElement a : element.getChildren()) - if (!containsUnavailableFeature(values, a, excludeAssignment)) - return false; - return true; - case ASSIGNED_ACTION_CALL: - case ASSIGNED_CROSSREF_DATATYPE_RULE_CALL: - case ASSIGNED_CROSSREF_ENUM_RULE_CALL: - case ASSIGNED_CROSSREF_TERMINAL_RULE_CALL: - case ASSIGNED_CROSSREF_KEYWORD: - case ASSIGNED_DATATYPE_RULE_CALL: - case ASSIGNED_ENUM_RULE_CALL: - case ASSIGNED_KEYWORD: - case ASSIGNED_PARSER_RULE_CALL: - case ASSIGNED_TERMINAL_RULE_CALL: - Feature2Assignment f2a = values[element.getAssignmentID()]; - if (f2a == null) - return true; - if (f2a.isAmbiguous()) - return false; - if (f2a.getValuesFor(element).isEmpty()) - return true; - return false; - } - return false; - } - - @Override - public void createSequence(EObject context, EObject semanticObject) { - initConstraints(); - IConstraint constraint = getConstraint(context, semanticObject.eClass()); - // System.out.println("Constraint: " + constraint); - if (constraint == null) { - if (errorAcceptor != null) - errorAcceptor.accept(diagnosticProvider.createInvalidContextOrTypeDiagnostic(semanticObject, context)); - return; - } - INodesForEObjectProvider nodes = nodeProvider.getNodesForSemanticObject(semanticObject, null); - Feature2Assignment[] values = createValues(semanticObject, constraint, nodes); - // System.out.println("Values: " + f2aToStr(constraint.getBody(), values)); - // System.out.println("Values (Disambiguated): " + f2aToStr(constraint.getBody(), values)); - if (constraint.getBody() != null) { - Quantity quant = new Quantity(constraint.getBody(), createUnambiguousAllocation(constraint.getBody(), - values)); - // System.out.println("Quantity: " + quant + " EndQuantity"); - // List result = Lists.newArrayList(); - quant.accept(semanticObject); - } - sequenceAcceptor.finish(); - } - - protected List createUnambiguousAllocation(IConstraintElement constraint, - Feature2Assignment[] values) { - switch (constraint.getType()) { - case ALTERNATIVE: - List result = Lists.newArrayList(); - for (IConstraintElement child : constraint.getChildren()) { - List allocs = createUnambiguousAllocation(child, values); - if (allocs == null) - return null; - if (child.isMany()) { - Quantity q = new Quantity(child, allocs); - result.add(new AlternativeAllocation(q)); - } else { - for (Allocation a : allocs) { - AlternativeAllocation alloc = new AlternativeAllocation(new Quantity(child, a)); - result.add(alloc); - } - } - } - return result; - case GROUP: - int min = 0; - int max = Integer.MAX_VALUE; - List>> children = Lists - .newArrayListWithExpectedSize(constraint.getChildren().size()); - for (IConstraintElement child : constraint.getChildren()) { - List allocs = createUnambiguousAllocation(child, values); - if (allocs == null) - return null; - if (allocs.size() > 0) - min = Math.max(min, child.isMany() ? 1 : allocs.size()); - if (!child.isOptional()) - max = Math.max(max, allocs.size()); - children.add(Tuples.> create(child, allocs)); - } - if (max < min) - throw new RuntimeException("err"); // TODO: handle this error - List result2 = Lists.newArrayListWithExpectedSize(min); - for (int i = 0; i < min; i++) { - List ch = Lists.newArrayList(); - for (Pair> p : children) { - if (i < p.getSecond().size()) { - if (i == min - 1) - ch.add(new Quantity(p.getFirst(), p.getSecond().subList(i, p.getSecond().size()))); - else - ch.add(new Quantity(p.getFirst(), p.getSecond().get(i))); - } - } - result2.add(new GroupAllocation(ch)); - } - // System.out.println(constraint + " => " + result2); - return result2; - case ASSIGNED_ACTION_CALL: - case ASSIGNED_CROSSREF_DATATYPE_RULE_CALL: - case ASSIGNED_CROSSREF_ENUM_RULE_CALL: - case ASSIGNED_CROSSREF_TERMINAL_RULE_CALL: - case ASSIGNED_CROSSREF_KEYWORD: - case ASSIGNED_DATATYPE_RULE_CALL: - case ASSIGNED_ENUM_RULE_CALL: - case ASSIGNED_KEYWORD: - case ASSIGNED_PARSER_RULE_CALL: - case ASSIGNED_TERMINAL_RULE_CALL: - Feature2Assignment f2a = values[constraint.getAssignmentID()]; - if (f2a == null) - return Collections.emptyList(); - else if (!f2a.isAmbiguous()) { - return f2a.getValuesFor(constraint); - // List r = f2a.getValuesFor(constraint); - // return r.isEmpty() ? null : r; - } else - return null; - } - return null; - } - - protected Feature2Assignment[] createValues(EObject semanticObject, IConstraint constraint, - INodesForEObjectProvider nodes) { - Feature2Assignment[] result = new Feature2Assignment[constraint.getAssignments().length]; - for (IFeatureInfo feature : constraint.getSingleAssignementFeatures()) { - if (feature.getFeature().isMany()) { - List allocs = getNonTransientValuesForMVFeature(semanticObject, feature, nodes); - if (!allocs.isEmpty()) { - IConstraintElement ass = feature.getAssignments()[0]; - result[ass.getAssignmentID()] = new MVFeature2AssignmentUnambiguous(ass, allocs); - } - } else { - ValueTransient trans = transientValueService.isValueTransient(semanticObject, feature.getFeature()); - if (trans != ValueTransient.YES) { - Object value = semanticObject.eGet(feature.getFeature()); - INode node = nodes.getNodeForSingelValue(feature.getFeature(), value); - if (trans != ValueTransient.PREFERABLY || node != null) { - IConstraintElement ass = feature.getAssignments()[0]; - AllocationValue alloc = new AllocationValue(value, -1, trans == ValueTransient.PREFERABLY, node); - result[ass.getAssignmentID()] = new SVFeature2AssignmentUnambiguous(ass, - trans == ValueTransient.PREFERABLY, alloc); - } - } - } - } - for (IFeatureInfo feature : constraint.getMultiAssignementFeatures()) { - if (feature.getFeature().isMany()) { - List allocs = getNonTransientValuesForMVFeature(semanticObject, feature, nodes); - if (!allocs.isEmpty()) - createValues(semanticObject, feature, allocs, result); - } else { - ValueTransient trans = transientValueService.isValueTransient(semanticObject, feature.getFeature()); - if (trans != ValueTransient.YES) { - Object value = semanticObject.eGet(feature.getFeature()); - INode node = nodes.getNodeForSingelValue(feature.getFeature(), value); - AllocationValue alloc = new AllocationValue(value, -1, trans == ValueTransient.PREFERABLY, node); - createValues(semanticObject, feature, trans == ValueTransient.PREFERABLY, alloc, result); - } - } - } - return result; - } - - protected void createValues(EObject semanticObj, IFeatureInfo feature, boolean optional, AllocationValue value, - Feature2Assignment[] target) { - List remainingAssignments = Lists.newArrayList(); - for (IConstraintElement ass : feature.getAssignments()) - if (!isExcludedByDependees(ass, target)) - remainingAssignments.add(ass); - if (!remainingAssignments.isEmpty() && feature.isContentValidationNeeded()) - remainingAssignments = findValidAssignments(semanticObj, - remainingAssignments.toArray(new IConstraintElement[remainingAssignments.size()]), value); - if (remainingAssignments.isEmpty()) - return; // TODO: handle this error, no valid assignment has been found for the value. - else if (remainingAssignments.size() == 1) { - IConstraintElement ass = remainingAssignments.get(0); - target[ass.getAssignmentID()] = new SVFeature2AssignmentUnambiguous(ass, optional, value); - return; - } - SVFeature2AssignmentAmbiguous f2a = new SVFeature2AssignmentAmbiguous(remainingAssignments, optional, value); - for (IConstraintElement ass : remainingAssignments) - target[ass.getAssignmentID()] = f2a; - } - - protected void createValues(EObject semanticObj, IFeatureInfo feature, List values, - Feature2Assignment[] target) { - List remainingAssignments = Lists.newArrayList(); - for (IConstraintElement ass : feature.getAssignments()) - if (!isExcludedByDependees(ass, target)) - remainingAssignments.add(ass); - if (feature.isContentValidationNeeded()) - remainingAssignments = findValidAssignments(semanticObj, remainingAssignments, values); - if (remainingAssignments.size() == 0) - throw new RuntimeException("no valid assignments"); // TODO: handle this better - if (remainingAssignments.size() == 1) { - IConstraintElement ass = remainingAssignments.get(0); - target[ass.getAssignmentID()] = new MVFeature2AssignmentUnambiguous(ass, values); - return; - } - List remainingValues = Lists.newArrayList(values); - distributeValuesByQuantity(remainingAssignments, remainingValues, target); - if (remainingAssignments.size() == 1) { - IConstraintElement ass = remainingAssignments.get(0); - target[ass.getAssignmentID()] = new MVFeature2AssignmentUnambiguous(ass, remainingValues); - return; - } - MVFeature2AssignmentAmbiguous f2a = new MVFeature2AssignmentAmbiguous(remainingAssignments, remainingValues); - for (IConstraintElement ass : remainingAssignments) - target[ass.getAssignmentID()] = f2a; - } - - protected void distributeValuesByQuantity(List assignments, List values, - Feature2Assignment[] target) { - while (true) { - IConstraintElement ass = assignments.get(0); - if (ass.isCardinalityOneAmongAssignments(assignments)) { - target[ass.getAssignmentID()] = new SVFeature2AssignmentUnambiguous(ass, false, values.get(0)); - values.remove(0); - assignments.remove(0); - } else - break; - } - for (int i = assignments.size() - 1; i >= 0 && !values.isEmpty(); i--) { - IConstraintElement ass = assignments.get(i); - if (ass != null && ass.isCardinalityOneAmongAssignments(assignments)) { - target[ass.getAssignmentID()] = new SVFeature2AssignmentUnambiguous(ass, false, values.get(values - .size() - 1)); - values.remove(values.size() - 1); - assignments.remove(i); - } else - break; - } - // for (int i = assignments.size() - 1; i >= 0; i--) - // if (assignments.get(i) == null) - // assignments.remove(i); - } - - protected String f2aToStr(IConstraintElement ele, Feature2Assignment[] values) { - if (ele == null) - return "(null)"; - StringBuilder result = new StringBuilder(); - f2aToStr(ele, values, "", result); - return result.toString(); - } - - protected void f2aToStr(IConstraintElement ele, Feature2Assignment[] values, String prefix, StringBuilder result) { - result.append(prefix); - if (ele.getChildren() != null) { - result.append(ele.getType().name()); - result.append(ele.getCardinality()); - result.append(" {\n"); - for (IConstraintElement child : ele.getChildren()) - f2aToStr(child, values, prefix + " ", result); - result.append(prefix); - result.append("}\n"); - } else if (ele.getAssignmentID() >= 0) { - result.append(ele.toString()); - result.append(" => "); - Feature2Assignment value = values[ele.getAssignmentID()]; - if (value != null) { - if (value.isAmbiguous()) - result.append("ambiguous!"); - result.append(Joiner.on(", ").join(values[ele.getAssignmentID()].getValuesFor(ele))); - } - result.append("\n"); - } - } - - @Inject - protected IAssignmentFinder assignmentFinder; - - protected List findValidAssignments(EObject semanticObj, IConstraintElement[] assignments, - AllocationValue value) { - List assignedElements = Lists.newArrayList(); - for (IConstraintElement ass : assignments) - assignedElements.add(ass.getGrammarElement()); - Set assignedElements2 = Sets.newHashSet(assignmentFinder.findAssignmentsByValue(semanticObj, - assignedElements, value.getValue(), value.getNode())); - List result = Lists.newArrayList(); - for (IConstraintElement ass : assignments) - if (assignedElements2.contains(ass.getGrammarElement())) - result.add(ass); - return result; - } - - protected List findValidAssignments(EObject semanticObj, List assignments, - List values) { - BitSet bs = new BitSet(); - IConstraintElement[] assignmentsAr = assignments.toArray(new IConstraintElement[assignments.size()]); - for (AllocationValue value : values) - for (IConstraintElement validAssignments : findValidAssignments(semanticObj, assignmentsAr, value)) - bs.set(validAssignments.getFeatureAssignmentID()); - List result = Lists.newArrayList(); - for (IConstraintElement ass : assignments) - if (bs.get(ass.getFeatureAssignmentID())) - result.add(ass); - return result; - } - - protected IConstraint getConstraint(EObject context, EClass type) { - return constraints.get(Tuples.create(context, type)); - } - - protected List getNonTransientValuesForMVFeature(EObject semanticObject, IFeatureInfo feature, - INodesForEObjectProvider nodes) { - switch (transientValueService.isListTransient(semanticObject, feature.getFeature())) { - case NO: - List allocs1 = Lists.newArrayList(); - List values1 = (List) semanticObject.eGet(feature.getFeature()); - for (int i = 0; i < values1.size(); i++) { - Object value = values1.get(i); - INode node = nodes.getNodeForMultiValue(feature.getFeature(), i, i, value); - allocs1.add(new AllocationValue(value, i, false, node)); - } - return allocs1; - case SOME: - List allocs2 = Lists.newArrayList(); - List values2 = (List) semanticObject.eGet(feature.getFeature()); - for (int i = 0, j = 0; i < values2.size(); i++) - if (!transientValueService.isValueInListTransient(semanticObject, i, feature.getFeature())) { - Object value = values2.get(i); - INode node = nodes.getNodeForMultiValue(feature.getFeature(), i, j++, value); - allocs2.add(new AllocationValue(value, i, false, node)); - } - return allocs2; - case YES: - } - return Collections.emptyList(); - } - - protected void initConstraints() { - if (constraintContexts == null) { - constraints = Maps.newLinkedHashMap(); - constraintContexts = grammarConstraintProvider.getConstraints(grammarAccess.getGrammar()); - // System.out.println(Joiner.on("\n").join(constraintContexts)); - for (IConstraintContext ctx : constraintContexts) - for (IConstraint constraint : ctx.getConstraints()) - constraints.put(Tuples.create(ctx.getContext(), constraint.getType()), constraint); - } - } - - protected boolean isAmbiguous(Feature2Assignment[] allocations) { - for (Feature2Assignment feat : allocations) - if (feat.isAmbiguous()) - return true; - return false; - } - - protected boolean isExcludedByDependees(IConstraintElement assignments, Feature2Assignment[] target) { - List> dependees = assignments.getDependingAssignment(); - if (dependees == null || dependees.isEmpty()) - return false; - for (Pair e : dependees) - switch (e.getSecond()) { - case EXCLUDE_IF_SET: - if (target[e.getFirst().getAssignmentID()] != null) - return true; - break; - case SAME: - case SAME_OR_LESS: - case EXCLUDE_IF_UNSET: - if (target[e.getFirst().getAssignmentID()] == null - && e.getFirst().getFeatureInfo().getAssignments().length == 1) - return true; - break; - case MANDATORY_IF_SET: - case SAME_OR_MORE: - } - return false; - } - -} diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GenericSemanticSequencerTest.java b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GenericSemanticSequencerTest.java deleted file mode 100644 index 13a55b28a..000000000 --- a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GenericSemanticSequencerTest.java +++ /dev/null @@ -1,190 +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; - -import org.eclipse.xtext.serializer.sequencer.GenericSemanticSequencer; -import org.eclipse.xtext.serializer.sequencer.ISemanticSequencer; -import org.junit.Test; - -/** - * @author Moritz Eysholdt - Initial contribution and API - */ -@SuppressWarnings("deprecation") -public class GenericSemanticSequencerTest extends AbstractSemanticSequencerTest { - - @Override - protected ISemanticSequencer getGenericSemanticSequencer() { - return get(GenericSemanticSequencer.class); - } - - @Override - @Test public void testUnorderedAlternative1() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedAlternative2() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedAlternative3() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedAlternative4() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedAlternative5() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedAlternative6() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedAlternative7() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroup1() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroup2() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroup3() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroup4() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroup5() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroup6() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroup7() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroup8() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroupOptional1() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroupOptional2() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroupOptional3() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroupOptional4() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroupOptional5() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroupOptional6() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroupOptional7() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroupBoolean1() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroupBoolean2() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroupBoolean3() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroupBoolean4() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroupBoolean5() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroupBoolean6() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroupBoolean7() throws Exception { - // unsupported - } - - @Override - @Test public void testUnorderedGroupBoolean8() throws Exception { - // unsupported - } - - @Override - @Test public void testSingleKeyword1OrID() throws Exception { - // unsupported - } - - @Override - @Test public void testSingleKeywordOrID2() throws Exception { - // unsupported - } - - @Override - @Test public void testSingleKeywordOrID3() throws Exception { - // unsupported - } - -} diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarConstraintProviderAssignedActionTest.java b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarConstraintProviderAssignedActionTest.java index 24ff6c273..9ffb6f706 100644 --- a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarConstraintProviderAssignedActionTest.java +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarConstraintProviderAssignedActionTest.java @@ -275,9 +275,9 @@ public class GrammarConstraintProviderAssignedActionTest extends AbstractXtextTe StringBuilder expected = new StringBuilder(); expected.append("Assignment: Addition_Assignment_Multiplication_Bin | Prim_Val;\n"); expected.append(" Addition_Assignment_Multiplication_Bin returns Bin: (\n"); + expected.append(" (left+=Assignment_Bin_1_0 op='=' right=Addition) | \n"); expected.append(" (left+=Addition_Bin_1_0 op='+' right=Multiplication) | \n"); - expected.append(" (left+=Multiplication_Bin_1_0 op='*' right=Prim) | \n"); - expected.append(" (left+=Assignment_Bin_1_0 op='=' right=Addition)\n"); + expected.append(" (left+=Multiplication_Bin_1_0 op='*' right=Prim)\n"); expected.append(");\n"); expected.append(" Prim_Val returns Val: name=ID;\n"); expected.append("Assignment_Bin_1_0: Addition_Assignment_Multiplication_Bin | Prim_Val;\n"); @@ -326,17 +326,17 @@ public class GrammarConstraintProviderAssignedActionTest extends AbstractXtextTe @Test public void testActionSequence3() throws Exception { StringBuilder grammar = new StringBuilder(); - grammar.append("Rule: val1=ID ({A.a1=current} a2=ID ({A.a1=current} a2=ID ({A.a1=current} a2=ID)?)?)?;\n"); + grammar.append("Rule: v0=ID ({A.a1=current} v1=ID ({A.a1=current} v2=ID ({A.a1=current} v3=ID)?)?)?;\n"); String actual = getParserRule(grammar.toString()); StringBuilder expected = new StringBuilder(); expected.append("Rule: Rule_A | Rule_Rule;\n"); - expected.append(" Rule_A returns A: ((a1=Rule_A_1_2_2_0 a2=ID) | (a1=Rule_A_1_2_0 a2=ID) | (a1=Rule_A_1_0 a2=ID));\n"); - expected.append(" Rule_Rule returns Rule: val1=ID;\n"); + expected.append(" Rule_A returns A: ((a1=Rule_A_1_0 v1=ID) | (a1=Rule_A_1_2_0 v2=ID) | (a1=Rule_A_1_2_2_0 v3=ID));\n"); + expected.append(" Rule_Rule returns Rule: v0=ID;\n"); expected.append("Rule_A_1_0: Rule_Rule;\n"); expected.append("Rule_A_1_2_0: Rule_A_1_2_0_A;\n"); - expected.append(" Rule_A_1_2_0_A returns A: (a1=Rule_A_1_0 a2=ID);\n"); + expected.append(" Rule_A_1_2_0_A returns A: (a1=Rule_A_1_0 v1=ID);\n"); expected.append("Rule_A_1_2_2_0: Rule_A_1_2_2_0_A;\n"); - expected.append(" Rule_A_1_2_2_0_A returns A: (a1=Rule_A_1_2_0 a2=ID);"); + expected.append(" Rule_A_1_2_2_0_A returns A: (a1=Rule_A_1_2_0 v2=ID);"); assertEquals(expected.toString(), actual); } @@ -455,7 +455,7 @@ public class GrammarConstraintProviderAssignedActionTest extends AbstractXtextTe String actual = getParserRule(grammar.toString()); StringBuilder expected = new StringBuilder(); expected.append("Expr: Abs_Expr_Op | Prim_NumberLiteral;\n"); - expected.append(" Abs_Expr_Op returns Op: ((op='ABS' rhs=Prim) | ((op='+' | op='-') rhs=Abs));\n"); + expected.append(" Abs_Expr_Op returns Op: (((op='+' | op='-') rhs=Abs) | (op='ABS' rhs=Prim));\n"); expected.append(" Prim_NumberLiteral returns NumberLiteral: value=INT;\n"); expected.append("Abs: Abs_Expr_Op | Prim_NumberLiteral;\n"); expected.append("Prim: Abs_Expr_Op | Prim_NumberLiteral;"); @@ -471,10 +471,10 @@ public class GrammarConstraintProviderAssignedActionTest extends AbstractXtextTe String actual = getParserRule(grammar.toString()); StringBuilder expected = new StringBuilder(); expected.append("Ex1: Ex1_Ex2_Ex3_o | Ex3_Ex;\n"); - expected.append(" Ex1_Ex2_Ex3_o returns o: ((l=Ex2_o_1_0 o='c' r=Ex3) | (l=Ex3_o_1_0 o='d' name=ID) | (l=Ex1_o_1_0_0 o='a' r=Ex2) | (l=Ex1_o_1_1_0 o='b' r=Ex2));\n"); + expected.append(" Ex1_Ex2_Ex3_o returns o: ((l=Ex1_o_1_0_0 o='a' r=Ex2) | (l=Ex1_o_1_1_0 o='b' r=Ex2) | (l=Ex2_o_1_0 o='c' r=Ex3) | (l=Ex3_o_1_0 o='d' name=ID));\n"); expected.append(" Ex3_Ex returns Ex: name=ID;\n"); expected.append("Ex1_o_1_0_0: Ex1_Ex2_Ex3_o_1_0_0_o | Ex3_Ex;\n"); - expected.append(" Ex1_Ex2_Ex3_o_1_0_0_o returns o: ((l=Ex2_o_1_0 o='c' r=Ex3) | (l=Ex3_o_1_0 o='d' name=ID) | (l=Ex1_o_1_0_0 o='a' r=Ex2));\n"); + expected.append(" Ex1_Ex2_Ex3_o_1_0_0_o returns o: ((l=Ex1_o_1_0_0 o='a' r=Ex2) | (l=Ex2_o_1_0 o='c' r=Ex3) | (l=Ex3_o_1_0 o='d' name=ID));\n"); expected.append("Ex1_o_1_1_0: Ex2_Ex3_o | Ex3_Ex;\n"); expected.append(" Ex2_Ex3_o returns o: ((l=Ex2_o_1_0 o='c' r=Ex3) | (l=Ex3_o_1_0 o='d' name=ID));\n"); expected.append("Ex2: Ex2_Ex3_o | Ex3_Ex;\n"); diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarConstraintProviderAssignmentsTest.java b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarConstraintProviderAssignmentsTest.java deleted file mode 100644 index 06244afa1..000000000 --- a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarConstraintProviderAssignmentsTest.java +++ /dev/null @@ -1,253 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010 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.List; - -import org.eclipse.xtext.Grammar; -import org.eclipse.xtext.XtextStandaloneSetup; -import org.eclipse.xtext.junit4.AbstractXtextTests; -import org.eclipse.xtext.serializer.analysis.IGrammarConstraintProvider; -import org.eclipse.xtext.serializer.analysis.IGrammarConstraintProvider.IConstraint; -import org.eclipse.xtext.serializer.analysis.IGrammarConstraintProvider.IConstraintElement; -import org.eclipse.xtext.serializer.analysis.IGrammarConstraintProvider.RelationalDependencyType; -import org.eclipse.xtext.util.Pair; -import org.junit.Test; - -import com.google.common.base.Joiner; -import com.google.common.collect.Lists; - -/** - * @author Moritz Eysholdt - Initial contribution and API - */ -public class GrammarConstraintProviderAssignmentsTest extends AbstractXtextTests { - - @Override - public void setUp() throws Exception { - super.setUp(); - with(XtextStandaloneSetup.class); - } - - final static String HEADER = "grammar org.eclipse.xtext.validation.GrammarConstraintTestLanguage" - + " with org.eclipse.xtext.common.Terminals " - + "generate grammarConstraintTest \"http://www.eclipse.org/2010/tmf/xtext/GCT\" "; - - private String getParserRule(String body) throws Exception { - Grammar grammar = (Grammar) getModel(HEADER + body); - IGrammarConstraintProvider gcp = get(IGrammarConstraintProvider.class); - - IConstraint ctxts = gcp.getConstraints(grammar).get(0).getConstraints().get(0); - List result = Lists.newArrayList(); - for (IConstraintElement ass : ctxts.getBody().getContainedAssignments()) { - result.add(ass.toString()); - for (Pair c : ass.getDependingAssignment()) - result.add(" " + c.getSecond() + " " + c.getFirst()); - } - return Joiner.on("\n").join(result); - } - - @Test public void testMandatoryGroup1() throws Exception { - String actual = getParserRule("Rule: 'kw1' a1='a1' a2+='a2'* a3+='a3'+ a4+='a4'?;"); - StringBuilder expected = new StringBuilder(); - expected.append("a1='a1'\n"); - expected.append("a2+='a2'*\n"); - expected.append("a3+='a3'+\n"); - expected.append("a4+='a4'?"); - assertEquals(expected.toString(), actual); - } - - @Test public void testMandatoryGroup2() throws Exception { - String actual = getParserRule("Rule: 'kw1' (a1='a1' a2='a2') (b1='b1'? b2='b2'?) (c1+='c1'+ c2+='c2'+) (d1+='a1'* d2+='d2'*);"); - StringBuilder expected = new StringBuilder(); - expected.append("a1='a1'\n"); - expected.append("a2='a2'\n"); - expected.append("b1='b1'?\n"); - expected.append("b2='b2'?\n"); - expected.append("c1+='c1'+\n"); - expected.append("c2+='c2'+\n"); - expected.append("d1+='a1'*\n"); - expected.append("d2+='d2'*"); - assertEquals(expected.toString(), actual); - } - - @Test public void testOptionalGroup1() throws Exception { - String actual = getParserRule("Rule: 'kw1' (a1='a1' a2+='a2'* a3+='a3'+ a4+='a4'?)?;"); - StringBuilder expected = new StringBuilder(); - expected.append("a1='a1'\n"); - expected.append(" MANDATORY_IF_SET a2+='a2'*\n"); - expected.append(" EXCLUDE_IF_UNSET a3+='a3'+\n"); - expected.append(" MANDATORY_IF_SET a3+='a3'+\n"); - expected.append(" MANDATORY_IF_SET a4+='a4'?\n"); - expected.append("a2+='a2'*\n"); - expected.append(" EXCLUDE_IF_UNSET a1='a1'\n"); - expected.append(" EXCLUDE_IF_UNSET a3+='a3'+\n"); - expected.append("a3+='a3'+\n"); - expected.append(" EXCLUDE_IF_UNSET a1='a1'\n"); - expected.append(" MANDATORY_IF_SET a1='a1'\n"); - expected.append(" MANDATORY_IF_SET a2+='a2'*\n"); - expected.append(" MANDATORY_IF_SET a4+='a4'?\n"); - expected.append("a4+='a4'?\n"); - expected.append(" EXCLUDE_IF_UNSET a1='a1'\n"); - expected.append(" EXCLUDE_IF_UNSET a3+='a3'+"); - assertEquals(expected.toString(), actual); - } - - @Test public void testOptionalGroup2() throws Exception { - String actual = getParserRule("Rule: 'kw1' (a1='a1' a2='a2')? (b1='b1'? b2='b2'?)? (c1+='c1'+ c2+='c2'+)? (d1+='a1'* d2+='d2'*)?;"); - StringBuilder expected = new StringBuilder(); - expected.append("a1='a1'\n"); - expected.append(" EXCLUDE_IF_UNSET a2='a2'\n"); - expected.append(" MANDATORY_IF_SET a2='a2'\n"); - expected.append("a2='a2'\n"); - expected.append(" EXCLUDE_IF_UNSET a1='a1'\n"); - expected.append(" MANDATORY_IF_SET a1='a1'\n"); - expected.append("b1='b1'?\n"); - expected.append("b2='b2'?\n"); - expected.append("c1+='c1'+\n"); - expected.append(" EXCLUDE_IF_UNSET c2+='c2'+\n"); - expected.append(" MANDATORY_IF_SET c2+='c2'+\n"); - expected.append("c2+='c2'+\n"); - expected.append(" EXCLUDE_IF_UNSET c1+='c1'+\n"); - expected.append(" MANDATORY_IF_SET c1+='c1'+\n"); - expected.append("d1+='a1'*\n"); - expected.append("d2+='d2'*"); - assertEquals(expected.toString(), actual); - } - - @Test public void testManyGroup1() throws Exception { - String actual = getParserRule("Rule: 'kw1' (a1+='a1' a2+='a2'* a3+='a3'+ a4+='a4'?)+;"); - StringBuilder expected = new StringBuilder(); - expected.append("a1+='a1'\n"); - expected.append(" MANDATORY_IF_SET a2+='a2'*\n"); - expected.append(" MANDATORY_IF_SET a3+='a3'+\n"); - expected.append(" SAME_OR_LESS a3+='a3'+\n"); - expected.append(" SAME_OR_MORE a4+='a4'?\n"); - expected.append("a2+='a2'*\n"); - expected.append(" EXCLUDE_IF_UNSET a1+='a1'\n"); - expected.append(" EXCLUDE_IF_UNSET a3+='a3'+\n"); - expected.append("a3+='a3'+\n"); - expected.append(" EXCLUDE_IF_UNSET a1+='a1'\n"); - expected.append(" SAME_OR_MORE a1+='a1'\n"); - expected.append(" MANDATORY_IF_SET a2+='a2'*\n"); - expected.append(" SAME_OR_MORE a4+='a4'?\n"); - expected.append("a4+='a4'?\n"); - expected.append(" SAME_OR_LESS a1+='a1'\n"); - expected.append(" SAME_OR_LESS a3+='a3'+"); - assertEquals(expected.toString(), actual); - } - - @Test public void testManyGroup2() throws Exception { - String actual = getParserRule("Rule: 'kw1' (a1='a1' a2='a2')+ (b1='b1'? b2='b2'?)+ (c1+='c1'+ c2+='c2'+)+ (d1+='a1'* d2+='d2'*)+;"); - StringBuilder expected = new StringBuilder(); - expected.append("a1='a1'\n"); - expected.append(" SAME a2='a2'\n"); - expected.append("a2='a2'\n"); - expected.append(" SAME a1='a1'\n"); - expected.append("b1='b1'?\n"); - expected.append("b2='b2'?\n"); - expected.append("c1+='c1'+\n"); - expected.append(" EXCLUDE_IF_UNSET c2+='c2'+\n"); - expected.append(" MANDATORY_IF_SET c2+='c2'+\n"); - expected.append("c2+='c2'+\n"); - expected.append(" EXCLUDE_IF_UNSET c1+='c1'+\n"); - expected.append(" MANDATORY_IF_SET c1+='c1'+\n"); - expected.append("d1+='a1'*\n"); - expected.append("d2+='d2'*"); - assertEquals(expected.toString(), actual); - } - - @Test public void testOptionalManyGroup1() throws Exception { - String actual = getParserRule("Rule: 'kw1' (a1='a1' a2+='a2'* a3+='a3'+ a4+='a4'?)*;"); - StringBuilder expected = new StringBuilder(); - expected.append("a1='a1'\n"); - expected.append(" MANDATORY_IF_SET a2+='a2'*\n"); - expected.append(" MANDATORY_IF_SET a3+='a3'+\n"); - expected.append(" SAME_OR_LESS a3+='a3'+\n"); - expected.append(" SAME_OR_MORE a4+='a4'?\n"); - expected.append("a2+='a2'*\n"); - expected.append(" EXCLUDE_IF_UNSET a1='a1'\n"); - expected.append(" EXCLUDE_IF_UNSET a3+='a3'+\n"); - expected.append("a3+='a3'+\n"); - expected.append(" EXCLUDE_IF_UNSET a1='a1'\n"); - expected.append(" SAME_OR_MORE a1='a1'\n"); - expected.append(" MANDATORY_IF_SET a2+='a2'*\n"); - expected.append(" SAME_OR_MORE a4+='a4'?\n"); - expected.append("a4+='a4'?\n"); - expected.append(" SAME_OR_LESS a1='a1'\n"); - expected.append(" SAME_OR_LESS a3+='a3'+"); - assertEquals(expected.toString(), actual); - } - - @Test public void testOptionalManyGroup2() throws Exception { - String actual = getParserRule("Rule: 'kw1' (a1='a1' a2='a2')* (b1='b1'? b2='b2'?)* (c1+='c1'+ c2+='c2'+)* (d1+='a1'* d2+='d2'*)*;"); - StringBuilder expected = new StringBuilder(); - expected.append("a1='a1'\n"); - expected.append(" SAME a2='a2'\n"); - expected.append("a2='a2'\n"); - expected.append(" SAME a1='a1'\n"); - expected.append("b1='b1'?\n"); - expected.append("b2='b2'?\n"); - expected.append("c1+='c1'+\n"); - expected.append(" EXCLUDE_IF_UNSET c2+='c2'+\n"); - expected.append(" MANDATORY_IF_SET c2+='c2'+\n"); - expected.append("c2+='c2'+\n"); - expected.append(" EXCLUDE_IF_UNSET c1+='c1'+\n"); - expected.append(" MANDATORY_IF_SET c1+='c1'+\n"); - expected.append("d1+='a1'*\n"); - expected.append("d2+='d2'*"); - assertEquals(expected.toString(), actual); - } - - @Test public void testKeywords2() throws Exception { - String actual = getParserRule("Rule: 'kw1' (a1='a1' | a2+='a2') b1='b1' b2+='b2'* b3+='b3'+ b4+='b4'?;"); - StringBuilder expected = new StringBuilder(); - expected.append("a1='a1'\n"); - expected.append(" EXCLUDE_IF_SET a2+='a2'\n"); - expected.append("a2+='a2'\n"); - expected.append(" EXCLUDE_IF_SET a1='a1'\n"); - expected.append("b1='b1'\n"); - expected.append("b2+='b2'*\n"); - expected.append("b3+='b3'+\n"); - expected.append("b4+='b4'?"); - assertEquals(expected.toString(), actual); - } - - @Test public void testKeywords3() throws Exception { - String actual = getParserRule("Rule: 'kw1' ((a1='a1' | a2+='a2') b1='b1' b2+='b2'* b3+='b3'+ b4+='b4'?)?;"); - StringBuilder expected = new StringBuilder(); - expected.append("a1='a1'\n"); - expected.append(" EXCLUDE_IF_SET a2+='a2'\n"); - expected.append(" EXCLUDE_IF_UNSET b1='b1'\n"); - expected.append(" EXCLUDE_IF_UNSET b3+='b3'+\n"); - expected.append("a2+='a2'\n"); - expected.append(" EXCLUDE_IF_SET a1='a1'\n"); - expected.append(" EXCLUDE_IF_UNSET b1='b1'\n"); - expected.append(" EXCLUDE_IF_UNSET b3+='b3'+\n"); - expected.append("b1='b1'\n"); - expected.append(" MANDATORY_IF_SET a1='a1'\n"); - expected.append(" MANDATORY_IF_SET a2+='a2'\n"); - expected.append(" MANDATORY_IF_SET b2+='b2'*\n"); - expected.append(" EXCLUDE_IF_UNSET b3+='b3'+\n"); - expected.append(" MANDATORY_IF_SET b3+='b3'+\n"); - expected.append(" MANDATORY_IF_SET b4+='b4'?\n"); - expected.append("b2+='b2'*\n"); - expected.append(" EXCLUDE_IF_UNSET b1='b1'\n"); - expected.append(" EXCLUDE_IF_UNSET b3+='b3'+\n"); - expected.append("b3+='b3'+\n"); - expected.append(" MANDATORY_IF_SET a1='a1'\n"); - expected.append(" MANDATORY_IF_SET a2+='a2'\n"); - expected.append(" EXCLUDE_IF_UNSET b1='b1'\n"); - expected.append(" MANDATORY_IF_SET b1='b1'\n"); - expected.append(" MANDATORY_IF_SET b2+='b2'*\n"); - expected.append(" MANDATORY_IF_SET b4+='b4'?\n"); - expected.append("b4+='b4'?\n"); - expected.append(" EXCLUDE_IF_UNSET b1='b1'\n"); - expected.append(" EXCLUDE_IF_UNSET b3+='b3'+"); - assertEquals(expected.toString(), actual); - } -} \ No newline at end of file diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarConstraintProviderFeatureTest.xtend b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarConstraintProviderFeatureTest.xtend new file mode 100644 index 000000000..1c1232b0b --- /dev/null +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/serializer/GrammarConstraintProviderFeatureTest.xtend @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2015 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 com.google.inject.Inject +import org.eclipse.xtext.Grammar +import org.eclipse.xtext.junit4.InjectWith +import org.eclipse.xtext.junit4.XtextRunner +import org.eclipse.xtext.junit4.internal.XtextInjectorProvider +import org.eclipse.xtext.junit4.util.ParseHelper +import org.eclipse.xtext.junit4.validation.ValidationTestHelper +import org.eclipse.xtext.serializer.analysis.IGrammarConstraintProvider +import org.eclipse.xtext.serializer.analysis.IGrammarConstraintProvider.IFeatureInfo +import org.junit.runner.RunWith +import org.junit.Test +import org.junit.Assert + +/** + * @author Moritz Eysholdt - Initial contribution and API + */ +@RunWith(XtextRunner) +@InjectWith(XtextInjectorProvider) +class GrammarConstraintProviderFeatureTest { + + @Inject ValidationTestHelper validator + @Inject ParseHelper parser + @Inject IGrammarConstraintProvider constraintProvider + + @Test def void simple() { + val actual = ''' + Rule: val=ID; + '''.toFeatureInfo + val expected = ''' + Rule_Rule{ + val[1,1] + } + ''' + Assert.assertEquals(expected, actual) + } + + @Test def void optional() { + val actual = ''' + Rule: {Rule} val=ID?; + '''.toFeatureInfo + val expected = ''' + Rule_Rule{ + val[0,1] + } + ''' + Assert.assertEquals(expected, actual) + } + + @Test def void multi() { + val actual = ''' + Rule: val=ID+; + '''.toFeatureInfo + val expected = ''' + Rule_Rule{ + val[1,*] + } + ''' + Assert.assertEquals(expected, actual) + } + + @Test def void optionalMulti() { + val actual = ''' + Rule: {Rule} val=ID*; + '''.toFeatureInfo + val expected = ''' + Rule_Rule{ + val[0,*] + } + ''' + Assert.assertEquals(expected, actual) + } + + @Test def void twoToThree() { + val actual = ''' + Rule: val+=ID val+=ID val+=ID?; + '''.toFeatureInfo + val expected = ''' + Rule_Rule{ + val[2,3] + } + ''' + Assert.assertEquals(expected, actual) + } + + def String toFeatureInfo(CharSequence grammarString) { + val grammar = parser.parse(''' + grammar org.eclipse.xtext.serializer.GrammarConstraintProviderFeatureTestLanguage with org.eclipse.xtext.common.Terminals + + generate GrammarConstraintProviderFeatureTest "http://www.eclipse.org/2010/tmf/xtext/GrammarConstraintProviderFeatureTestLanguage" + + «grammarString» + ''') + validator.assertNoErrors(grammar) + val constraints = constraintProvider.getConstraints(grammar).map[constraints].flatten.toSet + return constraints.map[name + "{\n " + features.map[asString].join("\n ") + "\n}"].join("\n") + "\n" + } + + def String asString(IFeatureInfo it) { + val upper = if(upperBound == IGrammarConstraintProvider.MAX) "*" else upperBound + return feature.name + "[" + lowerBound + "," + upper + "]"; + } +} \ No newline at end of file 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 dbac745e2..ee5ac2dc9 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 @@ -140,12 +140,19 @@ public class GrammarConstraintProviderTest extends AbstractXtextTests { String actual = getParserRule("Rule: (x1=ID | x2=ID) | (x3=ID | x4=ID)* | (x5=ID | x6=ID)? | (x7=ID | x8=ID)+;"); StringBuilder expected = new StringBuilder(); expected.append("Rule: Rule_Rule | Rule_null;\n"); - expected.append(" Rule_Rule returns Rule: (x1=ID | x2=ID | (x3=ID | x4=ID)* | (x5=ID | x6=ID)? | (x7=ID | x8=ID)+);\n"); + expected.append(" Rule_Rule returns Rule: (\n"); + expected.append(" x1=ID | \n"); + expected.append(" x2=ID | \n"); + expected.append(" (x3=ID | x4=ID)+ | \n"); + expected.append(" x5=ID | \n"); + expected.append(" x6=ID | \n"); + expected.append(" (x7=ID | x8=ID)+\n"); + expected.append(");\n"); expected.append(" Rule_null returns null: {null};"); assertEquals(expected.toString(), actual); } - @Test public void testEmptyAlternatives() throws Exception { + @Test @Ignore public void testEmptyAlternatives() throws Exception { String actual = getParserRule("Rule: (x1=ID | x2=ID | 'foo') (x3=ID | x4=ID | 'foo' | 'bar');"); StringBuilder expected = new StringBuilder(); expected.append("Rule: Rule_Rule | Rule_null;\n"); @@ -153,6 +160,15 @@ public class GrammarConstraintProviderTest extends AbstractXtextTests { expected.append(" Rule_null returns null: {null};"); assertEquals(expected.toString(), actual); } + + @Test @Ignore public void testDoubleMulti() throws Exception { + String actual = getParserRule("Rule: x1=ID* x2=ID*;"); + StringBuilder expected = new StringBuilder(); + expected.append("Rule: Rule_Rule | Rule_null;\n"); + expected.append(" Rule_Rule returns Rule: ((x1=ID+ x2=ID+) | x1=ID+ | x2=ID+);\n"); + expected.append(" Rule_null returns null: {null};"); + assertEquals(expected.toString(), actual); + } @Test public void testAssignedAlternatives1() throws Exception { String actual = getParserRule("Rule: a1=(ID|'id') a2=(ID|STRING|'bar') a3+=(ID|STRING|'bar')*;"); @@ -172,7 +188,7 @@ public class GrammarConstraintProviderTest extends AbstractXtextTests { expected.append(" a2=ID | \n"); expected.append(" a2=STRING | \n"); expected.append(" a2='bar' | \n"); - expected.append(" (a3+=ID | a3+=STRING | a3+='bar')*\n"); + expected.append(" (a3+=ID | a3+=STRING | a3+='bar')+\n"); expected.append(");\n"); expected.append(" Rule_null returns null: {null};"); assertEquals(expected.toString(), actual); @@ -182,7 +198,7 @@ public class GrammarConstraintProviderTest extends AbstractXtextTests { String actual = getParserRule("Rule: {Rule} ('false' | isTrue?='true');"); StringBuilder expected = new StringBuilder(); expected.append("Rule: Rule_Rule;\n"); - expected.append(" Rule_Rule returns Rule: (isTrue?='true'?);"); + expected.append(" Rule_Rule returns Rule: isTrue?='true'?;"); assertEquals(expected.toString(), actual); } @@ -341,18 +357,23 @@ public class GrammarConstraintProviderTest extends AbstractXtextTests { } @Test - @Ignore public void testReturnsNullAlways() throws Exception { String actual = getParserRule("Rule: val1=NullRule val2=ID; NullRule: 'kw1';"); StringBuilder expected = new StringBuilder(); + expected.append("Rule: Rule_Rule;\n"); + expected.append(" Rule_Rule returns Rule: (val1=NullRule val2=ID);"); assertEquals(expected.toString(), actual); } @Test - @Ignore public void testReturnsNullSometimes() throws Exception { String actual = getParserRule("Rule: val1=NullRule val2=ID; NullRule: 'kw1' | 'kw2' {NullRule};"); StringBuilder expected = new StringBuilder(); + expected.append("Rule: Rule_Rule;\n"); + expected.append(" Rule_Rule returns Rule: (val1=NullRule val2=ID);\n"); + expected.append("NullRule: NullRule_NullRule | NullRule_null;\n"); + expected.append(" NullRule_NullRule returns NullRule: {NullRule};\n"); + expected.append(" NullRule_null returns null: {null};"); assertEquals(expected.toString(), actual); } } diff --git a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/util/formallang/NfaToProductionTest.java b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/util/formallang/NfaToProductionTest.java index b4cef9367..3630f764b 100644 --- a/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/util/formallang/NfaToProductionTest.java +++ b/tests/org.eclipse.xtext.tests/src/org/eclipse/xtext/util/formallang/NfaToProductionTest.java @@ -73,7 +73,7 @@ public class NfaToProductionTest extends Assert { nfa.start().followedBy("x", "y", "stop"); nfa.state("x").followedBy("stop", "x"); nfa.state("y").followedBy("stop"); - assertEquals("start (x* | y?) stop", nfa2g(nfa)); + assertEquals("start (x+ | y)? stop", nfa2g(nfa)); } @Test public void testAlternative7() {