diff --git a/plugins/org.eclipse.xtext.junit4/src/org/eclipse/xtext/junit4/AbstractXtextTests.java b/plugins/org.eclipse.xtext.junit4/src/org/eclipse/xtext/junit4/AbstractXtextTests.java index 4a7026cae..0aae046c7 100644 --- a/plugins/org.eclipse.xtext.junit4/src/org/eclipse/xtext/junit4/AbstractXtextTests.java +++ b/plugins/org.eclipse.xtext.junit4/src/org/eclipse/xtext/junit4/AbstractXtextTests.java @@ -44,6 +44,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; +import com.google.common.base.Joiner; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; @@ -261,9 +262,9 @@ public abstract class AbstractXtextTests extends Assert implements ResourceLoadH checkNodeModel(resource); if (expectedErrors != UNKNOWN_EXPECTATION) { if (expectedErrors == EXPECT_ERRORS) - assertFalse(resource.getErrors().toString(), resource.getErrors().isEmpty()); + assertFalse(Joiner.on('\n').join(resource.getErrors()), resource.getErrors().isEmpty()); else - assertEquals(resource.getErrors().toString(), expectedErrors, resource.getErrors().size()); + assertEquals(Joiner.on('\n').join(resource.getErrors()), expectedErrors, resource.getErrors().size()); } for(Diagnostic d: resource.getErrors()) { if (d instanceof ExceptionDiagnostic) diff --git a/plugins/org.eclipse.xtext.junit4/src/org/eclipse/xtext/junit4/serializer/SerializerTester.java b/plugins/org.eclipse.xtext.junit4/src/org/eclipse/xtext/junit4/serializer/SerializerTester.java index 3d7bbcb25..12f03a932 100644 --- a/plugins/org.eclipse.xtext.junit4/src/org/eclipse/xtext/junit4/serializer/SerializerTester.java +++ b/plugins/org.eclipse.xtext.junit4/src/org/eclipse/xtext/junit4/serializer/SerializerTester.java @@ -14,6 +14,7 @@ import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.xtext.IGrammarAccess; import org.eclipse.xtext.junit4.util.ParseHelper; import org.eclipse.xtext.junit4.validation.ValidationTestHelper; import org.eclipse.xtext.nodemodel.ICompositeNode; @@ -66,6 +67,9 @@ public class SerializerTester { @Inject protected ValidationTestHelper validationHelper; + + @Inject + private IGrammarAccess grammarAccess; /** * @since 2.3 @@ -174,7 +178,7 @@ public class SerializerTester { if (Iterables.size(contexts) != 1) { StringBuilder msg = new StringBuilder(); msg.append("One context is expected, but " + Iterables.size(contexts) + " have been found\n"); - msg.append("Contexts: " + Joiner.on(", ").join(Iterables.transform(contexts, new Context2NameFunction()))); + msg.append("Contexts: " + Joiner.on(", ").join(Iterables.transform(contexts, new Context2NameFunction().toFunction(grammarAccess.getGrammar())))); msg.append("Semantic Object: " + EmfFormatter.objPath(semanticObject)); Assert.fail(msg.toString()); } diff --git a/plugins/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/grammarAccess/GrammarAccessExtensions.xtend b/plugins/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/grammarAccess/GrammarAccessExtensions.xtend index 56d0bafc7..28300cc6e 100644 --- a/plugins/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/grammarAccess/GrammarAccessExtensions.xtend +++ b/plugins/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/grammarAccess/GrammarAccessExtensions.xtend @@ -28,6 +28,7 @@ import org.eclipse.xtext.Grammar import org.eclipse.xtext.GrammarUtil import org.eclipse.xtext.Keyword import org.eclipse.xtext.RuleCall +import org.eclipse.xtext.RuleNames import org.eclipse.xtext.TypeRef import org.eclipse.xtext.XtextRuntimeModule import org.eclipse.xtext.formatting.ILineSeparatorInformation @@ -137,9 +138,18 @@ class GrammarAccessExtensions { /** * Creates an identifier for a Rule which is a valid Java identifier and unique within - * the Rule's grammar. + * the Rule's grammar and its super grammars. */ def String gaRuleIdentifier(AbstractRule rule) { + val plainName = RuleNames.getRuleNames(rule).getUniqueRuleName(rule); + return toJavaIdentifier(plainName, true); + } + + /** + * Creates an identifier for a Rule which is a valid Java identifier and unique within + * the grammar that defines the rule. + */ + def String gaBaseRuleIdentifier(AbstractRule rule) { rule.name.toJavaIdentifier(true) } @@ -223,6 +233,13 @@ class GrammarAccessExtensions { def String gaRuleAccessMethodName(AbstractRule rule) { 'get' + rule.gaRuleIdentifier + 'Rule' } + + /** + * Returns the method name for accessing a rule via a GrammarAccess implementation. + */ + def String gaBaseRuleAccessMethodName(AbstractRule rule) { + 'get' + rule.gaBaseRuleIdentifier + 'Rule' + } /** * Returns the method name for accessing a rule's content via a ParserRuleAccess implementation. @@ -231,6 +248,13 @@ class GrammarAccessExtensions { 'get' + rule.gaRuleIdentifier + 'Access' } + /** + * Returns the method name for accessing a rule's content via a ParserRuleAccess implementation. + */ + def String gaBaseRuleElementsMethodName(AbstractRule rule) { + 'get' + rule.gaBaseRuleIdentifier + 'Access' + } + /** * Returns the method name for accessing an element via a GrammarAccess implementation. */ @@ -245,6 +269,14 @@ class GrammarAccessExtensions { def String gaRuleAccessorClassName(AbstractRule rule) { rule.gaRuleIdentifier + 'Elements' } + + /** + * Returns the simple class name of a rule's facade. A GrammarAccess implementation has + * a facade for each parser rule, which contains the methods for accessing the rule's elements. + */ + def String gaBaseRuleAccessorClassName(AbstractRule rule) { + rule.gaBaseRuleIdentifier + 'Elements' + } /** * Returns the invocation of the rule accessor method as Java statement. @@ -253,12 +285,26 @@ class GrammarAccessExtensions { rule.gaRuleAccessMethodName + '()' } + /** + * Returns the invocation of the rule accessor method as Java statement. + */ + def String gaBaseRuleAccessor(AbstractRule rule) { + rule.gaBaseRuleAccessMethodName + '()' + } + /** * Returns the invocation of the rule's content accessor method as Java statement. */ def String gaElementsAccessor(AbstractRule rule) { rule.gaRuleElementsMethodName + '()' } + + /** + * Returns the invocation of the rule's content accessor method as Java statement. + */ + def String gaBaseElementsAccessor(AbstractRule rule) { + rule.gaBaseRuleElementsMethodName + '()' + } /** * Returns the invocation of the element accessor method as Java statement. @@ -327,7 +373,7 @@ class GrammarAccessExtensions { return result; } - @FinalFieldsConstructor + @FinalFieldsConstructor protected static class LineSeparatorModule extends XtextRuntimeModule { val ILineSeparatorInformation lineSeparatorInformation; diff --git a/plugins/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/grammarAccess/GrammarAccessFragment2.xtend b/plugins/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/grammarAccess/GrammarAccessFragment2.xtend index d2ae10265..758a2dbd2 100644 --- a/plugins/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/grammarAccess/GrammarAccessFragment2.xtend +++ b/plugins/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/grammarAccess/GrammarAccessFragment2.xtend @@ -77,6 +77,10 @@ class GrammarAccessFragment2 extends AbstractGeneratorFragment2 { writeGrammar(language) } + def protected String getQualifiedName(AbstractRule rule) { + return GrammarUtil.getGrammar(rule).name + '.' + rule.name + } + protected def void writeGrammar(LanguageConfig2 language) { val isSaving = Wrapper.wrap(false) val ResourceSet cloneInto = new ResourceSetImpl @@ -246,7 +250,7 @@ class GrammarAccessFragment2 extends AbstractGeneratorFragment2 { protected def StringConcatenationClient parserRuleClasses(ParserRule it) ''' public class «gaRuleAccessorClassName» extends «AbstractParserRuleElementFinder» { - private final «ParserRule» rule = («ParserRule») «GrammarUtil».findRuleForName(getGrammar(), "«name»"); + private final «ParserRule» rule = («ParserRule») «GrammarUtil».findRuleForName(getGrammar(), "«qualifiedName»"); «FOR e : containedAbstractElements» private final «e.eClass» «e.gaElementAccessorLocalVarName» = «e.loadElementStatement»; «ENDFOR» @@ -263,7 +267,7 @@ class GrammarAccessFragment2 extends AbstractGeneratorFragment2 { protected def StringConcatenationClient parserRuleClasses(EnumRule it) ''' public class «gaRuleAccessorClassName» extends «AbstractEnumRuleElementFinder» { - private final «EnumRule» rule = («EnumRule») «GrammarUtil».findRuleForName(getGrammar(), "«name»"); + private final «EnumRule» rule = («EnumRule») «GrammarUtil».findRuleForName(getGrammar(), "«qualifiedName»"); «FOR e : containedAbstractElements» private final «e.eClass» «e.gaElementAccessorLocalVarName» = «e.loadElementStatement»; «ENDFOR» @@ -299,7 +303,7 @@ class GrammarAccessFragment2 extends AbstractGeneratorFragment2 { ''' protected def dispatch StringConcatenationClient initializer(TerminalRule it) ''' - this.«gaRuleAccessorLocalVarName» = («TerminalRule») «GrammarUtil».findRuleForName(getGrammar(), "«name»"); + this.«gaRuleAccessorLocalVarName» = («TerminalRule») «GrammarUtil».findRuleForName(getGrammar(), "«qualifiedName»"); ''' protected def dispatch StringConcatenationClient getter(ParserRule it, Grammar original) ''' @@ -309,8 +313,8 @@ class GrammarAccessFragment2 extends AbstractGeneratorFragment2 { return «gaRuleAccessorLocalVarName»; } «ELSE» - public «grammar.grammarAccess».«gaRuleAccessorClassName» «gaElementsAccessor» { - return «usedGrammar(original).gaGrammarAccessLocalVarName».«gaElementsAccessor»; + public «grammar.grammarAccess».«gaBaseRuleAccessorClassName» «gaElementsAccessor» { + return «usedGrammar(original).gaGrammarAccessLocalVarName».«gaBaseElementsAccessor»; } «ENDIF» @@ -342,7 +346,7 @@ class GrammarAccessFragment2 extends AbstractGeneratorFragment2 { «IF grammar === original» return «gaRuleAccessorLocalVarName»; «ELSE» - return «usedGrammar(original).gaGrammarAccessLocalVarName».«gaRuleAccessor»; + return «usedGrammar(original).gaGrammarAccessLocalVarName».«gaBaseRuleAccessor»; «ENDIF» } ''' diff --git a/plugins/org.eclipse.xtext/.settings/.api_filters b/plugins/org.eclipse.xtext/.settings/.api_filters index 3e61c6392..295cabb0a 100644 --- a/plugins/org.eclipse.xtext/.settings/.api_filters +++ b/plugins/org.eclipse.xtext/.settings/.api_filters @@ -1,7 +1,7 @@ - + @@ -24,6 +24,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/org.eclipse.xtext/org/eclipse/xtext/Xtext.ecore b/plugins/org.eclipse.xtext/org/eclipse/xtext/Xtext.ecore index d7f5c97b5..60f7102d5 100644 --- a/plugins/org.eclipse.xtext/org/eclipse/xtext/Xtext.ecore +++ b/plugins/org.eclipse.xtext/org/eclipse/xtext/Xtext.ecore @@ -32,6 +32,10 @@ + + + @@ -53,6 +57,9 @@ + + @@ -84,7 +91,10 @@ - + + + @@ -96,4 +106,32 @@ eType="#//AbstractElement" containment="true"/> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/org.eclipse.xtext/org/eclipse/xtext/Xtext.genmodel b/plugins/org.eclipse.xtext/org/eclipse/xtext/Xtext.genmodel index 0a4defdb5..867be9a08 100644 --- a/plugins/org.eclipse.xtext/org/eclipse/xtext/Xtext.genmodel +++ b/plugins/org.eclipse.xtext/org/eclipse/xtext/Xtext.genmodel @@ -5,7 +5,7 @@ forceOverwrite="true" modelName="Xtext" updateClasspath="false" rootExtendsClass="org.eclipse.emf.ecore.impl.MinimalEObjectImpl$Container" importerID="org.eclipse.emf.importer.ecore" complianceLevel="5.0" copyrightFields="false" editPluginID="org.eclipse.xtext.edit" editorPluginID="org.eclipse.xtext.editor" - usedGenPackages="../../../../org.eclipse.emf.ecore/model/Ecore.genmodel#//ecore"> + usedGenPackages="platform:/resource/org.eclipse.emf.ecore/model/Ecore.genmodel#//ecore"> Xtext.ecore @@ -33,6 +33,9 @@ + + + @@ -53,6 +56,8 @@ + + @@ -88,5 +93,17 @@ + + + + + + + + + + + + diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/EcoreUtil2.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/EcoreUtil2.java index fabf7efff..3d0352654 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/EcoreUtil2.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/EcoreUtil2.java @@ -55,6 +55,9 @@ import org.eclipse.xtext.resource.DerivedStateAwareResource; import org.eclipse.xtext.util.CancelIndicator; import org.eclipse.xtext.util.Strings; +import com.google.common.collect.Iterables; +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; import com.google.common.collect.AbstractIterator; import com.google.common.collect.MapMaker; @@ -179,27 +182,12 @@ public class EcoreUtil2 extends EcoreUtil { return target; } - @SuppressWarnings("unchecked") public static List getAllContentsOfType(EObject ele, Class type) { - List result = new ArrayList(); - TreeIterator allContents = ele.eAllContents(); - while (allContents.hasNext()) { - EObject object = allContents.next(); - if (type.isAssignableFrom(object.getClass())) { - result.add((T) object); - } - } - return result; + return Lists.newArrayList(Iterators.filter(ele.eAllContents(), type)); } public static List typeSelect(List elements, Class clazz) { - List result = new ArrayList(); - for (Object ele : elements) { - if (ele != null && clazz.isAssignableFrom(ele.getClass())) { - result.add(clazz.cast(ele)); - } - } - return result; + return Lists.newArrayList(Iterables.filter(elements, clazz)); } public static List collect(Collection instances, int featureId, Class type) { @@ -217,19 +205,8 @@ public class EcoreUtil2 extends EcoreUtil { return result; } - @SuppressWarnings("unchecked") public static List eAllOfType(EObject ele, Class type) { - List result = new ArrayList(); - if (type.isAssignableFrom(ele.getClass())) - result.add((T) ele); - TreeIterator allContents = ele.eAllContents(); - while (allContents.hasNext()) { - EObject object = allContents.next(); - if (type.isAssignableFrom(object.getClass())) { - result.add((T) object); - } - } - return result; + return Lists.newArrayList(Iterators.filter(eAll(ele), type)); } public static TreeIterator eAll(final EObject obj) { @@ -291,20 +268,11 @@ public class EcoreUtil2 extends EcoreUtil { } public static List eAllContentsAsList(EObject ele) { - List result = new ArrayList(); - TreeIterator iterator = ele.eAllContents(); - while (iterator.hasNext()) - result.add(iterator.next()); - return result; + return Lists.newArrayList(ele.eAllContents()); } public static List eAllContentsAsList(Resource resource) { - List result = new ArrayList(); - TreeIterator iterator = resource.getAllContents(); - while (iterator.hasNext()) { - result.add(iterator.next()); - } - return result; + return Lists.newArrayList(resource.getAllContents()); } public static final EPackage loadEPackage(String uriAsString, ClassLoader classLoader) { @@ -412,10 +380,11 @@ public class EcoreUtil2 extends EcoreUtil { } private static void collectAllSuperTypes(Set collectedTypes, EClass eClass) { - for (EClass superType : eClass.getESuperTypes()) + for (EClass superType : eClass.getESuperTypes()) { if (collectedTypes.add(superType)) { collectAllSuperTypes(collectedTypes, superType); } + } } /** diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/GrammarUtil.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/GrammarUtil.java index 38a2028fc..d06da47b4 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/GrammarUtil.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/GrammarUtil.java @@ -15,6 +15,8 @@ import static org.eclipse.xtext.EcoreUtil2.*; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -39,6 +41,8 @@ import org.eclipse.xtext.xtext.CurrentTypeFinder; import com.google.common.base.Function; import com.google.common.base.Predicate; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; /** * @author Jan Koehnlein @@ -179,8 +183,7 @@ public class GrammarUtil { public static boolean isEObjectRuleCall(EObject grammarElement) { if (grammarElement instanceof RuleCall) { AbstractRule calledRule = ((RuleCall) grammarElement).getRule(); - return calledRule != null && calledRule instanceof ParserRule - && calledRule.getType().getClassifier() instanceof EClass; + return isEObjectRule(calledRule); } return false; } @@ -189,8 +192,13 @@ public class GrammarUtil { * @since 2.0 */ public static boolean isEObjectRule(EObject grammarElement) { - return grammarElement instanceof ParserRule - && ((ParserRule) grammarElement).getType().getClassifier() instanceof EClass; + if (grammarElement instanceof ParserRule) { + ParserRule rule = (ParserRule) grammarElement; + TypeRef type = rule.getType(); + // wildcard fragments are considered to be EObjectRules, too + return type == null || type.getClassifier() instanceof EClass; + } + return false; } /** @@ -211,6 +219,24 @@ public class GrammarUtil { return GrammarUtil.containingAssignment(ele) == null; } + /** + * @since 2.9 + */ + public static boolean isEObjectFragmentRuleCall(EObject ele) { + if (ele instanceof RuleCall) { + AbstractRule rule = ((RuleCall) ele).getRule(); + return isEObjectFragmentRule(rule); + } + return false; + } + + /** + * @since 2.9 + */ + public static boolean isEObjectFragmentRule(AbstractRule rule) { + return rule instanceof ParserRule && ((ParserRule) rule).isFragment(); + } + /** * @since 2.0 */ @@ -224,7 +250,7 @@ public class GrammarUtil { if (grammarElement instanceof RuleCall) { AbstractRule calledRule = ((RuleCall) grammarElement).getRule(); return calledRule != null && calledRule instanceof ParserRule - && calledRule.getType().getClassifier() instanceof EDataType; + && calledRule.getType() != null && calledRule.getType().getClassifier() instanceof EDataType; } return false; } @@ -248,56 +274,102 @@ public class GrammarUtil { return false; } + /** + * @param ruleName + * the name of the rule that should be found. May be a qualified name with a dot as a delimiter. + */ public static AbstractRule findRuleForName(Grammar grammar, String ruleName) { if (ruleName == null) return null; - for (AbstractRule rule : grammar.getRules()) { - if (ruleName.equals(rule.getName())) { - return rule; - } + int lastIndex = ruleName.lastIndexOf('.'); + if (lastIndex == -1) { + return findRuleForNameRecursively(grammar, null, ruleName, Sets.newHashSet()); + } else { + return findRuleForNameRecursively(grammar, ruleName.substring(0, lastIndex), ruleName.substring(lastIndex + 1), Sets.newHashSet()); } - for (Grammar usedGrammar : grammar.getUsedGrammars()) { - AbstractRule rule = findRuleForName(usedGrammar, ruleName); - if (rule != null) { - return rule; + } + + private static AbstractRule findRuleForNameRecursively(Grammar grammar, String langName, String ruleName, Set visited) { + if (visited.add(grammar)) { + if (langName == null || langName.equals(grammar.getName())) { + for (AbstractRule rule : grammar.getRules()) { + if (ruleName.equals(rule.getName())) { + return rule; + } + } + if (langName != null) { + return null; + } + } + for (Grammar usedGrammar : grammar.getUsedGrammars()) { + AbstractRule rule = findRuleForNameRecursively(usedGrammar, langName, ruleName, visited); + if (rule != null) { + return rule; + } } } return null; } public static List allUsedGrammars(Grammar grammar) { - List grammars = new ArrayList(); - collectAllUsedGrammars(grammars, grammar); - return grammars; + Collection visitedGrammars = new LinkedHashSet(); + for(Grammar used: grammar.getUsedGrammars()) + collectAllUsedGrammars(used, grammar, visitedGrammars); + return new ArrayList(visitedGrammars); } - - private static void collectAllUsedGrammars(List grammars, Grammar grammar) { - grammars.addAll(grammar.getUsedGrammars()); - for (Grammar g : grammar.getUsedGrammars()) - collectAllUsedGrammars(grammars, g); + + private static void collectAllUsedGrammars(Grammar grammar, Grammar start, Collection result) { + if (grammar == start || !result.add(grammar)) + return; + for(Grammar usedGrammar: grammar.getUsedGrammars()) { + collectAllUsedGrammars(usedGrammar, start, result); + } } public static List allRules(Grammar grammar) { - final List result = new ArrayList(); - final Set names = new HashSet(); - final Set grammars = new HashSet(); - collectAllRules(grammar, result, grammars, names); - return result; + final Set result = Sets.newLinkedHashSet(); + final Set explicitlyCalled = Sets.newHashSet(); + final Set seenNames = Sets.newHashSet(); + final Set seenGrammars = Sets.newHashSet(); + collectAllRules(grammar, result, explicitlyCalled, seenNames, seenGrammars); + return Lists.newArrayList(result); } - private static void collectAllRules(Grammar grammar, List result, Set visitedGrammars, - Set knownRulenames) { - if (!visitedGrammars.add(grammar)) + private static void collectAllRules( + Grammar grammar, + Set result, + Set explicitlyCalled, + Set seenNames, + Set seenGrammars) { + if (!seenGrammars.add(grammar)) return; - for (AbstractRule rule : grammar.getRules()) { - if (knownRulenames.add(rule.getName())) { + // we need to iterate the rules twice in case an explicit call to a + // rule is made from a rule defined later + // this explicitly called rule needs to be added + // to the set of all rules even though it may + // have been specialized in the sub grammar + if (!seenNames.contains(rule.getName()) || explicitlyCalled.contains(rule)) { + AbstractElement body = rule.getAlternatives(); + if (body != null) { + Iterator contents = eAll(body); + while(contents.hasNext()) { + EObject content = contents.next(); + if (content instanceof RuleCall) { + AbstractRule calledRule = ((RuleCall) content).getRule(); + explicitlyCalled.add(calledRule); + } + } + } + } + } + for (AbstractRule rule : grammar.getRules()) { + if (seenNames.add(rule.getName()) || explicitlyCalled.contains(rule)) { result.add(rule); } } - for (Grammar usedGrammar : grammar.getUsedGrammars()) { - collectAllRules(usedGrammar, result, visitedGrammars, knownRulenames); + collectAllRules(usedGrammar, result, explicitlyCalled, seenNames, seenGrammars); } } @@ -349,8 +421,13 @@ public class GrammarUtil { final BidiIterator leafNodes = node.getAsTreeIterable().iterator(); while (leafNodes.hasPrevious()) { INode previous = leafNodes.previous(); - if (previous instanceof ILeafNode && !((ILeafNode) previous).isHidden()) - return previous.getText(); + if (previous instanceof ILeafNode && !((ILeafNode) previous).isHidden()) { + String result = previous.getText(); + if (result != null && result.startsWith("^")) { + result = result.substring(1); + } + return result; + } } } return null; diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/RuleNames.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/RuleNames.java new file mode 100644 index 000000000..1faec5221 --- /dev/null +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/RuleNames.java @@ -0,0 +1,256 @@ +/******************************************************************************* + * 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; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.emf.common.notify.impl.AdapterImpl; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.util.EcoreUtil; + +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * Different kinds of name mapping for rules in a grammar. + * + * @author Sebastian Zarnekow - Initial contribution and API + * @since 2.9 + */ +@Singleton +public class RuleNames { + + static class Adapter extends AdapterImpl { + private RuleNames ruleNames; + + Adapter(RuleNames ruleNames) { + this.ruleNames = ruleNames; + } + + @Override + public boolean isAdapterForType(Object type) { + return RuleNames.class.equals(type); + } + + public RuleNames getRuleNames() { + return ruleNames; + } + } + + private final Grammar contextGrammar; + + /** + * All rules by simple name. + */ + private final ListMultimap simpleNameToRules; + /** + * A mapping from qualified name to rule. + */ + private final Map qualifiedNameToRule; + /** + * The name that is unique or qualified depending on the occurrence. + * It may either be a mapping from {@code ID} to rule or {@code com.acme.ID} to + * rule depending on the fact if the ID rule is overridden in the context grammar. + */ + private final BiMap nameToRule; + /** + * The name that is used in code, e.g. the grammar access method names. + */ + private final BiMap uniqueNameToRule; + /** + * The name that is used in the generated Antlr grammar. + */ + private final BiMap antlrNameToRule; + + private final List allRules; + + public static RuleNames getRuleNames(Grammar grammar, boolean cache) { + if (cache) { + Adapter adapter = (Adapter) EcoreUtil.getAdapter(grammar.eAdapters(), RuleNames.class); + if (adapter == null) { + return new RuleNames(grammar, true); + } + return adapter.getRuleNames(); + } + return new RuleNames(grammar, false); + } + + public static RuleNames getRuleNames(AbstractRule rule) { + Adapter adapter = (Adapter) EcoreUtil.getAdapter(rule.eAdapters(), RuleNames.class); + if (adapter == null) { + throw new IllegalStateException("Cannot find adapter"); + } + return adapter.getRuleNames(); + } + + public static void ensureAdapterInstalled(Grammar grammar) { + getRuleNames(grammar, true); + } + + @Inject + public RuleNames(IGrammarAccess grammarAccess) { + this(grammarAccess.getGrammar(), false); + } + + public RuleNames(Grammar grammar, boolean installAdapter) { + this.contextGrammar = grammar; + Adapter adapter = new Adapter(this); + if (installAdapter) { + installAdapterIfMissing(adapter, grammar); + } + List allRules = GrammarUtil.allRules(grammar); + ImmutableListMultimap.Builder simpleNameToRulesBuilder = ImmutableListMultimap.builder(); + ImmutableMap.Builder qualifiedNameToRuleBuilder = ImmutableMap.builder(); + ImmutableBiMap.Builder uniqueNameToRuleBuilder = ImmutableBiMap.builder(); + ImmutableBiMap.Builder antlrNameToRuleBuilder = ImmutableBiMap.builder(); + + Map names = Maps.newHashMap(); + Set usedAntlrNames = Sets.newHashSet(); + Set usedUniqueNames = Sets.newHashSet(); + for(AbstractRule rule: allRules) { + String name = rule.getName(); + simpleNameToRulesBuilder.put(name, rule); + String qualifiedName = getQualifiedName(rule); + qualifiedNameToRuleBuilder.put(qualifiedName, rule); + String uniqueName = name; + String antlrRuleName; + if (names.containsKey(name)) { + name = qualifiedName; + uniqueName = getInheritedUniqueName(rule, usedUniqueNames); + antlrRuleName = getInheritedAntlrRuleName(rule, usedAntlrNames); + } else { + antlrRuleName = getDefaultAntlrRuleName(rule); + } + names.put(name, rule); + if (!usedUniqueNames.add(uniqueName)) { + throw new IllegalStateException(uniqueName); + } + uniqueNameToRuleBuilder.put(uniqueName, rule); + if (!usedAntlrNames.add(antlrRuleName)) { + throw new IllegalStateException(antlrRuleName); + } + antlrNameToRuleBuilder.put(antlrRuleName, rule); + if (installAdapter) { + installAdapterIfMissing(adapter, rule); + } + } + simpleNameToRules = simpleNameToRulesBuilder.build(); + qualifiedNameToRule = qualifiedNameToRuleBuilder.build(); + nameToRule = ImmutableBiMap.copyOf(names); + uniqueNameToRule = uniqueNameToRuleBuilder.build(); + antlrNameToRule = antlrNameToRuleBuilder.build(); + this.allRules = ImmutableList.copyOf(allRules); + } + + private void installAdapterIfMissing(Adapter adapter, EObject context) { + if (EcoreUtil.getAdapter(context.eAdapters(), RuleNames.class) == null) { + context.eAdapters().add(adapter); + } else { + throw new IllegalStateException("Duplicate adapter"); + } + } + + public List getRulesBySimpleName(String name) { + return simpleNameToRules.get(name); + } + + public AbstractRule getRuleByQualifiedName(String name) { + return qualifiedNameToRule.get(name); + } + + public String getQualifiedName(AbstractRule rule) { + return GrammarUtil.getGrammar(rule).getName() + "." + rule.getName(); + } + + public String getUniqueRuleName(AbstractRule rule) { + return uniqueNameToRule.inverse().get(rule); + } + + public AbstractRule getRuleByUniqueName(String uniqueName) { + return uniqueNameToRule.get(uniqueName); + } + + public String getAntlrRuleName(AbstractRule rule) { + return antlrNameToRule.inverse().get(rule); + } + + public AbstractRule getRuleByAntlrName(String name) { + return antlrNameToRule.get(name); + } + + public String getBestRuleName(AbstractRule rule) { + return nameToRule.inverse().get(rule); + } + + public Grammar getContextGrammar() { + return contextGrammar; + } + + public List getAllRules() { + return allRules; + } + + public Iterable getAllParserRules() { + return Iterables.filter(allRules, ParserRule.class); + } + + private String getInheritedAntlrRuleName(AbstractRule rule, Set usedNames) { + if (rule instanceof ParserRule || rule instanceof EnumRule) { + String candidate = "super" + rule.getName(); + int i = 1; + while(usedNames.contains(candidate)) { + candidate = "super" + i + rule.getName(); + i++; + } + return candidate; + } + if (rule instanceof TerminalRule) { + String candidate = "SUPER_" + rule.getName(); + int i = 1; + while(usedNames.contains(candidate)) { + candidate = "SUPER_" + i + "_" + rule.getName(); + i++; + } + return candidate; + } + throw new IllegalArgumentException(rule.eClass().getName()); + } + + private String getInheritedUniqueName(AbstractRule rule, Set usedNames) { + String grammarName = GrammarUtil.getName(GrammarUtil.getGrammar(rule)); + String candidate = grammarName + rule.getName(); + int i = 1; + while(usedNames.contains(candidate)) { + candidate = grammarName + i + rule.getName(); + i++; + } + return candidate; + } + + private String getDefaultAntlrRuleName(AbstractRule rule) { + if (rule instanceof ParserRule || rule instanceof EnumRule) { + return "rule" + rule.getName(); + } + if (rule instanceof TerminalRule) { + return "RULE_" + rule.getName().toUpperCase(); + } + throw new IllegalArgumentException(rule.eClass().getName()); + } + +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/Xtext.xtext b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/Xtext.xtext index c96693791..22437b58d 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/Xtext.xtext +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/Xtext.xtext @@ -12,13 +12,13 @@ import "http://www.eclipse.org/emf/2002/Ecore" as ecore Grammar: 'grammar' name=GrammarID ('with' usedGrammars+=[Grammar|GrammarID] (',' usedGrammars+=[Grammar|GrammarID])*)? - (definesHiddenTokens?='hidden' '(' (hiddenTokens+=[AbstractRule] (',' hiddenTokens+=[AbstractRule])*)? ')')? + (definesHiddenTokens?='hidden' '(' (hiddenTokens+=[AbstractRule|RuleID] (',' hiddenTokens+=[AbstractRule|RuleID])*)? ')')? metamodelDeclarations+=AbstractMetamodelDeclaration* (rules+=AbstractRule)+ ; GrammarID returns ecore::EString: - ID ('.' ID)*; + ValidID ('.' ValidID)*; AbstractRule : ParserRule | TerminalRule | EnumRule; @@ -28,25 +28,46 @@ AbstractMetamodelDeclaration : // constraint: typeSelect(GeneratedMetamodel).size() == typeSelect(GeneratedMetamodel).alias.size() // generated metamodels have to have different aliases GeneratedMetamodel : - 'generate' name=ID ePackage=[ecore::EPackage|STRING] ('as' alias=ID)?; + 'generate' name=ValidID ePackage=[ecore::EPackage|STRING] ('as' alias=ValidID)?; // referenced metamodels may share aliases with other referenced metamodels // and with generated metamodels ReferencedMetamodel : - 'import' ePackage=[ecore::EPackage|STRING] ('as' alias=ID)?; + 'import' ePackage=[ecore::EPackage|STRING] ('as' alias=ValidID)?; + +//fragment Alias returns AbstractMetamodelDeclaration: +// 'as' alias=ValidID +//; ParserRule : - name=ID ('returns' type=TypeRef)? (definesHiddenTokens?='hidden' '(' (hiddenTokens+=[AbstractRule] (',' hiddenTokens+=[AbstractRule])*)? ')')?':' + ( + ^fragment?='fragment' RuleNameAndParams (wildcard?='*' | ('returns' type=TypeRef)?) + | RuleNameAndParams ('returns' type=TypeRef)? + ) + (definesHiddenTokens?='hidden' '(' (hiddenTokens+=[AbstractRule|RuleID] (',' hiddenTokens+=[AbstractRule|RuleID])*)? ')')? ':' alternatives=Alternatives ';' ; +fragment RuleNameAndParams returns ParserRule: + name=ValidID ('<' (parameters+=Parameter (',' parameters+=Parameter)*)? '>')? +; + +Parameter: + name=ID +; + TypeRef : (metamodel=[AbstractMetamodelDeclaration] '::')? classifier=[ecore::EClassifier] ; Alternatives returns AbstractElement: - UnorderedGroup ({Alternatives.elements+=current} ('|' elements+=UnorderedGroup)+)? + ConditionalBranch ({Alternatives.elements+=current} ('|' elements+=ConditionalBranch)+)? +; + +ConditionalBranch returns AbstractElement: + UnorderedGroup + | {Group} '<' guardCondition=Disjunction '>' (elements+=AbstractToken)+ ; UnorderedGroup returns AbstractElement: @@ -64,11 +85,11 @@ AbstractToken returns AbstractElement: /* SuppressWarnings[potentialOverride]: Handled in CardinalityAwareEcoreFactory */ AbstractTokenWithCardinality returns AbstractElement: - (Assignment | AbstractTerminal) (cardinality=('?'|'*'|'+'))? + (Assignment | AbstractTerminal) cardinality=('?'|'*'|'+')? ; Action returns Action: - '{' type=TypeRef ('.' feature=ID operator=('='|'+=') 'current')? '}' + '{' type=TypeRef ('.' feature=ValidID operator=('='|'+=') 'current')? '}' ; AbstractTerminal returns AbstractElement: @@ -82,8 +103,6 @@ AbstractTerminal returns AbstractElement: PredicatedRuleCall | // We have to make this one explicit since the ParenthesizedElement does not // create an object but we have to set the predicated flag - // TODO: As soon as we have an own element for parenthesized elements with - // cardinality, we should refactor this part of the grammar PredicatedGroup ; @@ -92,7 +111,60 @@ Keyword : ; RuleCall : - rule=[AbstractRule] + rule=[AbstractRule|RuleID] ('<' arguments+=NamedArgument (',' arguments+=NamedArgument)* '>')? +; + +NamedArgument: + ( parameter=[Parameter|ID] calledByName?= '=')? + ( value=ConditionOrLiteral ) +; + +ConditionOrLiteral returns Condition: + LiteralCondition | Disjunction +; + +LiteralCondition: + {LiteralCondition} (true?='true' | 'false') +; + +Disjunction returns Condition: + Conjunction ({Disjunction.left=current} '|' right=Conjunction)* +; + +Conjunction returns Condition: + Negation ({Conjunction.left=current} '&' right=Negation)* +; + +Negation returns Condition: + Atom | {Negation} '!' value=Negation +; + +Atom returns Condition: + ParameterReference | ParenthesizedCondition +; + +ParenthesizedCondition returns Condition: + '(' Disjunction ')' +; + +ParameterReference: + parameter=[Parameter|ID] +; + +TerminalRuleCall returns RuleCall: + rule=[AbstractRule|RuleID] +; + +RuleID returns ecore::EString: + ValidID ('::' ValidID)* +; + +ValidID returns ecore::EString: + ID | 'true' | 'false' +; + +Boolean returns ecore::EBoolean: + 'true' | 'false' ; PredicatedKeyword returns Keyword: @@ -100,11 +172,11 @@ PredicatedKeyword returns Keyword: ; PredicatedRuleCall returns RuleCall: - (predicated?='=>' | firstSetPredicated?='->') rule=[AbstractRule] + (predicated?='=>' | firstSetPredicated?='->') rule=[AbstractRule] ('<' arguments+=NamedArgument (',' arguments+=NamedArgument)* '>')? ; Assignment returns Assignment: - (predicated?='=>' | firstSetPredicated?='->')? feature=ID operator=('+='|'='|'?=') ^terminal=AssignableTerminal + (predicated?='=>' | firstSetPredicated?='->')? feature=ValidID operator=('+='|'='|'?=') ^terminal=AssignableTerminal ; AssignableTerminal returns AbstractElement: @@ -136,7 +208,7 @@ PredicatedGroup returns Group: ; TerminalRule : - 'terminal' (^fragment?='fragment' name=ID | name=ID ('returns' type=TypeRef)?) ':' + 'terminal' (^fragment?='fragment' name=ValidID | name=ValidID ('returns' type=TypeRef)?) ':' alternatives=TerminalAlternatives ';' ; @@ -151,11 +223,11 @@ TerminalGroup returns AbstractElement: /* SuppressWarnings[potentialOverride]: Handled in CardinalityAwareEcoreFactory */ TerminalToken returns AbstractElement: - TerminalTokenElement (cardinality=('?'|'*'|'+'))? + TerminalTokenElement cardinality=('?'|'*'|'+')? ; TerminalTokenElement returns AbstractElement: - CharacterRange | RuleCall | ParenthesizedTerminalElement | AbstractNegatedToken | Wildcard | ^EOF + CharacterRange | TerminalRuleCall | ParenthesizedTerminalElement | AbstractNegatedToken | Wildcard | ^EOF ; ParenthesizedTerminalElement returns AbstractElement: @@ -187,7 +259,7 @@ CharacterRange returns AbstractElement: ; EnumRule: - 'enum' name=ID ('returns' type=TypeRef)? ':' + 'enum' name=ValidID ('returns' type=TypeRef)? ':' alternatives=EnumLiterals ';' ; diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/common/services/DefaultTerminalConverters.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/common/services/DefaultTerminalConverters.java index fd209c323..8cd4942ce 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/common/services/DefaultTerminalConverters.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/common/services/DefaultTerminalConverters.java @@ -31,6 +31,17 @@ public class DefaultTerminalConverters extends AbstractDeclarativeValueConverter return idValueConverter; } + @Inject + private AbstractIDValueConverter terminalsIdValueConverter; + + /** + * @since 2.9 + */ + @ValueConverter(rule = "org.eclipse.xtext.common.Terminals.ID") + public IValueConverter TerminalsID() { + return terminalsIdValueConverter; + } + @Inject private INTValueConverter intValueConverter; @@ -38,6 +49,17 @@ public class DefaultTerminalConverters extends AbstractDeclarativeValueConverter public IValueConverter INT() { return intValueConverter; } + + @Inject + private INTValueConverter terminalsIntValueConverter; + + /** + * @since 2.9 + */ + @ValueConverter(rule = "org.eclipse.xtext.common.Terminals.INT") + public IValueConverter TerminalsINT() { + return terminalsIntValueConverter; + } @Inject private STRINGValueConverter stringValueConverter; @@ -47,4 +69,15 @@ public class DefaultTerminalConverters extends AbstractDeclarativeValueConverter return stringValueConverter; } + @Inject + private STRINGValueConverter terminalsStringValueConverter; + + /** + * @since 2.9 + */ + @ValueConverter(rule = "org.eclipse.xtext.common.Terminals.STRING") + public IValueConverter TerminalsSTRING() { + return terminalsStringValueConverter; + } + } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/common/services/Ecore2XtextTerminalConverters.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/common/services/Ecore2XtextTerminalConverters.java index e9015db8a..d482340a1 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/common/services/Ecore2XtextTerminalConverters.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/common/services/Ecore2XtextTerminalConverters.java @@ -14,9 +14,12 @@ import org.eclipse.xtext.conversion.ValueConverter; import org.eclipse.xtext.conversion.impl.AbstractNullSafeConverter; import org.eclipse.xtext.nodemodel.INode; +import com.google.inject.Singleton; + /** * @author Jan Koehnlein - Initial contribution and API */ +@Singleton public class Ecore2XtextTerminalConverters extends DefaultTerminalConverters { private static final Pattern ID_PATTERN = Pattern.compile("\\p{Alpha}\\w*"); diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/conversion/ValueConverter.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/conversion/ValueConverter.java index 9f7b4c280..b04cfa3f4 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/conversion/ValueConverter.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/conversion/ValueConverter.java @@ -12,13 +12,22 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.eclipse.xtext.conversion.impl.AbstractDeclarativeValueConverterService; + /** - * @author Sven Efftinge - Initial contribution and API - * + * Annotate methods in your own {@link AbstractDeclarativeValueConverterService} + * to mark them as contributions to all known value converters. * + * @author Sven Efftinge - Initial contribution and API */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD}) public @interface ValueConverter { + /** + * The name of the rule that is converted by this value converter. + * May be a qualified name, e.g. {@code com.acme.MySuperLang.MyDataTypeRule} to + * provide value converters for inherited rules that are called with the explicit + * notation {@code super::MyDataTypeRule} or {@code com::acme::MySuperLang::MyDataTypeRule}. + */ String rule(); } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/conversion/impl/AbstractDeclarativeValueConverterService.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/conversion/impl/AbstractDeclarativeValueConverterService.java index 8aa1f6c55..6f084b751 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/conversion/impl/AbstractDeclarativeValueConverterService.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/conversion/impl/AbstractDeclarativeValueConverterService.java @@ -10,6 +10,7 @@ package org.eclipse.xtext.conversion.impl; import static org.eclipse.xtext.GrammarUtil.*; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.Set; @@ -22,6 +23,7 @@ import org.eclipse.xtext.Grammar; import org.eclipse.xtext.GrammarUtil; import org.eclipse.xtext.IGrammarAccess; import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.RuleNames; import org.eclipse.xtext.TerminalRule; import org.eclipse.xtext.conversion.IValueConverter; import org.eclipse.xtext.conversion.IValueConverterService; @@ -55,6 +57,9 @@ public abstract class AbstractDeclarativeValueConverterService extends AbstractV @Inject protected DefaultTerminalConverter.Factory defaultTerminalConverterFactory; + @Inject + private RuleNames ruleNames; + @Inject public void setGrammar(IGrammarAccess grammarAccess) { this.grammar = grammarAccess.getGrammar(); @@ -105,33 +110,48 @@ public abstract class AbstractDeclarativeValueConverterService extends AbstractV * @nooverride This method is not intended to be re-implemented or extended by clients. * @noreference This method is not intended to be referenced by clients. */ - @SuppressWarnings("unchecked") protected void internalRegisterForClass(Class clazz, Map> converters) { + recursiveRegisterForClass(clazz, converters); + registerEFactoryConverters(converters); + } + + /** + * @since 2.9 + */ + protected void recursiveRegisterForClass(Class clazz, Map> converters) { Method[] methods = clazz.getDeclaredMethods(); Set thisConverters = Sets.newHashSet(); + recursiveRegisterForClass(methods, thisConverters, true, converters); + recursiveRegisterForClass(methods, thisConverters, false, converters); + if (clazz.getSuperclass() != null) { + recursiveRegisterForClass(clazz.getSuperclass(), converters); + } + } + + private void recursiveRegisterForClass(Method[] methods, Set thisConverters, boolean onlyExplicitConverters, + Map> converters) { for (Method method : methods) { if (isConfigurationMethod(method)) { try { String ruleName = method.getAnnotation(ValueConverter.class).rule(); - AbstractRule rule = GrammarUtil.findRuleForName(getGrammar(), ruleName); - if (rule != null) { - if (rule instanceof TerminalRule) { - if (((TerminalRule) rule).isFragment()) - throw new IllegalStateException("Tried to register a value converter for a fragment terminal rule: '" + ruleName + "'"); + if (onlyExplicitConverters ^ ruleName.indexOf('.') < 0) { + AbstractRule rule = GrammarUtil.findRuleForName(getGrammar(), ruleName); + if (rule != null) { + if (rule instanceof TerminalRule) { + if (((TerminalRule) rule).isFragment()) + throw new IllegalStateException("Tried to register a value converter for a fragment terminal rule: '" + ruleName + "'"); + } + if (!thisConverters.add(ruleName)) { + throw new IllegalStateException("Tried to register two value converters for rule '" + ruleName + "'"); + } + IValueConverter converter = registerIfMissing(ruleName, rule, method, converters); + String qualifiedName = ruleNames.getQualifiedName(rule); + registerIfMissing(qualifiedName, rule, converter, method, converters); + } else { + log.trace("Tried to register value converter for rule '" + ruleName + + "' which is not available in the grammar."); } - if (!thisConverters.add(ruleName)) { - throw new IllegalStateException("Tried to register two value converters for rule '" + ruleName + "'"); - } - if (!converters.containsKey(ruleName)) { - IValueConverter valueConverter = (IValueConverter) method.invoke(this); - if (valueConverter instanceof IValueConverter.RuleSpecific) - ((IValueConverter.RuleSpecific) valueConverter).setRule(rule); - converters.put(ruleName, valueConverter); - } - } else - log.trace("Tried to register value converter for rule '" + ruleName - + "' which is not available in the grammar."); - + } } catch (IllegalStateException e) { throw e; } catch (IllegalArgumentException e) { @@ -141,10 +161,35 @@ public abstract class AbstractDeclarativeValueConverterService extends AbstractV } } } - if (clazz.getSuperclass() != null) { - internalRegisterForClass(clazz.getSuperclass(), converters); + } + + private IValueConverter registerIfMissing(String name, AbstractRule rule, Method method, + Map> converters) throws IllegalAccessException, InvocationTargetException { + if (!converters.containsKey(name)) { + IValueConverter valueConverter = reflectiveGetConverter(method, rule); + converters.put(name, valueConverter); + return valueConverter; + } + return null; + } + + @SuppressWarnings("unchecked") + private IValueConverter reflectiveGetConverter(Method method, AbstractRule rule) + throws IllegalAccessException, InvocationTargetException { + IValueConverter valueConverter = (IValueConverter) method.invoke(this); + if (valueConverter instanceof IValueConverter.RuleSpecific) + ((IValueConverter.RuleSpecific) valueConverter).setRule(rule); + return valueConverter; + } + + private void registerIfMissing(String name, AbstractRule rule, IValueConverter valueConverter, Method method, + Map> converters) throws IllegalAccessException, InvocationTargetException { + if (!converters.containsKey(name)) { + if (valueConverter == null) { + valueConverter = reflectiveGetConverter(method, rule); + } + converters.put(name, valueConverter); } - registerEFactoryConverters(converters); } protected boolean isConfigurationMethod(Method method) { @@ -159,20 +204,33 @@ public abstract class AbstractDeclarativeValueConverterService extends AbstractV */ protected void registerEFactoryConverters(Map> converters) { for (ParserRule parserRule : allParserRules(getGrammar())) { - if (isDatatypeRule(parserRule) && !converters.containsKey(parserRule.getName())) { - EDataType datatype = (EDataType) parserRule.getType().getClassifier(); - converters.put(parserRule.getName(), new EFactoryValueConverter(datatype)); + if (isDatatypeRule(parserRule)) { + registerIfMissing(parserRule.getName(), parserRule, converters); + registerIfMissing(ruleNames.getQualifiedName(parserRule), parserRule, converters); } } for (TerminalRule terminalRule : allTerminalRules(getGrammar())) { if (!terminalRule.isFragment()) { - String terminalRuleName = terminalRule.getName(); - if (!converters.containsKey(terminalRuleName)) { - converters.put(terminalRuleName, defaultTerminalConverterFactory.create(terminalRule)); - } + registerIfMissing(terminalRule.getName(), terminalRule, converters); + registerIfMissing(ruleNames.getQualifiedName(terminalRule), terminalRule, converters); } } } + + private void registerIfMissing(String name, TerminalRule terminalRule, + Map> converters) { + if (!converters.containsKey(name)) { + converters.put(name, defaultTerminalConverterFactory.create(terminalRule)); + } + } + + private void registerIfMissing(String name, ParserRule parserRule, + Map> converters) { + if (!converters.containsKey(name)) { + EDataType datatype = (EDataType) parserRule.getType().getClassifier(); + converters.put(name, new EFactoryValueConverter(datatype)); + } + } public void setDefaultTerminalConverterFactory(DefaultTerminalConverter.Factory defaultTerminalConverterFactory) { this.defaultTerminalConverterFactory = defaultTerminalConverterFactory; diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/conversion/impl/AbstractLexerBasedConverter.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/conversion/impl/AbstractLexerBasedConverter.java index 4af9f7f33..e435721f6 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/conversion/impl/AbstractLexerBasedConverter.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/conversion/impl/AbstractLexerBasedConverter.java @@ -19,6 +19,7 @@ import org.eclipse.xtext.conversion.ValueConverterException; import org.eclipse.xtext.parser.antlr.ITokenDefProvider; import org.eclipse.xtext.parser.antlr.Lexer; import org.eclipse.xtext.parser.antlr.LexerBindings; +import org.eclipse.xtext.parser.antlr.TokenTool; import com.google.inject.Inject; import com.google.inject.Provider; @@ -109,7 +110,7 @@ public abstract class AbstractLexerBasedConverter extends AbstractValueConver // TODO: rename to getLexerRuleName() on next API change protected String getRuleName(Token token) { String result = getTokenDefMap().get(token.getType()); - return result.substring("RULE_".length()); + return TokenTool.getLexerRuleName(result); } protected Map getTokenDefMap() { diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting/impl/MatcherState.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting/impl/MatcherState.java index 8c7dd013b..717f28788 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting/impl/MatcherState.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/formatting/impl/MatcherState.java @@ -10,7 +10,6 @@ package org.eclipse.xtext.formatting.impl; import java.util.List; 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; @@ -108,8 +107,7 @@ public class MatcherState extends AbstractNFAState, T extends INFATransitio addOutgoing(((Assignment) element).getTerminal(), visited, isRuleCall, loopCenter); else if (element instanceof CrossReference) addOutgoing(((CrossReference) element).getTerminal(), visited, isRuleCall, loopCenter); - else if (element instanceof RuleCall - && ((RuleCall) element).getRule().getType().getClassifier() instanceof EClass) { + else if (GrammarUtil.isEObjectRuleCall(element)) { addOutgoing(((RuleCall) element).getRule().getAlternatives(), visited, true, loopCenter); collectOutgoingByContainer(element, visited, isRuleCall, loopCenter); } else { @@ -213,7 +211,7 @@ public class AbstractNFAState, T extends INFATransitio protected boolean filter(AbstractElement ele) { AbstractRule rule = GrammarUtil.containingRule(ele); - if (rule == null || !(rule.getType().getClassifier() instanceof EClass)) + if (rule == null || !GrammarUtil.isEObjectRule(rule)) return true; return builder.filter(ele); } @@ -227,7 +225,7 @@ public class AbstractNFAState, T extends INFATransitio for (EObject root : element.eResource().getContents()) if (root instanceof Grammar) for (AbstractRule rule : ((Grammar) root).getRules()) - if (rule.getType().getClassifier() instanceof EClass) + if (GrammarUtil.isEObjectRule(rule)) for (AbstractElement ele : EcoreUtil2.eAllOfType(rule, AbstractElement.class)) if (!builder.filter(ele)) builder.getState(ele).getOutgoing(); diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/grammaranalysis/impl/GrammarElementTitleSwitch.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/grammaranalysis/impl/GrammarElementTitleSwitch.java index 67a0119cb..909e87e83 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/grammaranalysis/impl/GrammarElementTitleSwitch.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/grammaranalysis/impl/GrammarElementTitleSwitch.java @@ -169,7 +169,7 @@ public class GrammarElementTitleSwitch extends XtextSwitch implements Fu o = (o == null) ? "" : o; String result; if (showActionAsRuleCall && f != null) { - result = f + o + new Context2NameFunction().apply(object) + card(object); + result = f + o + new Context2NameFunction().toFunction(null).apply(object) + card(object); } else { String t = object.getType() != null && object.getType().getClassifier() != null ? object.getType() .getClassifier().getName() : "null"; diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/linking/impl/AbstractCleaningLinker.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/linking/impl/AbstractCleaningLinker.java index 72a13ec59..6f8de0379 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/linking/impl/AbstractCleaningLinker.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/linking/impl/AbstractCleaningLinker.java @@ -17,8 +17,11 @@ import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.Action; import org.eclipse.xtext.Assignment; import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.RuleCall; import org.eclipse.xtext.diagnostics.IDiagnosticConsumer; import org.eclipse.xtext.linking.lazy.LazyLinkingResource; import org.eclipse.xtext.nodemodel.ICompositeNode; @@ -123,9 +126,17 @@ public abstract class AbstractCleaningLinker extends AbstractLinker { EObject grammarElement = node.getGrammarElement(); if (grammarElement instanceof AbstractElement) { ICompositeNode parent = node.getParent(); - if (parent != null && !parent.hasDirectSemanticElement()) { - Assignment assignment = GrammarUtil.containingAssignment(grammarElement); - return assignment == null; + if (parent != null) { + if (!parent.hasDirectSemanticElement()) { + Assignment assignment = GrammarUtil.containingAssignment(grammarElement); + return assignment == null; + } + if (grammarElement instanceof Action) { + ParserRule rule = (ParserRule) GrammarUtil.containingRule(grammarElement); + if (rule.isFragment()) { + return parent.getGrammarElement() instanceof RuleCall; + } + } } } return false; diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/linking/impl/Linker.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/linking/impl/Linker.java index db927ef34..905d8b101 100755 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/linking/impl/Linker.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/linking/impl/Linker.java @@ -18,8 +18,11 @@ import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.util.InternalEList; +import org.eclipse.xtext.AbstractRule; import org.eclipse.xtext.CrossReference; import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.RuleCall; import org.eclipse.xtext.diagnostics.DiagnosticMessage; import org.eclipse.xtext.diagnostics.IDiagnosticConsumer; import org.eclipse.xtext.diagnostics.IDiagnosticProducer; @@ -53,22 +56,29 @@ public class Linker extends AbstractCleaningLinker { if (node == null) return; Set handledReferences = new HashSet(); - ensureLinked(obj, producer, node, handledReferences); + ensureLinked(obj, producer, node, handledReferences, false); producer.setNode(node); setDefaultValues(obj, handledReferences, producer); } private void ensureLinked(EObject obj, IDiagnosticProducer producer, ICompositeNode node, - Set handledReferences) { + Set handledReferences, boolean dontCheckParent) { for(INode abstractNode = node.getFirstChild(); abstractNode != null; abstractNode = abstractNode.getNextSibling()) { - if (abstractNode.getGrammarElement() instanceof CrossReference) { - CrossReference ref = (CrossReference) abstractNode.getGrammarElement(); + EObject grammarElement = abstractNode.getGrammarElement(); + if (grammarElement instanceof CrossReference) { + CrossReference ref = (CrossReference) grammarElement; producer.setNode(abstractNode); ensureIsLinked(obj, abstractNode, ref, handledReferences, producer); + } else if (grammarElement instanceof RuleCall && abstractNode instanceof ICompositeNode) { + RuleCall ruleCall = (RuleCall) grammarElement; + AbstractRule calledRule = ruleCall.getRule(); + if (calledRule instanceof ParserRule && ((ParserRule) calledRule).isFragment()) { + ensureLinked(obj, producer, (ICompositeNode) abstractNode, handledReferences, true); + } } } - if (shouldCheckParentNode(node)) { - ensureLinked(obj, producer, node.getParent(), handledReferences); + if (!dontCheckParent && shouldCheckParentNode(node)) { + ensureLinked(obj, producer, node.getParent(), handledReferences, false); } } @@ -77,10 +87,11 @@ public class Linker extends AbstractCleaningLinker { } private void setDefaultValues(EObject obj, Set references, IDiagnosticProducer producer) { - for (EReference ref : obj.eClass().getEAllReferences()) + for (EReference ref : obj.eClass().getEAllReferences()) { if (canSetDefaultValues(ref) && !references.contains(ref) && !obj.eIsSet(ref) && !ref.isDerived()) { setDefaultValue(obj, ref, producer); } + } } protected boolean canSetDefaultValues(EReference ref) { diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/linking/lazy/LazyLinker.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/linking/lazy/LazyLinker.java index 20de387f2..f5a3b13f7 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/linking/lazy/LazyLinker.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/linking/lazy/LazyLinker.java @@ -28,11 +28,13 @@ import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.ecore.util.InternalEList; import org.eclipse.xtext.AbstractMetamodelDeclaration; +import org.eclipse.xtext.AbstractRule; import org.eclipse.xtext.CrossReference; import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.GrammarUtil; import org.eclipse.xtext.IGrammarAccess; import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.RuleCall; import org.eclipse.xtext.diagnostics.IDiagnosticConsumer; import org.eclipse.xtext.diagnostics.IDiagnosticProducer; import org.eclipse.xtext.linking.impl.AbstractCleaningLinker; @@ -110,11 +112,11 @@ public class LazyLinker extends AbstractCleaningLinker { ICompositeNode node = NodeModelUtils.getNode(obj); if (node == null) return; - installProxies(obj, producer, settingsToLink, node); + installProxies(obj, producer, settingsToLink, node, false); } private void installProxies(EObject obj, IDiagnosticProducer producer, - Multimap settingsToLink, ICompositeNode parentNode) { + Multimap settingsToLink, ICompositeNode parentNode, boolean dontCheckParent) { final EClass eClass = obj.eClass(); if (eClass.getEAllReferences().size() - eClass.getEAllContainments().size() == 0) return; @@ -137,10 +139,16 @@ public class LazyLinker extends AbstractCleaningLinker { createAndSetProxy(obj, node, eRef); afterCreateAndSetProxy(obj, node, eRef, crossReference, producer); } + } else if (grammarElement instanceof RuleCall && node instanceof ICompositeNode) { + RuleCall ruleCall = (RuleCall) grammarElement; + AbstractRule calledRule = ruleCall.getRule(); + if (calledRule instanceof ParserRule && ((ParserRule) calledRule).isFragment()) { + installProxies(obj, producer, settingsToLink, (ICompositeNode) node, true); + } } } - if (shouldCheckParentNode(parentNode)) { - installProxies(obj, producer, settingsToLink, parentNode.getParent()); + if (!dontCheckParent && shouldCheckParentNode(parentNode)) { + installProxies(obj, producer, settingsToLink, parentNode.getParent(), dontCheckParent); } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parser/antlr/AbstractSplittingTokenSource.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parser/antlr/AbstractSplittingTokenSource.java index b8e9870ce..f753c57a2 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parser/antlr/AbstractSplittingTokenSource.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parser/antlr/AbstractSplittingTokenSource.java @@ -24,7 +24,11 @@ import com.google.inject.Inject; */ public abstract class AbstractSplittingTokenSource implements TokenSource { - public static final String LEXER_RULE_PREFIX = "RULE_"; + /** + * @deprecated use {@link TokenTool#LEXER_RULE_PREFIX} if strictly necessary. + */ + @Deprecated + public static final String LEXER_RULE_PREFIX = TokenTool.LEXER_RULE_PREFIX; private TokenSource delegate; @@ -74,9 +78,7 @@ public abstract class AbstractSplittingTokenSource implements TokenSource { } public String getLexerRuleName(String antlrTokenDef) { - if (antlrTokenDef.startsWith(LEXER_RULE_PREFIX)) - return antlrTokenDef.substring(LEXER_RULE_PREFIX.length()); - return antlrTokenDef; + return TokenTool.getLexerRuleName(antlrTokenDef); } @Inject diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parser/antlr/AntlrTokenDefProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parser/antlr/AntlrTokenDefProvider.java index 75b212e97..ac8e495fd 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parser/antlr/AntlrTokenDefProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parser/antlr/AntlrTokenDefProvider.java @@ -62,7 +62,7 @@ public class AntlrTokenDefProvider implements ITokenDefProvider { antlrTokenDef = Strings.convertFromJavaString(antlrTokenDef, true); antlrTokenDef = "'" + antlrTokenDef + "'"; tokenDefMap.put(antlrTokenType, antlrTokenDef); - } else if (antlrTokenDef.startsWith("RULE_") || isKeywordToken(antlrTokenDef)) { + } else if (antlrTokenDef.startsWith("RULE_") || isKeywordToken(antlrTokenDef) || antlrTokenDef.startsWith("SUPER_")) { tokenDefMap.put(antlrTokenType, antlrTokenDef); } line = br.readLine(); diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parser/antlr/TokenTool.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parser/antlr/TokenTool.java index 7be1e296c..e33a87ab4 100755 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parser/antlr/TokenTool.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parser/antlr/TokenTool.java @@ -8,6 +8,9 @@ *******************************************************************************/ package org.eclipse.xtext.parser.antlr; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import org.antlr.runtime.CommonToken; import org.antlr.runtime.Token; @@ -53,12 +56,20 @@ public class TokenTool { public static final String LEXER_RULE_PREFIX = "RULE_"; + private static final Pattern superRulePattern = Pattern.compile("^(SUPER_(\\d+_)?).*$"); + public static boolean isLexerRule(String antlrTokenDef) { return antlrTokenDef.startsWith(LEXER_RULE_PREFIX); } public static String getLexerRuleName(String antlrTokenDef) { - return antlrTokenDef.substring(LEXER_RULE_PREFIX.length()); + if (antlrTokenDef.startsWith(LEXER_RULE_PREFIX)) + return antlrTokenDef.substring(LEXER_RULE_PREFIX.length()); + Matcher matcher = superRulePattern.matcher(antlrTokenDef); + if (matcher.matches()) { + return antlrTokenDef.substring(matcher.group(1).length()); + } + return antlrTokenDef; } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parser/impl/PartialParsingHelper.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parser/impl/PartialParsingHelper.java index e22074919..fb789cfa6 100755 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parser/impl/PartialParsingHelper.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parser/impl/PartialParsingHelper.java @@ -281,9 +281,13 @@ public class PartialParsingHelper implements IPartialParsingHelper { return true; if (candidate.getGrammarElement() instanceof RuleCall) { AbstractRule rule = ((RuleCall) candidate.getGrammarElement()).getRule(); - if (!(rule instanceof ParserRule) || GrammarUtil.isDatatypeRule((ParserRule) rule)) + if (!(rule instanceof ParserRule)) return true; - else if (isInvalidDueToPredicates((AbstractElement) candidate.getGrammarElement())) + ParserRule casted = (ParserRule) rule; + if (GrammarUtil.isDatatypeRule(casted) || casted.isFragment() || !casted.getParameters().isEmpty()) { + return true; + } + if (isInvalidDueToPredicates((AbstractElement) candidate.getGrammarElement())) return true; } if (candidate.getGrammarElement() instanceof Action) { diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parsetree/reconstr/impl/DefaultHiddenTokenHelper.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parsetree/reconstr/impl/DefaultHiddenTokenHelper.java index 7c9447a1e..cdd509bcd 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parsetree/reconstr/impl/DefaultHiddenTokenHelper.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parsetree/reconstr/impl/DefaultHiddenTokenHelper.java @@ -8,10 +8,10 @@ package org.eclipse.xtext.parsetree.reconstr.impl; import org.eclipse.xtext.AbstractRule; -import org.eclipse.xtext.GrammarUtil; -import org.eclipse.xtext.IGrammarAccess; import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.RuleNames; +import com.google.common.collect.Iterables; import com.google.inject.Inject; /** @@ -47,8 +47,8 @@ public class DefaultHiddenTokenHelper extends AbstractHiddenTokenHelper { } @Inject - private void setGrammar(IGrammarAccess grammar) { - wsRule = GrammarUtil.findRuleForName(grammar.getGrammar(), "WS"); + private void setGrammar(RuleNames names) { + wsRule = Iterables.getFirst(names.getRulesBySimpleName("WS"), null); } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parsetree/reconstr/impl/TreeConstState.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parsetree/reconstr/impl/TreeConstState.java index f3a5ba08d..4099b1b02 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parsetree/reconstr/impl/TreeConstState.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/parsetree/reconstr/impl/TreeConstState.java @@ -20,13 +20,11 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.xtext.AbstractElement; import org.eclipse.xtext.Action; import org.eclipse.xtext.Assignment; import org.eclipse.xtext.GrammarUtil; -import org.eclipse.xtext.RuleCall; import org.eclipse.xtext.TypeRef; import org.eclipse.xtext.grammaranalysis.IGrammarNFAProvider.NFABuilder; import org.eclipse.xtext.grammaranalysis.impl.AbstractNFAState; @@ -272,7 +270,7 @@ public class TreeConstState extends AbstractNFAState { +public class Context2NameFunction { - @Override - public String apply(EObject from) { - return getContextName(from); + public Function toFunction(final Grammar grammar) { + return new Function() { + @Override + public String apply(EObject from) { + return getContextName(grammar, from); + } + }; } - - public String getContextName(Action ctx) { + + public String getContextName(Grammar grammar, Action ctx) { ParserRule rule = EcoreUtil2.getContainerOfType(ctx, ParserRule.class); - return rule.getName() + "_" + getUniqueActionName(ctx); + return getContextName(grammar, rule) + "_" + getUniqueActionName(ctx); } - public String getContextName(EObject ctx) { + public String getContextName(Grammar grammar, EObject ctx) { if (GrammarUtil.isEObjectRule(ctx)) - return getContextName((ParserRule) ctx); + return getContextName(grammar, (ParserRule) ctx); if (GrammarUtil.isAssignedAction(ctx)) - return getContextName((Action) ctx); + return getContextName(grammar, (Action) ctx); throw new RuntimeException("Invalid Context: " + EmfFormatter.objPath(ctx)); } - public String getContextName(ParserRule ctx) { - return ctx.getName(); + public String getContextName(Grammar grammar, ParserRule ctx) { + if (grammar == null) { + return ctx.getName(); + } + return RuleNames.getRuleNames(grammar, false).getUniqueRuleName(ctx); } public String getUniqueActionName(Action action) { diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ContextPDAProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ContextPDAProvider.java index 7649de725..0b7ffd36e 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ContextPDAProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ContextPDAProvider.java @@ -10,7 +10,6 @@ package org.eclipse.xtext.serializer.analysis; import java.util.Map; import java.util.Set; -import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.AbstractElement; import org.eclipse.xtext.Action; @@ -116,7 +115,7 @@ public class ContextPDAProvider implements IContextPDAProvider { @Override public AbstractElement getCall(AbstractElement ele) { if (ele instanceof RuleCall && !GrammarUtil.isAssigned(ele) - && ((RuleCall) ele).getRule().getType().getClassifier() instanceof EClass) + && GrammarUtil.isEObjectRuleCall(ele)) return ((RuleCall) ele).getRule().getAlternatives(); return null; } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ContextProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ContextProvider.java index 8e8fe76ed..6ed82d083 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ContextProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/analysis/ContextProvider.java @@ -39,7 +39,7 @@ public class ContextProvider implements IContextProvider { protected void collectTypesForContext(TypeFinderState state, Set types, boolean allowLocal, boolean hasAssignment, Set visited) { - hasAssignment = hasAssignment || state.getGrammarElement() instanceof Assignment; + hasAssignment = hasAssignment || state.getGrammarElement() instanceof Assignment || GrammarUtil.isEObjectFragmentRuleCall(state.getGrammarElement()); if (allowLocal) { if (state.getGrammarElement() instanceof Action) { types.add((EClass) ((Action) state.getGrammarElement()).getType().getClassifier()); @@ -47,10 +47,14 @@ public class ContextProvider implements IContextProvider { } } if (state.isEndState() && !GrammarUtil.isUnassignedEObjectRuleCall(state.getGrammarElement())) { - if (hasAssignment) - types.add((EClass) GrammarUtil.containingRule(state.getGrammarElement()).getType().getClassifier()); - else + if (hasAssignment) { + ParserRule rule = (ParserRule) GrammarUtil.containingRule(state.getGrammarElement()); + if (!rule.isFragment()) { + types.add((EClass) rule.getType().getClassifier()); + } + } else { types.add(null); + } } if (!visited.add(state)) return; 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 9463f5021..703c58431 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 @@ -32,7 +32,9 @@ import org.eclipse.xtext.Group; import org.eclipse.xtext.Keyword; import org.eclipse.xtext.ParserRule; import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.RuleNames; 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; @@ -185,17 +187,20 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { case ASSIGNED_TERMINAL_RULE_CALL: EClass type = ele.getContainingConstraint().getType(); EStructuralFeature feature = type.getEStructuralFeature(ele.getFeatureName()); - if (feature == null) - throw new RuntimeException("Feature " + ele.getFeatureName() + " not found in " + if (feature == null) { + // TODO find a meaningful way to handle this + System.err.println("Feature " + ele.getFeatureName() + " not found in " + type.getName()); - 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); + } 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: @@ -490,10 +495,6 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { return false; } - protected String context2Name(EObject context) { - return ((Constraint) getContainingConstraint()).provider.context2Name.apply(context); - } - @Override public boolean equals(Object obj) { if (!(obj instanceof ConstraintElement)) @@ -1016,6 +1017,9 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { @Inject protected Context2NameFunction context2Name; + + @Inject + protected RuleNames ruleNames; @Inject protected IContextProvider contextProvider; @@ -1040,20 +1044,32 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { } else if (ele instanceof RuleCall) { RuleCall rc = (RuleCall) ele; if (GrammarUtil.isUnassignedEObjectRuleCall(rc)) { - if (!visited.add(rc)) - return null; - ConstraintElement result = createConstraintElement((ParserRule) rc.getRule(), requiredType, visited); - if (result != null && result != INVALID) { - if (rc.getRule().getType().getClassifier() == requiredType) - result.typeMatch(); - if (result.isTypeMatch()) - return result; + 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; } - return isOptional ? null : INVALID; - } else if (GrammarUtil.containingAssignment(ele) != null) + } else if (GrammarUtil.containingAssignment(ele) != null) { return new ConstraintElement(context, getConstraintElementType(ele), ele); - else + } else { return null; + } } else if (ele instanceof Keyword) { if (GrammarUtil.containingAssignment(ele) != null) return new ConstraintElement(context, getConstraintElementType(ele), ele); @@ -1235,7 +1251,7 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { protected ConstraintElement createConstraintElement(ParserRule rule, EClass requiredType, Set visited) { if (!visited.add(rule)) return INVALID; - if (GrammarUtil.containsAssignedAction(rule)) { + if (GrammarUtil.containsAssignedAction(rule)) { // also check actions in used fragments ActionFilterState start = nfaProvider.getNFA(rule.getAlternatives()); return createConstraintElement(rule, start, requiredType, false, visited); } else { @@ -1256,7 +1272,7 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { for (Collection equal : equalConstraints.values()) { // Collection equal = filterConstraintsFromSubGrammars(grammar, allEqual); IConstraint representative = findRepresentativeConstraint(equal); - ((Constraint) representative).setName(findBestConstraintName(equal)); + ((Constraint) representative).setName(findBestConstraintName(grammar, equal)); for (IConstraint constraint : equal) allConstraints.put(constraint, representative); } @@ -1320,7 +1336,7 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { collectElements(e, result); } - protected String findBestConstraintName(Collection equalConstraints) { + protected String findBestConstraintName(Grammar grammar, Collection equalConstraints) { Set relevantRules = Sets.newLinkedHashSet(); Set relevantActions = Sets.newLinkedHashSet(); Set contextRules = Sets.newLinkedHashSet(); @@ -1366,7 +1382,7 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { for (Action a : relevantActions) actions.add(context2Name.getUniqueActionName(a)); for (ParserRule a : relevantRules) - rules.add(context2Name.getContextName(a)); + rules.add(context2Name.getContextName(grammar, a)); Collections.sort(rules); String result = Joiner.on("_").join(rules); if (!actions.isEmpty()) { @@ -1422,9 +1438,9 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { throw new RuntimeException("Unknown Grammar Element: " + EmfFormatter.objPath(ele)); } - protected IConstraintContext getConstraints(Action context) { + protected IConstraintContext getConstraints(Grammar grammar, Action context) { AssignedActionConstraintContext result = new AssignedActionConstraintContext(context, - context2Name.getContextName(context)); + context2Name.getContextName(grammar, context)); ActionFilterState start = nfaProvider.getNFA(context); Set types = contextProvider.getTypesForContext(context); for (EClass type : types) { @@ -1440,7 +1456,8 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { Constraint constraint = new ActionConstraint(context, type, ce, this); result.addConstraint(constraint); } else - System.err.println("constraint is " + ce + " for context " + context2Name.getContextName(context) + // TODO find a meaningful way to handle this + System.err.println("constraint is " + ce + " for context " + context2Name.getContextName(grammar, context) + " and type " + type.getName()); } } @@ -1452,22 +1469,23 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { List result = cache.get(context); if (result == null) { result = Lists.newArrayList(); - for (ParserRule parserRule : GrammarUtil.allParserRules(context)) - if (parserRule.getType().getClassifier() instanceof EClass) { - result.add(getConstraints(parserRule)); + for (ParserRule parserRule : GrammarUtil.allParserRules(context)) { + if (parserRule.getType() != null && parserRule.getType().getClassifier() instanceof EClass) { + result.add(getConstraints(context, parserRule)); for (Action action : GrammarUtil.containedActions(parserRule)) if (action.getFeature() != null) - result.add(getConstraints(action)); + result.add(getConstraints(context, action)); } + } filterDuplicateConstraintsAndSetNames(context, result); cache.put(context, result); } return result; } - protected IConstraintContext getConstraints(ParserRule context) { + protected IConstraintContext getConstraints(Grammar grammar, ParserRule context) { ParserRuleConstraintContext result = new ParserRuleConstraintContext(context, - context2Name.getContextName(context)); + context2Name.getContextName(grammar, context)); Set types = contextProvider.getTypesForContext(context); for (EClass type : types) { if (type == null) { @@ -1482,7 +1500,8 @@ public class GrammarConstraintProvider implements IGrammarConstraintProvider { Constraint constraint = new RuleConstraint(context, type, ce, this); result.addConstraint(constraint); } else - System.err.println("constraint is " + ce + " for context " + context2Name.getContextName(context) + // TODO find a meaningful way to handle this + System.err.println("constraint is " + ce + " for context " + context2Name.getContextName(grammar, context) + " and type " + type.getName()); } } 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 6d70c0ef4..e946fde84 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 @@ -25,8 +25,8 @@ import org.eclipse.xtext.Assignment; import org.eclipse.xtext.CrossReference; import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.GrammarUtil; -import org.eclipse.xtext.IGrammarAccess; import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.RuleNames; import org.eclipse.xtext.grammaranalysis.impl.GrammarElementTitleSwitch; import org.eclipse.xtext.serializer.analysis.ISyntacticSequencerPDAProvider.ISynAbsorberState; import org.eclipse.xtext.serializer.analysis.ISyntacticSequencerPDAProvider.SynAbsorberNfaAdapter; @@ -179,7 +179,7 @@ public class SemanticSequencerNfaProvider implements ISemanticSequencerNfaProvid protected Map elementIDCache; @Inject - protected IGrammarAccess grammar; + protected RuleNames ruleNames; @Inject protected ISyntacticSequencerPDAProvider pdaProvider; @@ -200,7 +200,7 @@ public class SemanticSequencerNfaProvider implements ISemanticSequencerNfaProvid if (elementIDCache == null) { elementIDCache = Maps.newHashMap(); int counter = 0; - for (ParserRule pr : GrammarUtil.allParserRules(grammar.getGrammar())) + for (ParserRule pr : ruleNames.getAllParserRules()) for (AbstractElement e : EcoreUtil2.getAllContentsOfType(pr, AbstractElement.class)) elementIDCache.put(e, counter++); } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/ISerializationDiagnostic.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/ISerializationDiagnostic.java index c2d5fef58..d1897fdf8 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/ISerializationDiagnostic.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/ISerializationDiagnostic.java @@ -12,6 +12,7 @@ import java.util.List; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.xtext.Grammar; import org.eclipse.xtext.serializer.analysis.Context2NameFunction; import org.eclipse.xtext.util.EmfFormatter; @@ -33,9 +34,12 @@ public interface ISerializationDiagnostic { public class ExceptionDiagnostic implements ISerializationDiagnostic { protected Throwable exception; + + protected Grammar grammar; - public ExceptionDiagnostic(Throwable exception) { + public ExceptionDiagnostic(Grammar grammar, Throwable exception) { this.exception = exception; + this.grammar = grammar; } @Override @@ -62,6 +66,11 @@ public interface ISerializationDiagnostic { public EObject getContext() { return null; } + + @Override + public Grammar getGrammar() { + return grammar; + } @Override public String getId() { @@ -90,7 +99,7 @@ public interface ISerializationDiagnostic { result.add("URI: " + eObject.eResource().getURI()); } if (diagnostic.getContext() != null) - result.add("Context: " + new Context2NameFunction().getContextName(diagnostic.getContext())); + result.add("Context: " + new Context2NameFunction().getContextName(diagnostic.getGrammar(), diagnostic.getContext())); if (diagnostic.getEStructuralFeature() != null) { EStructuralFeature feature = diagnostic.getEStructuralFeature(); EClass eClass = feature.getEContainingClass(); @@ -140,6 +149,8 @@ public interface ISerializationDiagnostic { EObject getSemanticObject(); EObject getContext(); + + Grammar getGrammar(); String getId(); diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/SequencerDiagnosticProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/SequencerDiagnosticProvider.java index 23a7d20f0..0b6a2c12f 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/SequencerDiagnosticProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/SequencerDiagnosticProvider.java @@ -74,7 +74,7 @@ public class SequencerDiagnosticProvider implements ISemanticSequencerDiagnostic @Override public ISerializationDiagnostic createFeatureValueMissing(EObject semanticObject, EStructuralFeature feature) { String msg = "A value for feature '" + feature.getName() + "' is missing but required."; - return new SerializationDiagnostic(FEATURE_VALUE_MISSING, semanticObject, msg); + return new SerializationDiagnostic(FEATURE_VALUE_MISSING, semanticObject, grammarAccess.getGrammar(), msg); } @Override @@ -89,18 +89,18 @@ public class SequencerDiagnosticProvider implements ISemanticSequencerDiagnostic else otherValidCtxs.add(ctx); } - String contextName = context2Name.apply(context); + String contextName = context2Name.getContextName(grammarAccess.getGrammar(), context); String semanticType = semanticObject.eClass().getName(); - String recommendCtxNames = Joiner.on(", ").join(Iterables.transform(recommendedCtxs, context2Name)); + String recommendCtxNames = Joiner.on(", ").join(Iterables.transform(recommendedCtxs, context2Name.toFunction(grammarAccess.getGrammar()))); String validTypeNames = Joiner.on(", ").join(Iterables.transform(validTypes, new NamedElement2Name())); StringBuilder msg = new StringBuilder(); msg.append("The context '" + contextName + "' is not valid for type '" + semanticType + "'\n"); msg.append("Recommended contexts for type '" + semanticType + "': " + recommendCtxNames + "\n"); if (!otherValidCtxs.isEmpty()) msg.append("Other valid contexts for type '" + semanticType + "': " - + Joiner.on(", ").join(Iterables.transform(otherValidCtxs, context2Name))); + + Joiner.on(", ").join(Iterables.transform(otherValidCtxs, context2Name.toFunction(grammarAccess.getGrammar())))); msg.append("The context '" + contextName + "' is valid for types: " + validTypeNames + "\n"); - return new SerializationDiagnostic(INVALID_CONTEXT_OR_TYPE, semanticObject, msg.toString()); + return new SerializationDiagnostic(INVALID_CONTEXT_OR_TYPE, semanticObject, grammarAccess.getGrammar(), msg.toString()); } protected List getValidContexts(EClass clazz) { @@ -135,7 +135,7 @@ public class SequencerDiagnosticProvider implements ISemanticSequencerDiagnostic msg.append("Could not serialize EObject via backtracking.\n"); msg.append("Constraint: " + grammar + "\n"); msg.append(semanticObject.getValuesString()); - return new SerializationDiagnostic(BACKTRACKING_FAILED, semanticObject.getEObject(), context, msg.toString()); + return new SerializationDiagnostic(BACKTRACKING_FAILED, semanticObject.getEObject(), context, grammarAccess.getGrammar(), msg.toString()); } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/SerializationDiagnostic.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/SerializationDiagnostic.java index 880944d70..8ca89dc55 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/SerializationDiagnostic.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/SerializationDiagnostic.java @@ -12,6 +12,7 @@ import static org.eclipse.xtext.serializer.impl.FeatureFinderUtil.*; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.Grammar; /** * @author Moritz Eysholdt - Initial contribution and API @@ -23,6 +24,8 @@ public class SerializationDiagnostic implements ISerializationDiagnostic { protected String message; protected EObject semanticObject; + + protected Grammar grammar; protected EObject context; @@ -30,41 +33,44 @@ public class SerializationDiagnostic implements ISerializationDiagnostic { private Throwable throwable; - public SerializationDiagnostic(String id, EObject semantic, AbstractElement element, String msg, Throwable exc) { - this(id, semantic, semantic != null ? getFeature(element, semantic.eClass()) : null, msg, exc); + public SerializationDiagnostic(String id, EObject semantic, AbstractElement element, Grammar grammar, String msg, Throwable exc) { + this(id, semantic, semantic != null ? getFeature(element, semantic.eClass()) : null, grammar, msg, exc); } - public SerializationDiagnostic(String id, EObject semanticObject, AbstractElement element, String message) { - this(id, semanticObject, semanticObject != null ? getFeature(element, semanticObject.eClass()) : null, message); + public SerializationDiagnostic(String id, EObject semanticObject, AbstractElement element, Grammar grammar, String message) { + this(id, semanticObject, semanticObject != null ? getFeature(element, semanticObject.eClass()) : null, grammar, message); } - public SerializationDiagnostic(String id, EObject semanticObject, EStructuralFeature feature, String message) { + public SerializationDiagnostic(String id, EObject semanticObject, EStructuralFeature feature, Grammar grammar, String message) { super(); this.id = id; this.semanticObject = semanticObject; + this.grammar = grammar; this.message = message; this.feature = feature; } - public SerializationDiagnostic(String id, EObject semanticObject, EStructuralFeature feature, String message, Throwable throwable) { + public SerializationDiagnostic(String id, EObject semanticObject, EStructuralFeature feature, Grammar grammar, String message, Throwable throwable) { super(); this.id = id; this.semanticObject = semanticObject; + this.grammar = grammar; this.throwable = throwable; this.feature = feature; this.message = message; } - public SerializationDiagnostic(String id, EObject semanticObject, EObject context, String message) { + public SerializationDiagnostic(String id, EObject semanticObject, EObject context, Grammar grammar, String message) { super(); this.id = id; this.semanticObject = semanticObject; + this.grammar = grammar; this.message = message; this.context = context; } - public SerializationDiagnostic(String id, EObject semanticObject, String message) { - this(id, semanticObject, (EStructuralFeature) null, message); + public SerializationDiagnostic(String id, EObject semanticObject, Grammar grammar, String message) { + this(id, semanticObject, (EStructuralFeature) null, grammar, message); } @Override @@ -91,6 +97,11 @@ public class SerializationDiagnostic implements ISerializationDiagnostic { public EObject getContext() { return context; } + + @Override + public Grammar getGrammar() { + return grammar; + } @Override public String getId() { diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/SyntacticSequencerDiagnosticProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/SyntacticSequencerDiagnosticProvider.java index 62b0440bb..f13859582 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/SyntacticSequencerDiagnosticProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/SyntacticSequencerDiagnosticProvider.java @@ -11,6 +11,7 @@ import java.util.List; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.AbstractElement; +import org.eclipse.xtext.IGrammarAccess; import org.eclipse.xtext.RuleCall; import org.eclipse.xtext.grammaranalysis.impl.GrammarElementTitleSwitch; import org.eclipse.xtext.serializer.analysis.ISyntacticSequencerPDAProvider.ISynAbsorberState; @@ -21,12 +22,16 @@ import org.eclipse.xtext.serializer.sequencer.RuleCallStack; import com.google.common.base.Joiner; import com.google.common.collect.Lists; +import com.google.inject.Inject; /** * @author Moritz Eysholdt - Initial contribution and API */ public class SyntacticSequencerDiagnosticProvider implements ISyntacticSequencerDiagnosticProvider { + @Inject + private IGrammarAccess grammarAccess; + @Override public ISerializationDiagnostic createInvalidFollowingAbsorberDiagnostic(EObject context, EObject semanticObject, ISynAbsorberState from, AbstractElement to) { @@ -42,7 +47,7 @@ public class SyntacticSequencerDiagnosticProvider implements ISyntacticSequencer msg.append("State '" + toName + "' may not follow '" + fromName + "'.\n"); msg.append("Valid followers are: " + Joiner.on(", ").join(targets)); - return new SerializationDiagnostic(INVALID_FOLLOWING_ABSORBER, semanticObject, context, msg.toString()); + return new SerializationDiagnostic(INVALID_FOLLOWING_ABSORBER, semanticObject, context, grammarAccess.getGrammar(), msg.toString()); } @Override @@ -60,6 +65,6 @@ public class SyntacticSequencerDiagnosticProvider implements ISyntacticSequencer buf.append("Found on top of the stack: " + poppedStr + "\n"); buf.append("Expected: " + toConsume + "\n"); buf.append("Rest of the stack: " + stack + "\n"); - return new SerializationDiagnostic(UNEXPECTED_STACK_TRACE, semanticObject, toConsume.getContext(), buf.toString()); + return new SerializationDiagnostic(UNEXPECTED_STACK_TRACE, semanticObject, toConsume.getContext(), grammarAccess.getGrammar(), buf.toString()); } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/TokenDiagnosticProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/TokenDiagnosticProvider.java index cb94c91c1..9f56f6397 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/TokenDiagnosticProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/diagnostic/TokenDiagnosticProvider.java @@ -15,6 +15,7 @@ import org.eclipse.xtext.AbstractElement; import org.eclipse.xtext.CrossReference; import org.eclipse.xtext.EnumLiteralDeclaration; import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.IGrammarAccess; import org.eclipse.xtext.RuleCall; import org.eclipse.xtext.linking.impl.LinkingHelper; import org.eclipse.xtext.scoping.IScope; @@ -31,6 +32,9 @@ public class TokenDiagnosticProvider implements ITokenDiagnosticProvider { @Inject private LinkingHelper linkingHelper; + + @Inject + private IGrammarAccess grammarAccess; protected String getFullReferenceName(EObject semanticObject, CrossReference reference) { EReference ref = GrammarUtil.getReference(reference); @@ -49,7 +53,7 @@ public class TokenDiagnosticProvider implements ITokenDiagnosticProvider { StringBuilder msg = new StringBuilder(); msg.append("The value '" + value + "' is invalid for enum " + rc.getRule().getName() + "\n"); msg.append("Valid values are: " + Joiner.on(", ").join(valid)); - return new SerializationDiagnostic(INVALID_ENUM_VALUE, semanticObject, rc, msg.toString()); + return new SerializationDiagnostic(INVALID_ENUM_VALUE, semanticObject, rc, grammarAccess.getGrammar(), msg.toString()); } @Override @@ -57,19 +61,19 @@ public class TokenDiagnosticProvider implements ITokenDiagnosticProvider { CrossReference element, EObject target, IScope scope) { String msg = "No EObjectDescription could be found in Scope " + getFullReferenceName(semanticObject, element) + " for " + EmfFormatter.objPath(target); - return new SerializationDiagnostic(NO_EOBJECT_DESCRIPTION_FOUND, semanticObject, element, msg); + return new SerializationDiagnostic(NO_EOBJECT_DESCRIPTION_FOUND, semanticObject, element, grammarAccess.getGrammar(), msg); } @Override public ISerializationDiagnostic getNoScopeFoundDiagnostic(EObject semanticObject, CrossReference element, EObject target) { String msg = "Could not create Scope for EReference " + getFullReferenceName(semanticObject, element); - return new SerializationDiagnostic(NO_SCOPE_FOUND, semanticObject, element, msg); + return new SerializationDiagnostic(NO_SCOPE_FOUND, semanticObject, element, grammarAccess.getGrammar(), msg); } @Override public ISerializationDiagnostic getNullNotAllowedDiagnostic(EObject semanticObject, AbstractElement ele) { - return new SerializationDiagnostic(NULL_NOT_ALLOWED, semanticObject, ele, "Must not be null"); + return new SerializationDiagnostic(NULL_NOT_ALLOWED, semanticObject, ele, grammarAccess.getGrammar(), "Must not be null"); } @Override @@ -82,7 +86,7 @@ public class TokenDiagnosticProvider implements ITokenDiagnosticProvider { msg.append("' for grammar rule '"); msg.append(ruleName); msg.append("' to string via ValueConverter."); - return new SerializationDiagnostic(VALUE_CONVERSION_EXCEPTION, semantic, element, msg.toString(), exception); + return new SerializationDiagnostic(VALUE_CONVERSION_EXCEPTION, semantic, element, grammarAccess.getGrammar(), msg.toString(), exception); } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/impl/Serializer.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/impl/Serializer.java index 9c4293923..8320cf9cf 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/impl/Serializer.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/impl/Serializer.java @@ -120,7 +120,7 @@ public class Serializer implements ISerializer { else formatterTokenStream = formatter.createFormatterStream(null, tokenStream, !options.isFormatting()); EObject context = getContext(obj); - ISequenceAcceptor acceptor = new TokenStreamSequenceAdapter(formatterTokenStream, errors); + ISequenceAcceptor acceptor = new TokenStreamSequenceAdapter(formatterTokenStream, grammar.getGrammar(), errors); serialize(obj, context, acceptor, errors); formatterTokenStream.flush(); } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/sequencer/AbstractSyntacticSequencer.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/sequencer/AbstractSyntacticSequencer.java index 511576fc0..92d322da8 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/sequencer/AbstractSyntacticSequencer.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/sequencer/AbstractSyntacticSequencer.java @@ -444,8 +444,10 @@ public abstract class AbstractSyntacticSequencer implements ISyntacticSequencer, if (fromState instanceof ISynNavigable) { ISynNavigable fromEmitter = (ISynNavigable) fromState; // RCStack back = stack.clone(); - if (fromEmitter.hasEmitters()) - accept(fromNode, fromEmitter.getShortestStackpruningPathToAbsorber(stack), stack); + if (fromEmitter.hasEmitters()) { + List path = fromEmitter.getShortestStackpruningPathToAbsorber(stack); + accept(fromNode, path, stack); + } return fromEmitter.getTarget(); } return null; 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 44a314a26..a424d5725 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 @@ -20,7 +20,7 @@ import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.xtext.AbstractElement; import org.eclipse.xtext.AbstractRule; import org.eclipse.xtext.GrammarUtil; -import org.eclipse.xtext.IGrammarAccess; +import org.eclipse.xtext.RuleNames; import org.eclipse.xtext.nodemodel.INode; import org.eclipse.xtext.serializer.analysis.IGrammarConstraintProvider; import org.eclipse.xtext.serializer.analysis.IGrammarConstraintProvider.IConstraint; @@ -55,7 +55,7 @@ public class ContextFinder implements IContextFinder { protected Map, IConstraint> constraints; @Inject - protected IGrammarAccess grammar; + protected RuleNames ruleNames; @Inject protected IGrammarConstraintProvider grammarConstraintProvider; @@ -223,7 +223,7 @@ public class ContextFinder implements IContextFinder { } protected EObject getRootContext() { - for (AbstractRule rule : grammar.getGrammar().getRules()) + for (AbstractRule rule : ruleNames.getAllRules()) if (GrammarUtil.isEObjectRule(rule)) return rule; throw new RuntimeException("There is no parser rule in the grammar."); @@ -232,7 +232,7 @@ public class ContextFinder implements IContextFinder { protected void initConstraints() { if (constraintContexts == null) { constraints = Maps.newLinkedHashMap(); - constraintContexts = grammarConstraintProvider.getConstraints(grammar.getGrammar()); + constraintContexts = grammarConstraintProvider.getConstraints(ruleNames.getContextGrammar()); // System.out.println(Joiner.on("\n").join(constraintContexts)); for (IConstraintContext ctx : constraintContexts) for (IConstraint constraint : ctx.getConstraints()) diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/sequencer/NodeModelSemanticSequencer.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/sequencer/NodeModelSemanticSequencer.java index afac3558e..6cdfabee4 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/sequencer/NodeModelSemanticSequencer.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/sequencer/NodeModelSemanticSequencer.java @@ -14,6 +14,7 @@ import org.eclipse.xtext.GrammarUtil; import org.eclipse.xtext.Keyword; import org.eclipse.xtext.ParserRule; import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.RuleNames; import org.eclipse.xtext.TerminalRule; import org.eclipse.xtext.conversion.IValueConverterService; import org.eclipse.xtext.nodemodel.BidiIterator; @@ -30,6 +31,9 @@ public class NodeModelSemanticSequencer extends AbstractSemanticSequencer { @Inject protected IValueConverterService valueConverter; + + @Inject + protected RuleNames ruleNames; @Override public void createSequence(EObject context, EObject semanticObject) { @@ -46,7 +50,7 @@ public class NodeModelSemanticSequencer extends AbstractSemanticSequencer { acceptSemantic(semanticObject, rc, semanticObject.eGet(feature), node.getFirst()); } else { String strVal = NodeModelUtils.getTokenText(node.getFirst()); - Object val = valueConverter.toValue(strVal, rc.getRule().getName(), node.getFirst()); + Object val = valueConverter.toValue(strVal, ruleNames.getQualifiedName(rc.getRule()), node.getFirst()); acceptSemantic(semanticObject, rc, val, node.getFirst()); } } else if (node.getSecond() instanceof Keyword) diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/tokens/ValueSerializer.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/tokens/ValueSerializer.java index 59cff26aa..0eda3760d 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/tokens/ValueSerializer.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/serializer/tokens/ValueSerializer.java @@ -9,6 +9,7 @@ package org.eclipse.xtext.serializer.tokens; import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.RuleCall; +import org.eclipse.xtext.RuleNames; import org.eclipse.xtext.conversion.IValueConverterService; import org.eclipse.xtext.nodemodel.INode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; @@ -25,6 +26,9 @@ public class ValueSerializer implements IValueSerializer { @Inject private IValueConverterService converter; + + @Inject + private RuleNames ruleNames; @Inject protected ITokenDiagnosticProvider diagnostics; @@ -35,7 +39,7 @@ public class ValueSerializer implements IValueSerializer { @Override public boolean isValid(EObject context, RuleCall ruleCall, Object value, Acceptor errors) { try { - String str = converter.toString(value, ruleCall.getRule().getName()); + String str = converter.toString(value, ruleNames.getQualifiedName(ruleCall.getRule())); if (str != null) return true; if (errors != null) @@ -51,12 +55,12 @@ public class ValueSerializer implements IValueSerializer { @Override public String serializeAssignedValue(EObject context, RuleCall ruleCall, Object value, INode node, Acceptor errors) { if (node != null) { - Object converted = converter.toValue(NodeModelUtils.getTokenText(node), ruleCall.getRule().getName(), node); + Object converted = converter.toValue(NodeModelUtils.getTokenText(node), ruleNames.getQualifiedName(ruleCall.getRule()), node); if (converted != null && converted.equals(value)) return tokenUtil.serializeNode(node); } try { - String str = converter.toString(value, ruleCall.getRule().getName()); + String str = converter.toString(value, ruleNames.getQualifiedName(ruleCall.getRule())); if (str != null) return str; if (errors != null) diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/CurrentTypeFinder.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/CurrentTypeFinder.java index c16a0e0b6..9454140ae 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/CurrentTypeFinder.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/CurrentTypeFinder.java @@ -125,12 +125,25 @@ public class CurrentTypeFinder { @Override public Boolean caseRuleCall(RuleCall object) { EClassifier wasType = currentType; + AbstractRule calledRule = object.getRule(); if (currentType == null) { - if (object.getRule() instanceof ParserRule && !GrammarUtil.isDatatypeRule((ParserRule) object.getRule())) { - TypeRef returnType = object.getRule().getType(); - if (returnType != null) - currentType = returnType.getClassifier(); + if (calledRule instanceof ParserRule && !GrammarUtil.isDatatypeRule((ParserRule) calledRule)) { + ParserRule parserRule = (ParserRule) calledRule; + if (parserRule.isFragment()) { + if (context.getType() != null) + currentType = context.getType().getClassifier(); + if (!parserRule.isWildcard()) { + doSwitch(parserRule.getAlternatives()); + } + } else { + TypeRef returnType = calledRule.getType(); + if (returnType != null) { + currentType = returnType.getClassifier(); + } + } } + } else if (isFragmentButNotWildcard(calledRule)) { + doSwitch(calledRule.getAlternatives()); } if (object == stopElement) return true; @@ -139,6 +152,14 @@ public class CurrentTypeFinder { return false; } + private boolean isFragmentButNotWildcard(AbstractRule calledRule) { + if (calledRule instanceof ParserRule) { + ParserRule casted = (ParserRule) calledRule; + return casted.isFragment() && !casted.isWildcard(); + } + return false; + } + protected EClassifier getCompatibleType(EClassifier a, EClassifier b, EObject context) { if (a == null) return b; diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/KeywordInspector.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/KeywordInspector.java index 17dfbb72d..596ecc4f4 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/KeywordInspector.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/KeywordInspector.java @@ -45,7 +45,7 @@ public class KeywordInspector { List rules = GrammarUtil.allTerminalRules(grammar); for(TerminalRule rule: rules) { if (!rule.isFragment()) { - AbstractElement element = rule.getAlternatives(); + AbstractElement element = rule.getAlternatives(); if (element instanceof Keyword && Strings.isEmpty(element.getCardinality())) { String value = ((Keyword) element).getValue(); if (value.equals(keyword.getValue())) diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/OverriddenValueInspector.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/OverriddenValueInspector.java index ee27ff297..a14ef9795 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/OverriddenValueInspector.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/OverriddenValueInspector.java @@ -44,10 +44,13 @@ public class OverriddenValueInspector extends XtextRuleInspector permanentlyVisited; + private Set fragmentStack; + public OverriddenValueInspector(ValidationMessageAcceptor acceptor) { super(acceptor); assignedFeatures = newMultimap(); permanentlyVisited = Sets.newHashSet(); + fragmentStack = Sets.newHashSet(); } @Override @@ -102,20 +105,34 @@ public class OverriddenValueInspector extends XtextRuleInspector sources = Lists.newArrayList(assignedFeatures.get(feature)); assignedFeatures.replaceValues(feature, Collections. emptyList()); if (sources != null && sources.equals(Collections.singletonList(object))) { - if (getNestingLevel() == 0) - acceptWarning("The assigned value of feature '" + feature - + "' will possibly override itself because it is used inside of a loop.", object, null); + if (getNestingLevel() == 0 && fragmentStack.isEmpty()) { + if (object instanceof RuleCall) { + acceptWarning("The fragment will possibly override the assigned value of feature '" + feature + + "' it is used inside of a loop.", object, null); + } else { + acceptWarning("The assigned value of feature '" + feature + + "' will possibly override itself because it is used inside of a loop.", object, null); + } + } } else { if (sources != null) { - if (getNestingLevel() == 0) - for (AbstractElement source : sources) + if (getNestingLevel() == 0 && fragmentStack.isEmpty()) { + for (AbstractElement source : sources) { acceptWarning("The possibly assigned value of feature '" + feature + "' may be overridden by subsequent assignments.", source, null); + } + } + } + if (getNestingLevel() == 0 && fragmentStack.isEmpty()) { + if (object instanceof RuleCall) { + acceptWarning("The fragment will potentially override the possibly assigned value of feature '" + + feature + "'.", object, null); + } else { + acceptWarning("This assignment will override the possibly assigned value of feature '" + + feature + "'.", object, null); + } } - if (getNestingLevel() == 0) - acceptWarning("This assignment will override the possibly assigned value of feature '" - + feature + "'.", object, null); } } else { @@ -131,6 +148,11 @@ public class OverriddenValueInspector extends XtextRuleInspector prevAssignedFeatures = assignedFeatures; @@ -143,6 +165,22 @@ public class OverriddenValueInspector extends XtextRuleInspector prevAssignedFeatures = assignedFeatures; + assignedFeatures = newMultimap(); + if (fragmentStack.add(object)) { + try { + doSwitch(object.getRule().getAlternatives()); + } finally { + fragmentStack.remove(object); + } + } + Multimap assignedByFragment = assignedFeatures; + assignedFeatures = prevAssignedFeatures; + for (String feature : assignedByFragment.keySet()) + checkAssignment(object, feature); + } + @Override public Boolean caseAlternatives(Alternatives object) { Multimap prevAssignedFeatures = assignedFeatures; diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/RuleWithoutInstantiationInspector.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/RuleWithoutInstantiationInspector.java index 8ef8905ac..4e4a0f2f3 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/RuleWithoutInstantiationInspector.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/RuleWithoutInstantiationInspector.java @@ -43,7 +43,7 @@ public class RuleWithoutInstantiationInspector extends XtextRuleInspector 1) { + Grammar grammar = GrammarUtil.getGrammar(context); + if (grammar != null) { + String firstSegment = qn.getFirstSegment(); + if (qn.getSegmentCount() == 2) { + String ruleName = qn.getLastSegment(); + if (SUPER.equals(firstSegment)) { + for(Grammar parent: grammar.getUsedGrammars()) { + AbstractRule superRule = GrammarUtil.findRuleForName(parent, ruleName); + if (superRule != null) { + return EObjectDescription.create(qn, superRule); + } + } + } + if (firstSegment.equals(GrammarUtil.getName(grammar))) { + AbstractRule rule = GrammarUtil.findRuleForName(grammar, grammar.getName() + "." + ruleName); + if (rule != null) { + return EObjectDescription.create(qn, rule); + } + } + for(Grammar usedGrammar: GrammarUtil.allUsedGrammars(grammar)) { + if (firstSegment.equals(GrammarUtil.getName(usedGrammar))) { + AbstractRule rule = GrammarUtil.findRuleForName(usedGrammar, usedGrammar.getName() + "." + ruleName); + if (rule != null) { + return EObjectDescription.create(qn, rule); + } + } + } + } + AbstractRule result = GrammarUtil.findRuleForName(grammar, qn.toString()); + if (result != null) { + return EObjectDescription.create(qn, result); + } + } + } + return null; + } + + @Override + public Iterable getElements(QualifiedName name) { + IEObjectDescription result = doGetSingleElement(name); + if (result != null) { + return Collections.singletonList(result); + } + return Collections.emptyList(); + } + + @Override + public IEObjectDescription getSingleElement(EObject object) { + return Iterables.getFirst(getElements(object), null); + } + + @Override + public Iterable getElements(EObject object) { + if (object instanceof AbstractRule) { + Grammar grammar = GrammarUtil.getGrammar(context); + AbstractRule rule = (AbstractRule) object; + if (GrammarUtil.getGrammar(rule) == grammar) { + return Lists.newArrayList( + EObjectDescription.create(GrammarUtil.getName(grammar) + "." + rule.getName(), rule), + EObjectDescription.create(grammar.getName() + "." + rule.getName(), rule)); + } + List result = Lists.newArrayList( + EObjectDescription.create(SUPER + "." + rule.getName(), rule), + EObjectDescription.create(GrammarUtil.getName(grammar) + "." + rule.getName(), rule), + EObjectDescription.create(grammar.getName() + "." + rule.getName(), rule)); + AbstractRule contextRule = GrammarUtil.containingRule(context); + if (contextRule != null && contextRule.getName().equals(rule.getName())) { + result.add(0, EObjectDescription.create(SUPER, rule)); + } + return result; + } + return Collections.emptyList(); + } + + @Override + public Iterable getAllElements() { + AbstractRule contextRule = GrammarUtil.containingRule(context); + Grammar grammar = contextRule != null ? GrammarUtil.getGrammar(contextRule) : GrammarUtil.getGrammar(context); + Map result = Maps.newLinkedHashMap(); + if (grammar != null) { + String shortName = GrammarUtil.getName(grammar) + "."; + String longName = grammar.getName() + "."; + for(AbstractRule rule: grammar.getRules()) { + putIfAbsent(EObjectDescription.create(shortName + rule.getName(), rule), result); + putIfAbsent(EObjectDescription.create(longName + rule.getName(), rule), result); + } + boolean waitingForSuper = contextRule != null; + for(Grammar usedGrammar: GrammarUtil.allUsedGrammars(grammar)) { + shortName = GrammarUtil.getName(usedGrammar) + "."; + longName = usedGrammar.getName() + "."; + for(AbstractRule rule: usedGrammar.getRules()) { + if (waitingForSuper) { + assert contextRule != null; + if (rule.getName().equals(contextRule.getName())) { + putIfAbsent(EObjectDescription.create(SUPER, rule), result); + waitingForSuper = false; + } + } + putIfAbsent(EObjectDescription.create(SUPER + "." + rule.getName(), rule), result); + putIfAbsent(EObjectDescription.create(shortName + rule.getName(), rule), result); + putIfAbsent(EObjectDescription.create(longName + rule.getName(), rule), result); + } + } + } + return Lists.newArrayList(result.values()); + } + + private void putIfAbsent(IEObjectDescription desc, Map result) { + if (!result.containsKey(desc.getName())) { + result.put(desc.getName(), desc); + } + } + +} diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextCrossReferenceSerializer.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextCrossReferenceSerializer.java index 43ab50f79..a239e19be 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextCrossReferenceSerializer.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextCrossReferenceSerializer.java @@ -10,34 +10,36 @@ package org.eclipse.xtext.xtext; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EReference; +import org.eclipse.xtext.AbstractMetamodelDeclaration; import org.eclipse.xtext.AbstractRule; import org.eclipse.xtext.Grammar; +import org.eclipse.xtext.RuleCall; import org.eclipse.xtext.XtextPackage; import org.eclipse.xtext.parsetree.reconstr.impl.CrossReferenceSerializer; -import org.eclipse.xtext.util.SimpleAttributeResolver; /** * @author Moritz Eysholdt - Initial contribution and API */ public class XtextCrossReferenceSerializer extends CrossReferenceSerializer { - private final SimpleAttributeResolver aliasResolver; - - public XtextCrossReferenceSerializer() { - super(); - aliasResolver = SimpleAttributeResolver.newResolver(String.class, "alias"); - } - @Override protected String getUnconvertedLinkText(EObject object, EReference reference, EObject context) { if (reference == XtextPackage.eINSTANCE.getGrammar_UsedGrammars()) return ((Grammar) object).getName(); - if (reference == XtextPackage.eINSTANCE.getTypeRef_Metamodel()) - return aliasResolver.getValue(object); + if (reference == XtextPackage.eINSTANCE.getTypeRef_Metamodel()) { + AbstractMetamodelDeclaration casted = (AbstractMetamodelDeclaration) object; + return casted.getAlias(); + } if (reference == XtextPackage.eINSTANCE.getAbstractMetamodelDeclaration_EPackage()) return ((EPackage) object).getNsURI(); - if (object instanceof AbstractRule) + if (object instanceof AbstractRule) { + if (reference == XtextPackage.eINSTANCE.getRuleCall_Rule()) { + if (((RuleCall)context).isExplicitlyCalled()) { + return super.getUnconvertedLinkText(object, reference, context); + } + } return ((AbstractRule) object).getName(); + } return super.getUnconvertedLinkText(object, reference, context); } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextFormatter.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextFormatter.java index f8ad0e3ec..153872246 100755 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextFormatter.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextFormatter.java @@ -18,8 +18,11 @@ import org.eclipse.xtext.services.XtextGrammarAccess.CharacterRangeElements; import org.eclipse.xtext.services.XtextGrammarAccess.CrossReferenceElements; import org.eclipse.xtext.services.XtextGrammarAccess.EnumLiteralDeclarationElements; import org.eclipse.xtext.services.XtextGrammarAccess.GrammarElements; +import org.eclipse.xtext.services.XtextGrammarAccess.NamedArgumentElements; import org.eclipse.xtext.services.XtextGrammarAccess.NegatedTokenElements; +import org.eclipse.xtext.services.XtextGrammarAccess.NegationElements; import org.eclipse.xtext.services.XtextGrammarAccess.ParserRuleElements; +import org.eclipse.xtext.services.XtextGrammarAccess.RuleCallElements; import org.eclipse.xtext.services.XtextGrammarAccess.TerminalTokenElements; import org.eclipse.xtext.services.XtextGrammarAccess.TypeRefElements; import org.eclipse.xtext.services.XtextGrammarAccess.UnorderedGroupElements; @@ -47,6 +50,14 @@ public class XtextFormatter extends AbstractDeclarativeFormatter { cfg.setNoSpace().after(pair.getFirst()); cfg.setNoSpace().before(pair.getSecond()); } + for (Pair pair : g.findKeywordPairs("[", "]")) { + cfg.setNoSpace().after(pair.getFirst()); + cfg.setNoSpace().before(pair.getSecond()); + } + for (Pair pair : g.findKeywordPairs("<", ">")) { + cfg.setNoSpace().after(pair.getFirst()); + cfg.setNoSpace().before(pair.getSecond()); + } for (Keyword comma : g.findKeywords(",")) { cfg.setNoSpace().before(comma); } @@ -72,7 +83,7 @@ public class XtextFormatter extends AbstractDeclarativeFormatter { // ParserRule ParserRuleElements pr = g.getParserRuleAccess(); - cfg.setNoSpace().before(pr.getLeftParenthesisKeyword_2_1()); + cfg.setNoSpace().before(pr.getLeftParenthesisKeyword_1_1()); // TypeRef TypeRefElements typeRef = g.getTypeRefAccess(); @@ -101,8 +112,6 @@ public class XtextFormatter extends AbstractDeclarativeFormatter { // CrossReference CrossReferenceElements cr = g.getCrossReferenceAccess(); - cfg.setNoSpace().after(cr.getLeftSquareBracketKeyword_0()); - cfg.setNoSpace().before(cr.getRightSquareBracketKeyword_3()); cfg.setNoSpace().around(cr.getVerticalLineKeyword_2_0()); // TerminalToken @@ -124,6 +133,20 @@ public class XtextFormatter extends AbstractDeclarativeFormatter { // EnumLiteralDeclaration EnumLiteralDeclarationElements eld = g.getEnumLiteralDeclarationAccess(); cfg.setNoSpace().around(eld.getEqualsSignKeyword_1_0()); + + // GuardCondition + NegationElements na = g.getNegationAccess(); + cfg.setNoSpace().after(na.getExclamationMarkKeyword_1_1()); + + // RuleCall + RuleCallElements rca = g.getRuleCallAccess(); + cfg.setNoSpace().before(rca.getLessThanSignKeyword_1_0()); + + // NamedArgument + NamedArgumentElements naa = g.getNamedArgumentAccess(); + cfg.setNoSpace().around(naa.getCalledByNameAssignment_0_1()); + cfg.setNoSpace().around(naa.getValueAssignment_1()); + cfg.setNoSpace().around(naa.getParameterAssignment_0_0()); //saveDebugGraphvizDiagram("XtextFormatting.dot"); } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextLinker.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextLinker.java index 462e4f1ce..4f9fb39fa 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextLinker.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextLinker.java @@ -32,6 +32,7 @@ import org.eclipse.xtext.EnumRule; import org.eclipse.xtext.GeneratedMetamodel; import org.eclipse.xtext.Grammar; import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.NamedArgument; import org.eclipse.xtext.ParserRule; import org.eclipse.xtext.ReferencedMetamodel; import org.eclipse.xtext.RuleCall; @@ -40,10 +41,10 @@ import org.eclipse.xtext.XtextFactory; import org.eclipse.xtext.XtextPackage; import org.eclipse.xtext.diagnostics.AbstractDiagnosticProducerDecorator; import org.eclipse.xtext.diagnostics.DiagnosticMessage; -import org.eclipse.xtext.diagnostics.Severity; import org.eclipse.xtext.diagnostics.ExceptionDiagnostic; import org.eclipse.xtext.diagnostics.IDiagnosticConsumer; import org.eclipse.xtext.diagnostics.IDiagnosticProducer; +import org.eclipse.xtext.diagnostics.Severity; import org.eclipse.xtext.linking.impl.Linker; import org.eclipse.xtext.nodemodel.INode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; @@ -132,7 +133,8 @@ public class XtextLinker extends Linker { @Override protected boolean canSetDefaultValues(EReference ref) { - return super.canSetDefaultValues(ref) || ref == XtextPackage.Literals.CROSS_REFERENCE__TERMINAL; + return super.canSetDefaultValues(ref) + || ref == XtextPackage.Literals.CROSS_REFERENCE__TERMINAL; } @Override @@ -155,6 +157,20 @@ public class XtextLinker extends Linker { call.setRule(rule); ((CrossReference) obj).setTerminal(call); } + } else if (XtextPackage.eINSTANCE.getNamedArgument_Parameter() == ref) { + final NamedArgument argument = (NamedArgument) obj; + if (!argument.isCalledByName()) { + RuleCall ruleCall = EcoreUtil2.getContainerOfType(argument, RuleCall.class); + AbstractRule calledRule = ruleCall.getRule(); + if (calledRule instanceof ParserRule && !calledRule.eIsProxy()) { + ParserRule casted = (ParserRule) calledRule; + int idx = ruleCall.getArguments().indexOf(argument); + if (idx < casted.getParameters().size()) { + argument.setParameter(casted.getParameters().get(idx)); + return; + } + } + } } else { super.setDefaultValueImpl(obj, ref, producer); } @@ -393,7 +409,7 @@ public class XtextLinker extends Linker { } final List allRuleCalls = EcoreUtil2.getAllContentsOfType(grammar, RuleCall.class); for (RuleCall call : allRuleCalls) { - if (call.getRule() != null) { + if (call.getRule() != null && !call.isExplicitlyCalled()) { AbstractRule rule = rulePerName.get(call.getRule().getName()); if (rule != null) call.setRule(rule); @@ -426,6 +442,9 @@ public class XtextLinker extends Linker { if (node == null) obj.eUnset(ref); } + if (ref == XtextPackage.Literals.RULE_CALL__RULE) { + obj.eUnset(XtextPackage.Literals.RULE_CALL__EXPLICITLY_CALLED); + } } public void setPackageRemover(PackageRemover packageRemover) { diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextRuleInspector.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextRuleInspector.java index 6073fccda..4dc9f17cd 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextRuleInspector.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextRuleInspector.java @@ -23,7 +23,7 @@ import com.google.common.collect.Sets; /** * @author Sebastian Zarnekow - Initial contribution and API */ -public class XtextRuleInspector extends XtextSwitch { +public abstract class XtextRuleInspector extends XtextSwitch { private final ValidationMessageAcceptor acceptor; diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextScopeProvider.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextScopeProvider.java index ff4527c38..f2db3d3df 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextScopeProvider.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextScopeProvider.java @@ -10,7 +10,6 @@ package org.eclipse.xtext.xtext; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashSet; import java.util.List; import org.eclipse.emf.common.util.URI; @@ -27,9 +26,13 @@ import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.AbstractMetamodelDeclaration; +import org.eclipse.xtext.AbstractRule; +import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.EnumRule; import org.eclipse.xtext.Grammar; import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.ParserRule; +import org.eclipse.xtext.RuleCall; import org.eclipse.xtext.TypeRef; import org.eclipse.xtext.XtextPackage; import org.eclipse.xtext.naming.QualifiedName; @@ -38,6 +41,7 @@ import org.eclipse.xtext.resource.IEObjectDescription; import org.eclipse.xtext.resource.IResourceDescription; import org.eclipse.xtext.scoping.IGlobalScopeProvider; import org.eclipse.xtext.scoping.IScope; +import org.eclipse.xtext.scoping.Scopes; import org.eclipse.xtext.scoping.impl.AbstractScopeProvider; import org.eclipse.xtext.scoping.impl.SelectableBasedScope; import org.eclipse.xtext.scoping.impl.SimpleScope; @@ -45,6 +49,7 @@ import org.eclipse.xtext.scoping.impl.SimpleScope; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.google.inject.Inject; /** @@ -92,7 +97,28 @@ public class XtextScopeProvider extends AbstractScopeProvider { } }); } - return createScope(context.eResource(), reference.getEReferenceType()); + if(reference == XtextPackage.eINSTANCE.getRuleCall_Rule()) { + return createScope(context.eResource(), reference.getEReferenceType(), new SuperCallScope(context)); + } + if (reference == XtextPackage.eINSTANCE.getParameterReference_Parameter()) { + ParserRule rule = GrammarUtil.containingParserRule(context); + if (rule == null) { + return IScope.NULLSCOPE; + } + return Scopes.scopeFor(rule.getParameters()); + } + if (reference == XtextPackage.eINSTANCE.getNamedArgument_Parameter()) { + RuleCall ruleCall = EcoreUtil2.getContainerOfType(context, RuleCall.class); + if (ruleCall == null) { + return IScope.NULLSCOPE; + } + AbstractRule referencedRule = ruleCall.getRule(); + if (referencedRule instanceof ParserRule) { + return Scopes.scopeFor(((ParserRule) referencedRule).getParameters()); + } + return IScope.NULLSCOPE; + } + return createScope(context.eResource(), reference.getEReferenceType(), IScope.NULLSCOPE); } protected IScope createEnumLiteralsScope(EEnum eEnum) { @@ -123,17 +149,17 @@ public class XtextScopeProvider extends AbstractScopeProvider { return createClassifierScope(allClassifiers); } - protected IScope createScope(Resource resource, EClass type) { + protected IScope createScope(Resource resource, EClass type, IScope parent) { if (resource.getContents().size() < 1) throw new IllegalArgumentException("resource is not as expected: contents.size == " + resource.getContents().size() + " but expected: >= 1"); final EObject firstContent = resource.getContents().get(0); if (!(firstContent instanceof Grammar)) - return IScope.NULLSCOPE; - return createScope((Grammar) firstContent, type); + return parent; + return createScope((Grammar) firstContent, type, parent); } - protected IScope createScope(final Grammar grammar, EClass type) { + protected IScope createScope(final Grammar grammar, EClass type, IScope current) { if (EcorePackage.Literals.EPACKAGE == type) { return createEPackageScope(grammar); } else if (AbstractMetamodelDeclaration.class.isAssignableFrom(type.getInstanceClass())) { @@ -147,30 +173,22 @@ public class XtextScopeProvider extends AbstractScopeProvider { })); } final List allGrammars = getAllGrammars(grammar); - IScope current = IScope.NULLSCOPE; for (int i = allGrammars.size() - 1; i >= 0; i--) { - current = createScope(allGrammars.get(i), type, current); + current = doCreateScope(allGrammars.get(i), type, current); } return current; } - protected IScope createScope(final Grammar grammar, final EClass type, IScope parent) { + + protected IScope doCreateScope(final Grammar grammar, final EClass type, IScope parent) { final IResourceDescription resourceDescription = resourceDescriptionManager.getResourceDescription(grammar.eResource()); return SelectableBasedScope.createScope(parent, resourceDescription, type, false); } protected List getAllGrammars(Grammar grammar) { - Collection visitedGrammars = new LinkedHashSet(); - collectAllUsedGrammars(grammar, visitedGrammars); - return new ArrayList(visitedGrammars); - } - - protected void collectAllUsedGrammars(Grammar grammar, Collection visited) { - if (!visited.add(grammar)) - return; - for(Grammar usedGrammar: grammar.getUsedGrammars()) { - collectAllUsedGrammars(usedGrammar, visited); - } + List result = Lists.newArrayList(grammar); + result.addAll(GrammarUtil.allUsedGrammars(grammar)); + return result; } protected IScope createEPackageScope(final Grammar grammar, IScope parent) { diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextTransientValueService.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextTransientValueService.java index bcd5275ce..b793d6d57 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextTransientValueService.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextTransientValueService.java @@ -17,6 +17,7 @@ import org.eclipse.xtext.CrossReference; import org.eclipse.xtext.EnumLiteralDeclaration; import org.eclipse.xtext.EnumRule; import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.NamedArgument; import org.eclipse.xtext.ParserRule; import org.eclipse.xtext.RuleCall; import org.eclipse.xtext.TerminalRule; @@ -67,6 +68,12 @@ public class XtextTransientValueService extends DefaultTransientValueService { return decl.getEnumLiteral() != null && decl.getLiteral() != null && Strings.equal(decl.getLiteral().getValue(), decl.getEnumLiteral().getName()); } + else if (feature == XtextPackage.eINSTANCE.getRuleCall_ExplicitlyCalled()) { + return true; + } + else if (feature == XtextPackage.eINSTANCE.getNamedArgument_Parameter()) { + return !((NamedArgument)owner).isCalledByName(); + } return super.isTransient(owner, feature, index); } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextTransientValueService2.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextTransientValueService2.java index 0cd4d4d56..6c4e36b91 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextTransientValueService2.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextTransientValueService2.java @@ -19,6 +19,7 @@ import org.eclipse.xtext.CrossReference; import org.eclipse.xtext.EnumLiteralDeclaration; import org.eclipse.xtext.EnumRule; import org.eclipse.xtext.GrammarUtil; +import org.eclipse.xtext.NamedArgument; import org.eclipse.xtext.ParserRule; import org.eclipse.xtext.RuleCall; import org.eclipse.xtext.TerminalRule; @@ -80,6 +81,13 @@ public class XtextTransientValueService2 extends TransientValueService { return NO; } return YES; + } else if (feature == XtextPackage.eINSTANCE.getRuleCall_ExplicitlyCalled()) { + return YES; + } else if (feature == XtextPackage.eINSTANCE.getNamedArgument_Parameter()) { + if (((NamedArgument)owner).isCalledByName()) { + return NO; + } + return YES; } return super.isValueTransient(owner, feature); } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextValidator.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextValidator.java index 0e6d41667..10e356114 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextValidator.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextValidator.java @@ -53,6 +53,8 @@ import org.eclipse.xtext.Grammar; import org.eclipse.xtext.GrammarUtil; import org.eclipse.xtext.Group; import org.eclipse.xtext.Keyword; +import org.eclipse.xtext.NamedArgument; +import org.eclipse.xtext.Parameter; import org.eclipse.xtext.ParserRule; import org.eclipse.xtext.ReferencedMetamodel; import org.eclipse.xtext.RuleCall; @@ -78,7 +80,6 @@ import org.eclipse.xtext.util.XtextSwitch; import org.eclipse.xtext.validation.AbstractDeclarativeValidator; import org.eclipse.xtext.validation.AbstractValidationMessageAcceptor; import org.eclipse.xtext.validation.Check; -import org.eclipse.xtext.validation.CheckType; import org.eclipse.xtext.validation.ValidationMessageAcceptor; import org.eclipse.xtext.xtext.ecoreInference.SourceAdapter; @@ -109,8 +110,74 @@ public class XtextValidator extends AbstractDeclarativeValidator { protected List getEPackages() { return Collections.singletonList(XtextPackage.eINSTANCE); } + + @Check + public void checkOrderOfArguments(RuleCall call) { + AbstractRule rule = call.getRule(); + if (rule instanceof ParserRule) { + Set usedParameters = Sets.newHashSet(); + boolean hasError = false; + boolean hasPositionalArgument = false; + boolean hasNamedArgument = false; + for(NamedArgument argument: call.getArguments()) { + Parameter parameter = argument.getParameter(); + if (parameter == null || parameter.eIsProxy()) { + hasError = true; + } else if (!usedParameters.add(parameter)) { + hasError = true; + error("Duplicate value for parameter " + parameter.getName(), + argument, XtextPackage.Literals.NAMED_ARGUMENT__PARAMETER); + } + if (!argument.isCalledByName()) { + hasPositionalArgument = true; + } else { + hasNamedArgument = true; + } + } + if (hasError) { + return; + } + List parameters = ((ParserRule) rule).getParameters(); + if (!hasPositionalArgument) { + if (usedParameters.size() != parameters.size()) { + StringBuilder missing = new StringBuilder(); + int count = 0; + for(Parameter parameter: parameters) { + if (!usedParameters.contains(parameter)) { + if (count > 0) { + missing.append(", "); + } + missing.append(parameter.getName()); + count++; + } + } + if (count == 1) { + error("Missing argument for parameter " + missing, + call, XtextPackage.Literals.RULE_CALL__RULE); + } else { + error(count + " missing arguments for the following parameters: " + missing, + call, XtextPackage.Literals.RULE_CALL__RULE); + } + } + } else { + if (usedParameters.size() != parameters.size()) { + error(String.format("Expected %d arguments but got %d", parameters.size(), usedParameters.size()), + call, XtextPackage.Literals.RULE_CALL__RULE); + } else if (hasNamedArgument) { + for(int i = 0, max = Math.min(usedParameters.size(), parameters.size()); i < max; i++) { + NamedArgument argument = call.getArguments().get(i); + Parameter param = parameters.get(i); + if (argument.isCalledByName() && argument.getParameter() != param) { + error("Out of sequence named argument. Expected value for " + param.getName(), + argument, XtextPackage.Literals.NAMED_ARGUMENT__PARAMETER); + } + } + } + } + } + } - @Check(CheckType.FAST) + @Check public void checkGrammarUsesMaxOneOther(Grammar grammar) { if (grammar.getUsedGrammars().size() > 1) { for(int i = 1; i < grammar.getUsedGrammars().size(); i++) { @@ -122,7 +189,7 @@ public class XtextValidator extends AbstractDeclarativeValidator { } } - @Check(CheckType.FAST) + @Check public void checkGrammarRecursiveReference(Grammar grammar) { Set visitedGrammars = Sets.newHashSet(grammar); for (int i = 0; i < grammar.getUsedGrammars().size(); i++) { @@ -684,7 +751,7 @@ public class XtextValidator extends AbstractDeclarativeValidator { @Check public void checkUnassignedRuleCallAllowed(final RuleCall call) { if (call.getRule() != null && !call.getRule().eIsProxy() && GrammarUtil.containingAssignment(call) == null) { - AbstractRule container = EcoreUtil2.getContainerOfType(call, AbstractRule.class); + AbstractRule container = GrammarUtil.containingRule(call); if (call.getRule() instanceof ParserRule) { if (container instanceof TerminalRule) { getMessageAcceptor().acceptError( @@ -693,9 +760,12 @@ public class XtextValidator extends AbstractDeclarativeValidator { XtextPackage.Literals.RULE_CALL__RULE, ValidationMessageAcceptor.INSIGNIFICANT_INDEX, null); + } else { + ParserRule parserRule = (ParserRule) call.getRule(); + if (!GrammarUtil.isDatatypeRule(parserRule) && !parserRule.isFragment()) { + checkCurrentMustBeUnassigned(call); + } } - else if (!GrammarUtil.isDatatypeRule((ParserRule) call.getRule())) - checkCurrentMustBeUnassigned(call); } if (call.getRule() instanceof EnumRule) { if (container instanceof TerminalRule) { @@ -714,7 +784,7 @@ public class XtextValidator extends AbstractDeclarativeValidator { public void checkTerminalFragmentCalledFromTerminalRule(final RuleCall call) { if (call.getRule() != null && !call.getRule().eIsProxy()) { if (call.getRule() instanceof TerminalRule && ((TerminalRule) call.getRule()).isFragment()) { - AbstractRule container = EcoreUtil2.getContainerOfType(call, AbstractRule.class); + AbstractRule container = GrammarUtil.containingRule(call); if (!(container instanceof TerminalRule)) { getMessageAcceptor().acceptError( "Only terminal rules may use terminal fragments.", @@ -728,12 +798,11 @@ public class XtextValidator extends AbstractDeclarativeValidator { } private void checkCurrentMustBeUnassigned(final AbstractElement element) { - ParserRule rule = GrammarUtil.containingParserRule(element); + final ParserRule rule = GrammarUtil.containingParserRule(element); if (GrammarUtil.isDatatypeRule(rule)) return; - XtextSwitch visitor = new XtextSwitch() { - private boolean isNull = true; + private boolean isNull = !rule.isFragment(); @Override public Boolean caseAbstractElement(AbstractElement object) { @@ -781,7 +850,7 @@ public class XtextValidator extends AbstractDeclarativeValidator { public Boolean caseAction(Action object) { if (object == element) { if (!(isNull && !isMany(object))) { - error("An unassigned action is not allowed, when the 'current' was already created.", null); + error("An unassigned action is not allowed, when the 'current' was already created.", object, null); checkDone(); } } @@ -792,8 +861,13 @@ public class XtextValidator extends AbstractDeclarativeValidator { @Override public Boolean caseRuleCall(RuleCall object) { if (object == element) { + AbstractRule calledRule = object.getRule(); + if (calledRule instanceof ParserRule && ((ParserRule) calledRule).isFragment()) { + isNull = false; + return isNull; + } if (!(isNull && !isMany(object))) { - error("An unassigned rule call is not allowed, when the 'current' was already created.", null); + error("An unassigned rule call is not allowed, when the 'current' was already created.", object, null); checkDone(); } } @@ -826,6 +900,9 @@ public class XtextValidator extends AbstractDeclarativeValidator { public void checkAssignedActionAfterAssignment(final Action action) { if (action.getFeature() != null) { ParserRule rule = GrammarUtil.containingParserRule(action); + if (rule.isFragment() && !rule.isWildcard()) { + return; + } XtextSwitch visitor = new XtextSwitch() { private boolean assignedActionAllowed = false; @@ -880,7 +957,7 @@ public class XtextValidator extends AbstractDeclarativeValidator { public Boolean caseAction(Action object) { if (object == action) { if (!assignedActionAllowed) { - error("An action is not allowed, when the current may still be unassigned.", null); + error("An action is not allowed in wildcard fragments and when the current may still be unassigned.", null); checkDone(); } } @@ -899,7 +976,7 @@ public class XtextValidator extends AbstractDeclarativeValidator { @Override public Boolean caseParserRule(ParserRule object) { - assignedActionAllowed = !GrammarUtil.isDatatypeRule(object); + assignedActionAllowed = !GrammarUtil.isDatatypeRule(object) && !(object.isFragment() && object.isWildcard()); return assignedActionAllowed; } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextValueConverters.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextValueConverters.java index b457ba36e..8ede7c779 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextValueConverters.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/XtextValueConverters.java @@ -13,14 +13,28 @@ import org.eclipse.xtext.conversion.IValueConverter; import org.eclipse.xtext.conversion.ValueConverter; import org.eclipse.xtext.conversion.ValueConverterException; import org.eclipse.xtext.conversion.impl.AbstractNullSafeConverter; +import org.eclipse.xtext.conversion.impl.KeywordAlternativeConverter; import org.eclipse.xtext.nodemodel.ILeafNode; import org.eclipse.xtext.nodemodel.INode; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + /** * @author Sebastian Zarnekow - Initial contribution and API */ +@Singleton public class XtextValueConverters extends DefaultTerminalConverters { + @Inject + private Provider validIDConverter; + + @ValueConverter(rule = "ValidID") + public IValueConverter ValidID() { + return validIDConverter.get(); + } + @ValueConverter(rule = "GrammarID") public IValueConverter GrammarID() { return new AbstractNullSafeConverter() { @@ -51,5 +65,51 @@ public class XtextValueConverters extends DefaultTerminalConverters { } }; } + + @ValueConverter(rule = "RuleID") + public IValueConverter RuleID() { + return new AbstractNullSafeConverter() { + @Override + protected String internalToValue(String string, INode node) throws ValueConverterException { + StringBuilder result = new StringBuilder(); + for(ILeafNode leaf: node.getLeafNodes()) { + if (!leaf.isHidden()) { + if (leaf.getGrammarElement() instanceof Keyword) + result.append("."); + else + result.append(ID().toValue(leaf.getText(), leaf)); + } + } + return result.toString(); + } + + @Override + protected String internalToString(String value) { + String[] splitted = value.split("\\."); + StringBuilder result = new StringBuilder(value.length()); + for(int i = 0; i < splitted.length; i++) { + if (i != 0) + result.append("::"); + result.append(ID().toString(splitted[i])); + } + return result.toString(); + } + }; + } + + @ValueConverter(rule = "LiteralValue") + public IValueConverter LiteralValue() { + return new AbstractNullSafeConverter() { + @Override + protected Boolean internalToValue(String string, INode node) throws ValueConverterException { + return "+".equals(string); + } + + @Override + protected String internalToString(Boolean value) { + return value.booleanValue() ? "+" : "!"; + } + }; + } } diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/TransformationErrorCode.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/TransformationErrorCode.java index c4e9c0d81..6b2d71af8 100644 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/TransformationErrorCode.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/TransformationErrorCode.java @@ -21,7 +21,9 @@ public enum TransformationErrorCode { UnknownMetaModelAlias, InvalidDatatypeRule, InvalidSupertype, - InvalidFeature; + InvalidFeature, + InvalidFragmentOverride, + InvalidRuleOverride; public String getFullyQualifiedCode() { return getClass().getCanonicalName() + "." + name(); diff --git a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/Xtext2EcoreTransformer.java b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/Xtext2EcoreTransformer.java index d937c9f8a..fa23ba906 100755 --- a/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/Xtext2EcoreTransformer.java +++ b/plugins/org.eclipse.xtext/src/org/eclipse/xtext/xtext/ecoreInference/Xtext2EcoreTransformer.java @@ -19,7 +19,6 @@ import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; -import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; @@ -204,31 +203,33 @@ public class Xtext2EcoreTransformer { for (AbstractRule rule : grammar.getRules()) { try { EClassifierInfo generatedEClass = findOrCreateEClassifierInfo(rule); - if (rule instanceof ParserRule) { - ParserRule parserRule = (ParserRule) rule; - if (parserRule.getAlternatives() != null) { - if (!GrammarUtil.isDatatypeRule(parserRule)) { - deriveTypesAndHierarchy(parserRule, generatedEClass, parserRule.getAlternatives()); - } else { + if (generatedEClass != null || !isWildcardFragment(rule)) { + if (rule instanceof ParserRule) { + ParserRule parserRule = (ParserRule) rule; + if (parserRule.getAlternatives() != null) { + if (!GrammarUtil.isDatatypeRule(parserRule)) { + deriveTypesAndHierarchy(parserRule, generatedEClass, parserRule.getAlternatives()); + } else { + checkSupertypeOfOverriddenDatatypeRule(rule); + } + } + } else if (rule instanceof TerminalRule) { + if (rule.getType() != null) { + if (!(rule.getType().getClassifier() instanceof EDataType)) + throw new TransformationException(TransformationErrorCode.NoSuchTypeAvailable, + "Return type of a terminal rule must be an EDataType.", rule.getType()); checkSupertypeOfOverriddenDatatypeRule(rule); } + } else if (rule instanceof EnumRule) { + if (rule.getType() != null) { + if (!(rule.getType().getClassifier() instanceof EEnum)) + throw new TransformationException(TransformationErrorCode.NoSuchTypeAvailable, + "Return type of an enum rule must be an EEnum.", rule.getType()); + checkSupertypeOfOverriddenDatatypeRule(rule); + } + } else { + throw new IllegalStateException("Unknown rule type: " + rule.eClass().getName()); } - } else if (rule instanceof TerminalRule) { - if (rule.getType() != null) { - if (!(rule.getType().getClassifier() instanceof EDataType)) - throw new TransformationException(TransformationErrorCode.NoSuchTypeAvailable, - "Return type of a terminal rule must be an EDataType.", rule.getType()); - checkSupertypeOfOverriddenDatatypeRule(rule); - } - } else if (rule instanceof EnumRule) { - if (rule.getType() != null) { - if (!(rule.getType().getClassifier() instanceof EEnum)) - throw new TransformationException(TransformationErrorCode.NoSuchTypeAvailable, - "Return type of an enum rule must be an EEnum.", rule.getType()); - checkSupertypeOfOverriddenDatatypeRule(rule); - } - } else { - throw new IllegalStateException("Unknown rule type: " + rule.eClass().getName()); } } catch (TransformationException e) { @@ -320,7 +321,7 @@ public class Xtext2EcoreTransformer { boolean result = true; for (AbstractRule rule : grammar.getRules()) { try { - if (rule instanceof ParserRule && !GrammarUtil.isDatatypeRule((ParserRule) rule)) { + if (rule instanceof ParserRule && !GrammarUtil.isDatatypeRule((ParserRule) rule) && !isWildcardFragment(rule)) { deriveFeatures((ParserRule) rule); } else if (rule instanceof EnumRule) { deriveEnums((EnumRule) rule); @@ -420,11 +421,15 @@ public class Xtext2EcoreTransformer { @Override public Xtext2EcoreInterpretationContext caseGroup(Group object) { - Xtext2EcoreInterpretationContext result = deriveFeatures(context.spawnContextForGroup(), object.getElements()); - if (GrammarUtil.isMultipleCardinality(object)) { - result = deriveFeatures(result.spawnContextForGroup(), object.getElements()); + return visitElements(object, object.getElements()); + } + + private Xtext2EcoreInterpretationContext visitElements(AbstractElement caller, List elementsToProcess) { + Xtext2EcoreInterpretationContext result = deriveFeatures(context.spawnContextForGroup(), elementsToProcess); + if (GrammarUtil.isMultipleCardinality(caller)) { + result = deriveFeatures(result.spawnContextForGroup(), elementsToProcess); } - if (GrammarUtil.isOptionalCardinality(object)) { + if (GrammarUtil.isOptionalCardinality(caller)) { result = result.mergeSpawnedContexts(Arrays.asList(context, result)); } return result; @@ -450,8 +455,14 @@ public class Xtext2EcoreTransformer { @Override public Xtext2EcoreInterpretationContext caseRuleCall(RuleCall object) { + AbstractRule calledRule = object.getRule(); + if (isWildcardFragment(calledRule)) { + return visitElements(object, Collections.singletonList(calledRule.getAlternatives())); + } + if (isParserRuleFragment(calledRule)) { + return context; + } if (!GrammarUtil.isOptionalCardinality(object)) { - AbstractRule calledRule = object.getRule(); // do not throw an exception for missing rules, these have been // announced during the first iteration if (calledRule != null && calledRule instanceof ParserRule && !GrammarUtil.isDatatypeRule((ParserRule) calledRule)) { @@ -495,7 +506,7 @@ public class Xtext2EcoreTransformer { } private Xtext2EcoreInterpretationContext deriveFeatures(Xtext2EcoreInterpretationContext context, - EList elements) { + List elements) { Xtext2EcoreInterpretationContext result = context; for (AbstractElement element : elements) { result = deriveFeatures(result, element); @@ -605,19 +616,14 @@ public class Xtext2EcoreTransformer { final TypeHierarchyHelper helper = new TypeHierarchyHelper(grammar, this.eClassifierInfos, this.errorAcceptor); helper.liftUpFeaturesRecursively(); helper.removeDuplicateDerivedFeatures(); -// helper.detectEClassesWithCyclesInTypeHierachy(); - - // duplicated features can occur in rare cases when alternatives produce - // different types of a feature - // If the internal structure (Set) of the underlying algorithm - // produces the features for the subtype first the implementation of EClassInfo - // wont find a conflict -// helper.detectDuplicatedFeatures(); } private void deriveTypesAndHierarchy(final ParserRule rule, final EClassifierInfo ruleReturnType, AbstractElement element) throws TransformationException { TransformationException ex = new XtextSwitch() { + + Set visiting = Sets.newHashSet(); + @Override public TransformationException caseAction(Action action) { final TypeRef actionTypeRef = action.getType(); @@ -633,10 +639,8 @@ public class Xtext2EcoreTransformer { @Override public TransformationException caseCompoundElement(CompoundElement object) { for (AbstractElement ele : object.getElements()) { - try { - deriveTypesAndHierarchy(rule, ruleReturnType, ele); - } - catch (TransformationException ex) { + TransformationException ex = doSwitch(ele); + if (ex != null) { return ex; } } @@ -656,21 +660,40 @@ public class Xtext2EcoreTransformer { "Cannot find called rule.", ruleCall); } if (calledRule instanceof TerminalRule || - calledRule instanceof ParserRule && (GrammarUtil.isDatatypeRule((ParserRule) calledRule))) + calledRule instanceof ParserRule && (GrammarUtil.isDatatypeRule((ParserRule) calledRule)) + || isWildcardFragment(calledRule)) return null; if (calledRule instanceof EnumRule) { return new TransformationException(TransformationErrorCode.NoSuchRuleAvailable, "Cannot call enum rule without assignment.", ruleCall); } - final TypeRef calledRuleReturnTypeRef = getOrComputeReturnType(calledRule); try { - addSuperType(rule, calledRuleReturnTypeRef, ruleReturnType); - } - catch (TransformationException ex) { + if (isParserRuleFragment(calledRule)) { + TypeRef subTypeRef = getOrComputeReturnType(rule); + addSuperType(rule, subTypeRef, findOrCreateEClassifierInfo(calledRule)); + if (visiting.add(calledRule)) { + try { + AbstractElement fragment = calledRule.getAlternatives(); + doSwitch(fragment); + } finally { + visiting.remove(calledRule); + } + } + } else { + final TypeRef calledRuleReturnTypeRef = getOrComputeReturnType(calledRule); + addSuperType(rule, calledRuleReturnTypeRef, ruleReturnType); + } + } catch (TransformationException ex) { return ex; } return null; } + + @Override + public TransformationException defaultCase(EObject object) { + // ignore + return null; + } }.doSwitch(element); if (ex != null) @@ -684,20 +707,80 @@ public class Xtext2EcoreTransformer { private boolean deriveTypeHierarchyFromOverridden(ParserRule rule, Grammar grammar) throws TransformationException { AbstractRule parentRule = GrammarUtil.findRuleForName(grammar, rule.getName()); - if (parentRule != null && parentRule.getType() != null && parentRule != rule) { - if (parentRule.getType().getClassifier() instanceof EDataType) - throw new TransformationException(TransformationErrorCode.InvalidSupertype, - "Cannot inherit from datatype rule and return another type.", rule.getType()); - EClassifierInfo parentTypeInfo = eClassifierInfos.getInfoOrNull(parentRule.getType()); - if (parentTypeInfo == null) - throw new TransformationException(TransformationErrorCode.InvalidSupertype, - "Cannot determine return type of overridden rule.", rule.getType()); - addSuperType(rule, rule.getType(), parentTypeInfo); - return true; + if (parentRule != null) { + if (parentRule != rule && parentRule instanceof ParserRule) { + ParserRule casted = (ParserRule) parentRule; + if (casted.isFragment() != rule.isFragment()) { + if (rule.isFragment()) { + throw new TransformationException(TransformationErrorCode.InvalidFragmentOverride, + "A fragment rule cannot override a production rule.", rule); + } else { + throw new TransformationException(TransformationErrorCode.InvalidFragmentOverride, + "Only fragment rule can override other fragment rules.", rule); + } + } + if (casted.isWildcard() != rule.isWildcard()) { + if (rule.isWildcard()) { + throw new TransformationException(TransformationErrorCode.InvalidFragmentOverride, + "A wildcard fragment rule cannot override a typed fragment rule.", rule); + } else { + throw new TransformationException(TransformationErrorCode.InvalidFragmentOverride, + "Only wildcard fragment rules can override other wildcard fragments.", rule); + } + } + if (rule.isFragment() && !rule.isWildcard() && parentRule.getType() != null) { + if (rule.getType().getClassifier() != parentRule.getType().getClassifier()) { + throw new TransformationException(TransformationErrorCode.InvalidFragmentOverride, + "Overriding fragment rules cannot redeclare their type.", rule.getType()); + } + } + checkParameterLists(rule, casted); + } + if (parentRule.getType() != null && parentRule != rule) { + if (parentRule.getType().getClassifier() instanceof EDataType) + throw new TransformationException(TransformationErrorCode.InvalidSupertype, + "Cannot inherit from datatype rule and return another type.", rule.getType()); + EClassifierInfo parentTypeInfo = eClassifierInfos.getInfoOrNull(parentRule.getType()); + if (parentTypeInfo == null) + throw new TransformationException(TransformationErrorCode.InvalidSupertype, + "Cannot determine return type of overridden rule.", rule.getType()); + addSuperType(rule, rule.getType(), parentTypeInfo); + return true; + } } return false; } + private void checkParameterLists(ParserRule rule, ParserRule overridden) throws TransformationException { + int inherited = overridden.getParameters().size(); + if (inherited == rule.getParameters().size()) { + boolean ok = true; + for(int i = 0; ok && i < inherited; i++) { + if (!Strings.equal(rule.getParameters().get(i).getName(), overridden.getParameters().get(i).getName())) { + ok = false; + } + } + if (ok) { + return; + } + } + if (inherited == 0) { + throw new TransformationException(TransformationErrorCode.InvalidRuleOverride, + "Overridden rule " + rule.getName() + " does not declare any parameters", rule); + } + StringBuilder message = new StringBuilder("Parameter list is incompatible with inherited "); + message.append(rule.getName()).append("["); + for(int i = 0; i < overridden.getParameters().size(); i++) { + if (i != 0) { + message.append(", "); + } + message.append(overridden.getParameters().get(i).getName()); + } + message.append("]"); + throw new TransformationException(TransformationErrorCode.InvalidRuleOverride, + message.toString(), rule); + } + private void addSuperType(ParserRule rule, TypeRef subTypeRef, EClassifierInfo superTypeInfo) throws TransformationException { final EClassifier subType = subTypeRef.getClassifier(); final EClassifierInfo subTypeInfo = subType == null @@ -841,14 +924,34 @@ public class Xtext2EcoreTransformer { } private EClassifierInfo findOrCreateEClassifierInfo(AbstractRule rule) throws TransformationException { + if (isWildcardFragment(rule)) { + return null; + } final TypeRef typeRef = getOrComputeReturnType(rule); - if (typeRef == null) + if (typeRef == null) { throw new TransformationException(TransformationErrorCode.NoSuchTypeAvailable, "Cannot create type for unnamed rule.", rule); + } if (typeRef.getMetamodel() != null && typeRef.getMetamodel().getEPackage() == null) throw new TransformationException(TransformationErrorCode.UnknownMetaModelAlias, "Cannot create type without declared package.", typeRef); return findOrCreateEClassifierInfo(typeRef, rule.getName(), grammar.getRules().contains(rule)); } + private boolean isWildcardFragment(AbstractRule rule) { + if (rule instanceof ParserRule) { + ParserRule casted = (ParserRule) rule; + return casted.isFragment() && casted.isWildcard(); + } + return false; + } + + private boolean isParserRuleFragment(AbstractRule rule) { + if (rule instanceof ParserRule) { + ParserRule casted = (ParserRule) rule; + return casted.isFragment() && !casted.isWildcard(); + } + return false; + } + private EClassifierInfo findEClassifierInfo(AbstractRule rule) { final TypeRef typeRef = getOrComputeReturnType(rule); if (typeRef == null) @@ -884,8 +987,7 @@ public class Xtext2EcoreTransformer { + typeRef.getClassifier().getName()); // + GrammarUtil.getQualifiedName(typeRef)); - String classifierName = null; - classifierName = GrammarUtil.getTypeRefName(typeRef); + String classifierName = GrammarUtil.getTypeRefName(typeRef); if (classifierName == null) classifierName = name; if (classifierName == null)