[xtext 2.0] Create Scopes [message #699247] |
Thu, 21 July 2011 07:48 |
Daniel Missing name Messages: 101 Registered: July 2011 |
Senior Member |
|
|
Hi again.
I read some scoping posts and the documentation but I don't get it. I written my own expression language which has a sub-expression that allows me to reference any EObject. I have several places where I allow to put expressions like on Constant assignments or on array-size specifiers:
ReferenceExpression returns IExpression:
{ReferenceExpression}
reference=[ecore::EObject|QualifiedName]
;
Constant:
name = ID '=' assignment = Expression ';'
;
Type:
'type' name = ID
('extends' superType = [Type|QualifiedName])? '{'
elements += TypeMember*
'}'
;
TypeMember:
Constant | TypeDeclarationVariable
;
TypeDeclarationVariable:
dataType = VariableType name = ID';'
;
VariableType:
type = DataType ('[' size = Expression ']')?
;
Now I want to restrict those references depending on where the expression is placed. On this post Alexander tells it's better to use a combination of Java Validation and Content Assist to rebuild scoping (better error messages). But that would lead me to very ugly code (a lot of instanceof checks to check if all elements are references are allowed within the current scope) or I'd have to rebuild the complete scoping system only.
I tried to use the AbstractDeclarativeScopeProvider to implement scoping.
I want to get the following scopes:
Within an constant assignment expression, it's only allowed to reference other Constants no matter where they got declared (globally or within a type). (Constant Scope)
Within a array size expression (VariableType) it's allowed to reference any Constant and any other Variable type declared within the same type declaration. (Constant Scope + Local TypeDeclaration Scope)
public IScope scope_ReferenceExpression_reference(Constant constant, EReference reference) {
List<EClassifier> allConstants = new ArrayList<EClassifier>();
// what to place here?
return Scopes.scopeFor(allConstants);
}
public IScope scope_ReferenceExpression_reference(Type type, EReference reference) {
List<EClassifier> allConstantsAndLocalVariables = new ArrayList<EClassifier>();
// what to place here?
return Scopes.scopeFor(allConstantsAndLocalVariables);
}
In the documentation I found a description that exactly this behaviour can achieved using Scoping but it doesn't give any code samples how to implement it. I also tried to find algorithms within the source code but I couldn't find any place where it's described how to load specific elements from the global model.
Is there any class which allows me to query the current model like "give me all constants", "give me all variable declarations within this instance" without traversing manually trough the whole model?
Greetings Daniel
[Updated on: Thu, 21 July 2011 07:48] Report message to a moderator
|
|
|
|
|
Re: [xtext 2.0] Create Scopes [message #699296 is a reply to message #699251] |
Thu, 21 July 2011 09:48 |
Daniel Missing name Messages: 101 Registered: July 2011 |
Senior Member |
|
|
Hi.
Thanks for the fast answer. The EcoreUtil2 provides a lot of stuff I need. Only one more question.
Is there a possibility for filtering a given scope? I want to filter the default global scope for a EObject crossreference by specific element types. This would allow me to take the global scope containing all referenceable elements, and filter them to only allow other constants as a reference. I need such a scope as a parent scope therefore validation is no alternative.
[Edit]
I found out that I can receive the original IScope using delegateGetScope.
If I receive the "system" scope using delegateGetScope(constant, reference) this will return a specific scope implementation like ImportScope.
I tried to filter the elements using getAllElements() and repack them using Scopes.scopeFor but this will return me a SimpleScope which has a completely different behavior.
It's getting the same problem as asked here: http://www.eclipse.org/forums/index.php/m/697799/ but I can't solve this problem using validation as I need a restricted scope as a parent scope.
[Edit2]
I finally got it working. It seems the behavior of the IScope implementation doesn't matter. Here's the code how I filter the system scope to only provide constant declarations:
public IScope scope_ReferenceExpression_reference(Constant constant,
EReference reference) {
AbstractScope baseScope = (AbstractScope) delegateGetScope(constant,
reference);
Iterable<Constant> constants = Iterables.filter(baseScope.getAllElements(),
Constant.class);
return Scopes.scopeFor(constants, baseScope.getParent());
}
[Updated on: Thu, 21 July 2011 13:12] Report message to a moderator
|
|
|
|
|
|
|
|
|
|
Re: [xtext 2.0] Create Scopes [message #703111 is a reply to message #702593] |
Wed, 27 July 2011 07:26 |
Daniel Missing name Messages: 101 Registered: July 2011 |
Senior Member |
|
|
[EDIT2]
Now my code works so far. But there's still a problem. resourceDescriptions .getAllResourceDescriptions() returns all IResourceDescriptions within the workspace. How can I filter them only provide only the own and referenced projects?
@Inject
private IQualifiedNameProvider fqnProvider;
@Inject
private IResourceDescriptions resourceDescriptions;
private void checkForDuplicates(EObject object, EStructuralFeature reference) {
EObject parent = object.eContainer();
// only check root duplicates. if root duplicates are prevented
// nested declarations are impossible
if (parent == null || !(parent instanceof Model)) {
return;
}
QualifiedName fqn = fqnProvider.getFullyQualifiedName(object);
URI currentResourceUri = object.eResource().getURI();
boolean errorMarked = false;
for (IResourceDescription resource : resourceDescriptions
.getAllResourceDescriptions()) {
if (!resource.getURI().equals(currentResourceUri)) {
Iterable<IEObjectDescription> duplicates = resource.getExportedObjects(
MyDslPackage.eINSTANCE.getDeclaration(), fqn, false);
if (duplicates.iterator().hasNext()) {
if (!errorMarked) {
error(String.format("Duplicate Declaration '%s'", nameResolver.apply(object)),
reference);
errorMarked = true;
}
// TODO: Validation on other resource
}
}
}
}
Improvements: Only the current resource gets validated and only this resource shows the duplicate. It would be great if I can raise the validator of the other resources programatically to show the duplicates there too. The problem is:
- How can I raise the validation of a resource using IResourceDescription.getURI()
[Updated on: Wed, 27 July 2011 08:19] Report message to a moderator
|
|
|
Re: [xtext 2.0] Create Scopes [message #703307 is a reply to message #703111] |
Wed, 27 July 2011 12:22 |
Daniel Missing name Messages: 101 Registered: July 2011 |
Senior Member |
|
|
Finally it's done! After hours of work and annoying bugs is solved it using scopes within the JavaValidator. I load a scope for the current model which contains all EObjects. This model contains all elements of the current module inclusive possible duplicates. The problem is: Scopes are shadowing FQN of parent scopes. The solution: I walk up the scope hierarchy (getParent()) and store all URIs of any qualified name in a map. Afterwards I can access this map and check if a specific qualified name contains multiple elements.
Here my solution for everyone:
@Inject
private IResourceScopeCache cache = new IResourceScopeCache.NullImpl();
@Inject
private IQualifiedNameProvider fqnProvider;
@Inject
private IScopeProvider scopeProvider;
private void checkForDuplicates(EObject object, EStructuralFeature errorFeature) {
EObject parent = object.eContainer();
// only check root duplicates, if we prevent root duplicates
// nested type-duplicates are impossible
if (parent == null || !(parent instanceof Model)) {
return;
}
final Model model = (Model) parent;
// Build up a map which stores all URIs for a specific QualifiedName
// Store the map within a cache for large files
Map<QualifiedName, Set<URI>> duplicates = cache.get("duplicates", model.eResource(),
new Provider<Map<QualifiedName, Set<URI>>>() {
@Override
public Map<QualifiedName, Set<URI>> get() {
Map<QualifiedName, Set<URI>> duplicates = new HashMap<QualifiedName, Set<URI>>();
// get the scope for any EObject reference
// replace REFERENCE_EXPRESSION__REFERENCE with
// any EReference which allows all EObject references
// mine is: ReferenceExpression: reference = [ecore::EObject|FQN];
AbstractScope scope = (AbstractScope) scopeProvider.getScope(model,
MyDslPackage.Literals.REFERENCE_EXPRESSION__REFERENCE);
// walk up all parent scopes because scopes are shadowing
// duplicate QualifiedNames
while (scope != null) {
// receive all elements available in this scope
Iterable<IEObjectDescription> elements = scope.getAllElements();
for (IEObjectDescription element : elements) {
// Create a new HashSet for the QualifiedName if needed
QualifiedName qn = element.getName();
if(!duplicates.containsKey(qn)) {
duplicates.put(qn, new HashSet<URI>());
}
// Add the new ObjectURi to the list
duplicates.get(qn).add(element.getEObjectURI());
}
// Find the parent scope
if (scope.getParent() instanceof AbstractScope) {
scope = (AbstractScope) scope.getParent();
}
else {
scope = null;
}
}
return duplicates;
}
});
// Now we can simply access all URIs of a qualified name
final QualifiedName fqn = fqnProvider.getFullyQualifiedName(object);
if (duplicates.get(fqn).size() > 1) {
error(String.format("Duplicate symbol '%s'", nameResolver.apply(object)),
errorFeature);
}
}
// Usage:
@Check
public void checkDuplicateDeclarations(Constant constant) {
checkForDuplicates(constant, MyDslPackage.Literals.CONSTANT__NAME);
}
There are several improvements possible:
- You can modify the cache only to return your desired qualified name (replace scope.getAllElements())
- Create a project global cache which stores all registered types.
...
The main performance issue is: Scopes don't allow accessing the getLocalElements method. Therefore we need to check all "un-swallowed" elements multiple times. Would it be worth to create a bug in the bugtracker to make this element public. It's quite annoying that you only can access the elements of all scopes in the hierarchy. Not only the local scope elements.
Restrictions:
- This solution only works if there's one "namespace" declaration within the file. Multiple namespace blocks won't work.
- The scopeProvider don't allow receiving a scope based on a EClass. The EClass is received using the EReference. Therefore you need a Rule which has a CrossReference to an EObject.
[Updated on: Wed, 27 July 2011 12:30] Report message to a moderator
|
|
|
Powered by
FUDForum. Page generated in 0.06282 seconds