Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Modeling » TMF (Xtext) » [xtext 2.0] Create Scopes
[xtext 2.0] Create Scopes [message #699247] Thu, 21 July 2011 07:48 Go to next message
Daniel Missing name is currently offline Daniel Missing nameFriend
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 #699251 is a reply to message #699247] Thu, 21 July 2011 07:57 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 14665
Registered: July 2009
Senior Member
Hi,

use ecore (.eContainer()) or EcoreUtil2.getContainerOfTyoe to find the container (parent) of your context that is a Type, and the types children for the stuff you want to add to the list.

~Christian


Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
Re: [xtext 2.0] Create Scopes [message #699267 is a reply to message #699247] Thu, 21 July 2011 08:34 Go to previous messageGo to next message
Alexander Nittka is currently offline Alexander NittkaFriend
Messages: 1193
Registered: July 2009
Senior Member
Hi,

just as a side note on "validation/content assist vs. scoping". It is not a global recommendation to use validation/CA instead of scoping. Otherwise there would be no need for any scoping at all. The choice has to be made for every single use case.

Alex
Re: [xtext 2.0] Create Scopes [message #699296 is a reply to message #699251] Thu, 21 July 2011 09:48 Go to previous messageGo to next message
Daniel Missing name is currently offline Daniel Missing nameFriend
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 #702353 is a reply to message #699296] Tue, 26 July 2011 08:59 Go to previous messageGo to next message
Daniel Missing name is currently offline Daniel Missing nameFriend
Messages: 101
Registered: July 2011
Senior Member
A new problem according to the global scope part comes along: If I have two files declaring the same module (package/namespace) each file has it's own unique scope. Therefore duplicate elements get not recognized and the referencing requires a FQN.

base.dsl
module Base;

type BaseType {}


base2.dsl
module Base;
// wierd stuff is allowed:
type SubType extends Base.BaseType {} 


I want to achive that the declaration at the top determines the scope in which the elements get declared. The DefaultGlobalScopeProvider has a great behaviour which I want to keep (allow referenced projects for lookup). I'm not sure where to implement such a behavior. Do I need to create a custom IGlobalScope or a IContainer.Manager to ensure those two files will be in the same scope?

Re: [xtext 2.0] Create Scopes [message #702360 is a reply to message #702353] Tue, 26 July 2011 09:10 Go to previous messageGo to next message
Alexander Nittka is currently offline Alexander NittkaFriend
Messages: 1193
Registered: July 2009
Senior Member
Hi,

I find your concept contradictory. You first say that both files have their own unique scope and later that both files are to be in the same one. If you want to allow defining the same namespace over several files, everything should work out of the box (using the qualified names fragment and imported namespace fragment).

Module: 'module' name=ID ';' types+=Type;
Type: 'type' name=ID (extends=[Type|Fqn])? '{''}';
Fqn hidden(): ID ('.'ID)*;

However, you will run into problems if both modules define the same type (name identical).

Alex


Need training, onsite consulting or any other kind of help for Xtext?
Go visit http://xtext.itemis.com or send a mail to xtext@itemis.de
Re: [xtext 2.0] Create Scopes [message #702441 is a reply to message #702360] Tue, 26 July 2011 11:38 Go to previous messageGo to next message
Daniel Missing name is currently offline Daniel Missing nameFriend
Messages: 101
Registered: July 2011
Senior Member
Hi.

You missunderstood me: The current behavior IS that it seems each file has it's own scope because I need to full qualify the names in different files even if the module is declared as the same one. See the example. That's the default behavior I get.

The behavior I want is that two files are in the same scope if they have the same module declared. I want to prevent duplicate elements even if they are declared in different files. Furthermore it shouldn't be neccesary to write the full qualified name of a type if it's declared in an other file with the same module. Exactly like the Java Packages. Each file declares a package. All types within it are in this package. Duplicate classes within this package are errors. You can fully qualify classes within the same package, or use only the class name.

The following example should explain the current state:
Project: Base
src/base.dsl
module Base;
type BaseType {}

src/base2.dsl
module Base;
type NewBase {}
type BaseType extends NewBase {} // [1]
type Test1 extends Base.BaseType {} // [2]
type Test2 extends BaseType {} // [3]

Project: Other
(Has a BuildPath reference to Project Base)
src/other.dsl
 
module Other;
type BaseType {}
type Test3 extends Base.Test2 {} // [4]


[1] Overriding the BaseType in another file is currently possible. This shouldn't be possible because it's already declared in the module Base.
[2] Referencing using the FQN is possible, this is OK this way.
[3] Referencing using the relative name is currently not possible. It should be possible to reference BaseType without the module name since Test2 in the same module.
[4] Referencing a type in another project with the buildpath set is currently possible. That's perfect.

Re: [xtext 2.0] Create Scopes [message #702447 is a reply to message #702441] Tue, 26 July 2011 11:45 Go to previous messageGo to next message
Alexander Nittka is currently offline Alexander NittkaFriend
Messages: 1193
Registered: July 2009
Senior Member
Hi,

OK, what you want is an implicit import of the namespace defined in your model file (Xtext does not do that out of the box, elements from the outside world, i.e. another file, are visible via the index using their fully qualified names; if you want to reference them using shorter names, an import has to be defined in the current file).

Have a look at ImportedNamespaceAwareLocalScopeProvider#internalGetImportNormalizers and add a normaliser for the namespace you define in the file. That way elements from the index are visible via their simple name.

Alex


Need training, onsite consulting or any other kind of help for Xtext?
Go visit http://xtext.itemis.com or send a mail to xtext@itemis.de
Re: [xtext 2.0] Create Scopes [message #702512 is a reply to message #702447] Tue, 26 July 2011 13:24 Go to previous messageGo to next message
Daniel Missing name is currently offline Daniel Missing nameFriend
Messages: 101
Registered: July 2011
Senior Member
Hi.

I tought of this solution already but that sounds to me like a workaround. Since the two files already declare the same module they should already be in the same namespace and an import for the own namespace elements shouldn't be required.

I implemented this solution quickly to check it out. I can reference the other-file&same-module elements without specifying the module name. But it seems all files have their own scope for their module and don't share a global module scope resolved by the module name. This leads to the problem that I can define the same type within both files without an error of duplicate element:

base.dsl
module Base;
type BaseType {}

base2.dsl
module Base;
type BaseType {}


If I would have imports the result would look like this:
base.dsl
module Base;

import "base2.dsl"::Base;

type BaseType {}

base2.dsl
module Base;

import "base2.dsl"::Base;

type BaseType {}


This results in following existing types:

"base.dsl"::Base::BaseType
"base2.dsl"::Base::BaseType

Re: [xtext 2.0] Create Scopes [message #702530 is a reply to message #702512] Tue, 26 July 2011 13:51 Go to previous messageGo to next message
Alexander Nittka is currently offline Alexander NittkaFriend
Messages: 1193
Registered: July 2009
Senior Member
Hi,

to put it briefly, you have to implement "everything" yourself. There is no concept like "two EObjects from different resources have exactly the same scope".

Alex


Need training, onsite consulting or any other kind of help for Xtext?
Go visit http://xtext.itemis.com or send a mail to xtext@itemis.de
Re: [xtext 2.0] Create Scopes [message #702593 is a reply to message #702530] Tue, 26 July 2011 14:53 Go to previous messageGo to next message
Daniel Missing name is currently offline Daniel Missing nameFriend
Messages: 101
Registered: July 2011
Senior Member
Okay. therefore I will write some own checks if a FullQualifiedName is already reserved by a declaration in another file. Scoping is currently my main problem. I'm already stuck solving the next scoping problem. We'll see if I can fix it own my own.

Thanks for your patience. The support here is simply awesome. Smile

- Daniel
Re: [xtext 2.0] Create Scopes [message #703111 is a reply to message #702593] Wed, 27 July 2011 07:26 Go to previous messageGo to next message
Daniel Missing name is currently offline Daniel Missing nameFriend
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 Go to previous message
Daniel Missing name is currently offline Daniel Missing nameFriend
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

Previous Topic:Integrating DSL in TableViewer (XtextResource?)
Next Topic:?how to mix strategies of global scoping?
Goto Forum:
  


Current Time: Sat Apr 20 06:53:43 GMT 2024

Powered by FUDForum. Page generated in 0.04594 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software

Back to the top