Home > Xtext > Xtext and Dot/Path-Expressions

Xtext and Dot/Path-Expressions

If you have a DSL that describes structure (e.g. like an Entity DSL) you often need to “walk” on this structure using dot/path-expressions.

Let us asume we have a grammar like

Model:
	entities+=Entity*
;

Entity:
	"entity" name=ID "{"
		features+=Feature*
	"}"
;

Feature:
	Attribute | Reference
;

Attribute:
	"attr" name=ID ":" type=DataType
;

enum DataType:
	string | int
;

Reference:
	"ref" name=ID ":" type=[Entity]
;

and a model like

entity A {
	attr a1 : int
	attr a2 : string
	ref b : B
	ref c : C
}

entity B {
	attr b1 : string
	attr b2 : string
	ref a : A
	ref c : C
}

entity C {
	attr c1 : string
	attr c2 : int
}

and want to have expressions like

use A.b.b2
use A.b.c.c1
use A.a1
use A.b.a.a1

but how to do this with Xtext?
there are several possibility but the following was working well in my usecase:

Model:
	entities+=Entity*
	usages+=Usage*
;
Usage:
	"use" ref=DotExpression
;

DotExpression returns Ref:
	EntityRef ({DotExpression.ref=current}  "." tail=[Feature])*
;

EntityRef returns Ref:
	{EntityRef} entity=[Entity]
; 

a bit of scoping

class MyDslScopeProvider extends AbstractDeclarativeScopeProvider {

	def IScope scope_DotExpression_tail(DotExpression exp, EReference ref) {
		val head = exp.ref;
		switch (head) {
			EntityRef : Scopes::scopeFor(head.entity.features)
			DotExpression : {
				val tail = head.tail
				switch (tail) {
					Attribute : IScope::NULLSCOPE
					Reference : Scopes::scopeFor(tail.type.features)
					default: IScope::NULLSCOPE
				}
			}
			
			default: IScope::NULLSCOPE
		}
	}

}

and it works fine.

as an additional note: the ast of the expressions look like

Sample AST

Sample AST

About these ads
Categories: Xtext Tags: , ,
  1. Peter
    July 24, 2013 at 10:50

    This works great, but for example if I have in the Attribute a reference to e.g. [Entity2]. How do I correct write the Expression.

    • July 24, 2013 at 10:56

      Hi I do not understand this question. Could you please elaborate a bit more

  2. Peter
    July 25, 2013 at 05:22

    Sorry, there was an failure in the grammar, everything is ok now. Thank you for quick response

  3. David Callahan
    March 14, 2014 at 11:21

    I found this post super helpful. Thank you.

    How would you handle scoping for something that models class inheritance? For example here is a trivial grammar for classes with def’s and use’s.

    Model:
    classes+=Class*;
    Class: “class” name=ID (“extends” base=[Class])? “{” fields+=Field* “}” ;
    Field: Def | Use;
    Def: “def” name=ID;
    Use: “use” ref=[Def];

    And simple input

    class A {
    def A1
    def A2
    use A1
    }

    class B extends A{
    def B1
    use B1
    use A1 // should link to A.A1
    }

    How do you get the use of A1 to refer to the base class field A.A1?
    I tried this:

    def IScope scope_Class_fields(Class c, EReference ref) {
    println(“Class_fields ” + c.name)
    val m = c.fields + c?.base.fields
    return Scopes::scopeFor(m)
    }

    which I know won’t handle transitive cases but does not even handle this simple case.
    Thanks in advance

    • March 14, 2014 at 11:39

      Hi,

      have a look at the docs on how the naming for scoping methods work.

      the rest should be straight forward

      class MyDslScopeProvider extends org.eclipse.xtext.scoping.impl.AbstractDeclarativeScopeProvider {

      def IScope scope_Use_ref(org.xtext.example.mydsl.myDsl.Class ctx, EReference ref) {
      Scopes.scopeFor(ctx.fields.filter(Def), if (ctx.base == null) IScope.NULLSCOPE else scope_Use_ref(ctx.base, ref))
      }

      }

  4. David Callahan
    March 14, 2014 at 12:05

    Thank you for the quick reply and the pointer to the next step.
    I hope you appreciate how difficult it is to use reference documentation to *learn* a framework. I have found the scoping/linking aspects particularly challenging with few samples that illustrate how the API features work together. For example, while the notion of a scope tree is understood, its is not clear what the framework provides implicitly and where explicit acts are needed. So thanks again for this help.

    I did get the example question solved. However, if I extend my sample by allowing declarations outside of class:

    Model:
    definitions += Def*
    classes+=Class*;

    and have this input

    def D2
    class A {
    def A1
    def A2
    use A1
    use D2
    }

    class B extends A{
    def B1
    use B1
    use A1
    use D2
    }

    the in-class definitions all resolve but the “global” D2 does not. If I do nothing then of course the D2 reference works “out of the box”. This is where the implicit/explicit interactions are frustratingly opaque.

    • March 14, 2014 at 12:12

      Hi,
      it still is pretty the same – just a matter of traversing the model (i asume it still should be “one file only” – and inner defs overrule outer)
      Scopes.scopeFor(ctx.fields.filter(Def), if (ctx.base == null) Scopes.scopeFor(EcoreUtil2.getContainerOfType(ctx, Model).definitions) else scope_Use_ref(ctx.base, ref))

  5. David Callahan
    March 14, 2014 at 12:17

    Thanks again.
    How would I have discovered “EcoreUtil2.getContainerOfType” on my own?
    I am not trying to be snarky just trying to how best to navigate the framework.

    • March 14, 2014 at 12:23

      Hi, the Problem is: It is multiple Framework. The Metamodell is based on Ecore an knowing that leads to EcoreUtil … the rest is having a look at a lot of code :D
      feel free to file bugs against the docs at eclipse bugzilla

    • March 14, 2014 at 12:27

      If you have some time (and dollars) left and are willing to dive a bit deeper maybe the book “Implementing Domain-Specific Languages with Xtext and Xtend” by Lorenzo Bettini is a look worth.

  6. David Callahan
    March 14, 2014 at 12:52

    For the sake of those who follow:
    I expanded the example to include a min “expression” language so my grammar is now:
    Model:
    definitions += Def*
    classes+=Class*;
    Class: “class” name=ID (“extends” base=[Class])? “{” fields+=Field* “}” ;
    Field: Def | Use;
    Def: “def” name=ID;
    Use: “use” expr=Expr;
    Expr: Var | {BinOp} “(” left=Var “+” right=Expr “)”;
    Var: var=[Def];
    Then took the strategy that when I scope a variable (Var.var) is will manually walk up the AST to find the innermost scoping structure. The scope tree will need to be explicitly built there where the scope contains the definitions contained in that object plus a pointer to a parent scope. In this toy example, I know the containing scope will be a class so these functions suffice:

    def IScope scope_Var_var(Var v, EReference ref) {
    class_scope(find_scope(v) as Class)
    }
    def EObject find_scope(EObject obj) {
    if(obj instanceof Class) return obj
    val p = obj.eContainer
    if(p != null) return find_scope(p)
    obj
    }
    def IScope class_scope(Class c) {
    val parent = (if (c.base == null)
    Scopes.scopeFor(EcoreUtil2.getContainerOfType(c,Model).definitions)
    else
    class_scope(c.base)
    )
    Scopes.scopeFor(c.fields.filter(Def), parent)
    }

    Thanks for the help
    david

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: