textx

How to use a link rule reference in an ordered choice expression in a TextX grammar?


I am new to TextX. I am trying to create a grammar for defining data types that have fields that could be of a simple type or of the type of another data type. The grammar description is:

Library: 
    data_types *= DataType
;

DataType: name=ID "{" 
fields*=Field
"}" ;

Field: type=([DataType] | ID) name=ID;
//Type: [DataType] | ID;

An example of a model following this grammar would be

vec { 
int64 a
int64 b
int64 c
}

matrix {
    vec a
    vec b
}

I want to link the type of the field to either a data type that is already declared, or to some simple string. However, when compiling the above grammar with textx generate dummy.tx --target dot, I get the error Error: None:9:13: error: Expected ''((\\')|[^'])*'' or '"((\\")|[^"])*"' or re_match or rule_ref or '[' at position dummy.tx:(9, 13) => 'eld: type=*([DataType'..

Is there any way to accomplish what I want? I have tried putting the type declaration in a separate block, as seen in the comment, but that did not help. Any suggestion or hint would be highly appreciated.


Solution

  • A standard approach is to use custom classes and create builtins for all types that are not created by users themselves. It is best to show how it is done in code using your example. Note the use of registration so that the language with registered builtins can be available to textx CLI command. Also, see the entity example as the same techniques is used there.

    from textx import metamodel_from_str
    from textx.registration import (language, register_language,
                                    metamodel_for_language)
    
    # We use registration support to register language
    # This way it will be available to textx CLI command
    @language('library', '.lib')
    def library_lang():
        "Library language."
    
        grammar = r'''
        Library:
            data_types *= DataType
        ;
    
        DataType: name=ID "{"
            fields*=Field
        "}" ;
    
        Field: type=[Type] name=ID;
        Type: DataType | BuiltInType;
        BuiltInType: name=ID;
        '''
    
        # Here we use our class for builtin types so we can
        # make our own instances for builtin types.
        # See textX Entity example for more.
        class BuiltInType:
            def __init__(self, parent, name):
                self.parent = parent
                self.name = name
    
        # Create all builtin instances.
        builtins = {
            'int64': BuiltInType(None, 'int64'),
        }
    
        return metamodel_from_str(grammar,
                                  # Register custom classes and builtins.
                                  classes=[BuiltInType],
                                  builtins=builtins)
    
    
    # This should really be done through setup.{cfg,py}
    # Here it is done through registration API for an example to
    # be self-contained.
    register_language(library_lang)
    
    model_str = r'''
    vec {
    int64 a
    int64 b
    int64 c
    }
    
    matrix {
        vec a
        vec b
    }
    
    '''
    
    # Now we can get registered language metamodel by name and
    # parse our model.
    model = metamodel_for_language('library').model_from_str(model_str)
    # ... do something with the model
    assert len(model.data_types) == 2
    assert model.data_types[0].name == 'vec'
    assert model.data_types[0].fields[0].name == 'a'
    assert model.data_types[0].fields[0].type.name == 'int64'