Question

I would like to create an incredibly simple DSL using xtext, with the following features:

  • It will have two primitive types: Number and String
  • Users can define their own classes, which are composed of field declarations
  • A field declaration associates a name with a type, where a type can be a class or a primitive

The following is my attempt at a DSL, the class definition and references work fine, but I cannot figure out how to have primitive types. The 'String' and 'Number' literals are not working:

Model:
    (classes+=Class)*
    (fields+=Field)*;

FieldType: Class | 'String' | 'Number';

Field:
    type=[FieldType] name=ID ";";

Class:
    "class" name=ID
    "{"
        (fields+=Field)*
    "}";

Here is an example I expect to be valid against the above DSL:

Class SomeClass {

}

// This works!
SomeClass reference;

// This does not, doesn't recognise the "String" literal
String string;

Note that further down the line I will be supporting assignment. Thus my DSL will need to incorporate the concept of number / string literals, such that it supports Number someNumber = 123;

Was it helpful?

Solution

Having slept on it, I think the right answer is to change my approach. In the above definition we have

Field:
    type=[FieldType] name=ID ";";

This defines a rule called "Field" which is made up of two parts; the "type" and the "name". It is the type part that is presenting the issue. The square brackets denote we are expecting an instance of FieldType, which is:

FieldType: Class | 'String' | 'Number';

Now, it's clear to see that you can have an instance of class, but semantically there's no way to have an instance of the 'String' or 'Number' Literals.

I believe this to be why my DSL above won't let me declare the primitives. The String/Number "types" simply aren't elements that you can have an instance of.


Thinking further, there are some very important distinctions between the definitions of fields that are primitive vs. those which are instances of a class. For example, you can only invoke methods on instances of a class (in my case I am treating String as a true primitive, so no methods).

Thus, it's probably important to have two different type of declarations, one for PrimitiveField and one for ObjectField. A field can be either of these:

Model:
    (classes+=Class)*
    (fields+=Field)*;

PrimitiveType: 'String' | 'Number' | 'Boolean';

Field:
    PrimitiveField | ObjectField
;

PrimitiveField:
    type=PrimitiveType name=ID ";"
;
ObjectField:
    type=[Class] name=ID ";";

Class:
    "class" name=ID
    "{"
    (fields+=Field)*
    (methods+=Method)*
    "}";
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top