Improve performance of Xtext editor with large grammars (#1562)

* Use EPackage.getEClassifier during grammar linking
* Avoid redundant validations of predicates that cover unordered groups
* Reuse previously collected information in the rule inspector for overridden values

closes #1561
This commit is contained in:
Sebastian Zarnekow 2020-09-15 11:17:46 +02:00 committed by GitHub
parent 8a6605a018
commit dc49298b09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 70 additions and 30 deletions

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2009 itemis AG (http://www.itemis.eu) and others.
* Copyright (c) 2009, 2020 itemis AG (http://www.itemis.eu) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
@ -10,6 +10,8 @@ package org.eclipse.xtext.xtext;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.xtext.AbstractElement;
@ -25,6 +27,7 @@ import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.TerminalRule;
import org.eclipse.xtext.validation.ValidationMessageAcceptor;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
@ -40,16 +43,19 @@ public class OverriddenValueInspector extends XtextRuleInspector<Boolean, Parser
private Multimap<String, AbstractElement> assignedFeatures;
/**
* Remember all visited unassigned rule calls in case the grammar is broken, e.g. has
* Remember all visited rules in case the grammar is broken, e.g. has
* no assignments (yet).
*/
private Set<AbstractRule> permanentlyVisited;
private Set<RuleCall> fragmentStack;
private Map<AbstractRule, ImmutableMultimap<String, AbstractElement>> assignedFeaturesAtEnd;
public OverriddenValueInspector(ValidationMessageAcceptor acceptor) {
super(acceptor);
assignedFeatures = newMultimap();
assignedFeaturesAtEnd = new HashMap<>();
permanentlyVisited = Sets.newHashSet();
fragmentStack = Sets.newHashSet();
}
@ -73,6 +79,10 @@ public class OverriddenValueInspector extends XtextRuleInspector<Boolean, Parser
@Override
protected Boolean doInspect(ParserRule rule) {
permanentlyVisited.clear();
fragmentStack.clear();
assignedFeatures.clear();
visitedRules.clear();
return doSwitch(rule.getAlternatives());
}
@ -160,9 +170,16 @@ public class OverriddenValueInspector extends XtextRuleInspector<Boolean, Parser
if (!addVisited(parserRule))
return Boolean.FALSE;
Multimap<String, AbstractElement> prevAssignedFeatures = assignedFeatures;
assignedFeatures = newMultimap();
doSwitch(parserRule.getAlternatives());
for (String feature : assignedFeatures.keySet())
// Cannot use #computeIfAbsent since we will call this recursively and that causes ConcurrentModificationExceptions
ImmutableMultimap<String, AbstractElement> assignedInCalledRule = assignedFeaturesAtEnd.get(parserRule);
if (assignedInCalledRule == null) {
assignedFeatures = newMultimap();
doSwitch(parserRule.getAlternatives());
assignedInCalledRule = ImmutableMultimap.copyOf(assignedFeatures);
assignedFeaturesAtEnd.put(parserRule, assignedInCalledRule);
}
for (String feature : assignedInCalledRule.keySet())
prevAssignedFeatures.put(feature, object);
assignedFeatures = prevAssignedFeatures;
removeVisited(parserRule);

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2011 itemis AG (http://www.itemis.eu) and others.
* Copyright (c) 2011, 2020 itemis AG (http://www.itemis.eu) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
@ -63,7 +63,7 @@ public class PredicateUsesUnorderedGroupInspector extends XtextSwitch<Boolean> i
validatedRules = Sets.newHashSet();
doSwitch(rule);
storedRules.addAll(validatedRules);
storedRules = validatedRules;
validatedRules = storedRules;
}
}
}

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2009 itemis AG (http://www.itemis.eu) and others.
* Copyright (c) 2009, 2020 itemis AG (http://www.itemis.eu) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
@ -28,7 +28,7 @@ public abstract class XtextRuleInspector<Result, RuleType extends AbstractRule>
private final ValidationMessageAcceptor acceptor;
private Collection<AbstractRule> visitedRules;
Collection<AbstractRule> visitedRules;
public XtextRuleInspector(ValidationMessageAcceptor acceptor) {
this.acceptor = acceptor;

View file

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2008 itemis AG (http://www.itemis.eu) and others.
* Copyright (c) 2008, 2020 itemis AG (http://www.itemis.eu) and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
@ -9,7 +9,6 @@
package org.eclipse.xtext.xtext;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@ -49,6 +48,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.FluentIterable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
@ -73,7 +73,7 @@ public class XtextScopeProvider extends AbstractScopeProvider {
if (metaModel != null) {
EPackage pack = metaModel.getEPackage();
if (pack != null)
return createClassifierScope(pack.getEClassifiers());
return createClassifierScope(Collections.singletonList(pack));
} else {
return createReferencedPackagesScope(GrammarUtil.getGrammar(context));
}
@ -123,31 +123,50 @@ public class XtextScopeProvider extends AbstractScopeProvider {
}
protected IScope createEnumLiteralsScope(EEnum eEnum) {
return new SimpleScope(IScope.NULLSCOPE,Iterables.transform(eEnum.getELiterals(), new Function<EEnumLiteral, IEObjectDescription>() {
@Override
public IEObjectDescription apply(EEnumLiteral param) {
return EObjectDescription.create(QualifiedName.create(param.getName()), param);
}
}));
return new SimpleScope(IScope.NULLSCOPE, Iterables.transform(eEnum.getELiterals(), new Function<EEnumLiteral, IEObjectDescription>() {
@Override
public IEObjectDescription apply(EEnumLiteral param) {
return EObjectDescription.create(QualifiedName.create(param.getName()), param);
}
}));
}
protected IScope createClassifierScope(Iterable<EClassifier> classifiers) {
protected IScope createClassifierScope(List<EPackage> packages) {
return new SimpleScope(
IScope.NULLSCOPE,Iterables.transform(classifiers, new Function<EClassifier, IEObjectDescription>() {
@Override
public IEObjectDescription apply(EClassifier param) {
return EObjectDescription.create(QualifiedName.create(param.getName()), param);
IScope.NULLSCOPE,
FluentIterable.from(packages)
.transformAndConcat(EPackage::getEClassifiers)
.transform(classifier->EObjectDescription.create(QualifiedName.create(classifier.getName()), classifier))) {
@Override
protected IEObjectDescription getSingleLocalElementByName(QualifiedName name) {
if (name.getSegmentCount() == 1) {
EClassifier candidate = null;
for(EPackage pack: packages) {
EClassifier classifier = pack.getEClassifier(name.getFirstSegment());
if (classifier != null) {
if (candidate != null) {
return null;
}
candidate = classifier;
}
}
}));
if (candidate != null) {
return EObjectDescription.create(name, candidate);
}
}
return null;
}
};
}
protected IScope createReferencedPackagesScope(Grammar g) {
final Collection<EClassifier> allClassifiers = new ArrayList<EClassifier>();
List<EPackage> allPackages = new ArrayList<EPackage>();
for(AbstractMetamodelDeclaration decl: g.getMetamodelDeclarations()) {
if (decl.getEPackage() != null)
allClassifiers.addAll(decl.getEPackage().getEClassifiers());
allPackages.add(decl.getEPackage());
}
return createClassifierScope(allClassifiers);
return createClassifierScope(allPackages);
}
protected IScope createScope(Resource resource, EClass type, IScope parent) {
@ -164,7 +183,7 @@ public class XtextScopeProvider extends AbstractScopeProvider {
if (EcorePackage.Literals.EPACKAGE == type) {
return createEPackageScope(grammar);
} else if (AbstractMetamodelDeclaration.class.isAssignableFrom(type.getInstanceClass())) {
return new SimpleScope(IScope.NULLSCOPE,Iterables.transform(grammar.getMetamodelDeclarations(),
return new SimpleScope(IScope.NULLSCOPE, Iterables.transform(grammar.getMetamodelDeclarations(),
new Function<AbstractMetamodelDeclaration,IEObjectDescription>(){
@Override
public IEObjectDescription apply(AbstractMetamodelDeclaration from) {

View file

@ -1062,9 +1062,13 @@ public class XtextValidator extends AbstractDeclarativeValidator {
}
@Check
public void checkForOverriddenValue(final ParserRule rule) {
public void checkForOverriddenValue(final Grammar grammar) {
OverriddenValueInspector inspector = new OverriddenValueInspector(this);
inspector.inspect(rule);
for(AbstractRule rule: grammar.getRules()) {
if (rule instanceof ParserRule) {
inspector.inspect((ParserRule) rule);
}
}
}
@Check