Xtext HOWTOs
Here, we collect short snippets to solve common problems.
Define a named object
Use the special attribute "name" in combination with default scope providers.
... MyObj: 'myobj' name=ID
Define an optional attribute
Use "(...)?". Note: * Some types, like INT or enums have default values (like 0 for INTs) and, thus, an unset value cannot be distinguished from, e.g., the value 0. * Using helper objects (like "Other" in the example below) helps to detect unset optional values, since the object is "null", if not set.
... MyObj: 'myobj' name=ID (option_int=INT)? (option_obj=Other)?; Other: 'other' val=INT;
Define a boolean flag based on the presence of a keyword
See Xtext documentation:
... MyObj: 'myobj' name=ID (option_flag?='flag_keyword')?
Define an enum
See Xtext docu. Note: The first enum value is the default value.
... MyObj: 'myobj' name=ID '=' value=MyEnum; enum MyEnum: val1='VAL1'|val2='VAL2'|val3='VAL3';
Force the creation of an object when no attributes are defined
Object without attributes are not instantiated as objects in the model representation. This can also happen, when all attributes are optional and not set in a concrete model (in this case you get a warning in your grammar). Use "{Rule-name}" to force instantiation.
MyObj: {MyObj} 'myobj' (value=MyVal)?; MyVal: text=STING;
Define an attribute containing a list
... Model: things+=Thing*; Thing: 'thing' name=ID;
Add one element to a list
... Model: things+=Thing; Thing: 'thing' name=ID;
Define a comma separated list
... Model: things+=Thing (',' things+=Thing)*; Thing: 'thing' name=ID;
Define an element to represent either a signed int or a float
Example grammar snippet defining a 'myobj' containing a name and either a float or an integer value:
import "http://www.eclipse.org/emf/2002/Ecore" as ecore ... MyObj: 'myobj' name=ID '=' (value=MyVal)?; MyVal: MyInt|MyFloat; MyInt: ivalue=MYINT_T; MyFloat: fvalue=MYFLOAT_T; terminal MYINT_T returns ecore::EInt: '-'?INT; terminal MYFLOAT_T returns ecore::EFloat: '-'?INT'.'INT;
With the test illustrating the types (int/float):
@Inject extension ParseHelper<Model> parseHelper @Inject extension ValidationTestHelper testHelper; ... @Test def void testFloatInt() { val result = ''' myobj pi=3.1415 myobj N=4 '''.parse result.assertNotNull result.assertNoErrors assertTrue( result.objs.head.value instanceof MyFloat ) val f = (result.objs.head.value as MyFloat).fvalue assertEquals( 3.1415, f, 1e-4 ) assertTrue( result.objs.last.value instanceof MyInt ) val i = (result.objs.last.value as MyInt).ivalue assertEquals( 4, i ) }
Allow keywords as name of elements
Problem: keywords (like 'myobj' or 'ref' in the example below) are not classified as "ID" by the lexer. Thus, they cannot be used as ID (the default lexer token for references).
... MyObj: 'myobj' name=ID Ref: 'ref' [MyObj]; // same as [MyObj|ID]
Solution: see https://blogs.itemis.com/en/xtext-hint-identifiers-conflicting-with-keywords for more details. Define a VALID_ID, including the desired keywords to be allowed as name:
... MyObj: 'myobj' name=ID Ref: 'ref' [MyObj|VALID_ID]; VALID_ID: ID|'ref'|'myobj'
Define a list of objects with the same base type
Example:
struct Simple { scalar x array y[10] scalar z }
With grammar:
... Struct: 'struct' name=ID '{' attrs+=Attribute+ '}'; Attribute: ScalarAttribute|ArrayAttribute; ScalarAttribute: 'scalar' name=ID; ArrayAttribute: 'array' name=ID '[' dim=INT ']';
Allow to model a mix of objects of unrelated type
Example (same as in last example):
struct Simple { scalar x array y[10] scalar z }
With grammar:
... Struct: 'struct' name=ID '{' (s+=ScalarAttribute | a+=ArrayAttribute)* '}' ; ScalarAttribute: 'scalar' name=ID; ArrayAttribute: 'array' name=ID '[' dim=INT ']';
Define multiline string attributes
You can use the "->" feature for terminals. You need to strip the leading and trailing '"""' when accessing the multiline string (e.g., with a xtend extension method).
Grammar snippet:
... MultlineInfo: 'info' text=MLTEXT; terminal MLTEXT: '"""' -> '"""';
Xtend code snippet and test:
static def getMltext(MultlineInfo info) { info.text.replaceAll('^"""','').replaceAll('"""$',''); } @Test def void testMlInfo() { val result = ''' info """Hello World""" info """Hello Multiline World""" '''.parse result.assertNotNull result.assertNoErrors assertEquals( '"""Hello World"""', result.infos.head.text ) assertEquals( "Hello World", result.infos.head.mltext ) assertEquals( "Hello Multiline\nWorld", result.infos.last.mltext ) }
Filter a list based on a type
resource.allContents.filter(Entity)
Filter a list based on an attribute value
resource.allContents.filter(Entity).filter[name="Tom"]
Transform a list
resource.allContents.filter(Entity).map[e|'the name is '+e.name]
Concatenate two lists
// list1 and list2 are of type java.util.List<...> (list1+list2).toList