TextX: Model Modularization
Similar to xtext_modularization.md, we show how to achive model modularization across files in TextX.
Modularization within the same Meta Model
Ready to use scope providers exists to handle multiple files. See the (TextX) documentation for details.
Referencing Model Elements form other Meta Models
If you wish to reference model elements from other metamodels, this can be achieved using the multi meta model support of TextX (see (TextX)).
The following example is self contained and shows how to deploy two python packages for two DSLs, one referencing the other.
- mydsl: a simple "Hello World" language
- mydsl1: a language referencing Greetings from the mydsl language.
mydsl - a simple model of "Greetings"
File layout
├── mydsl │ ├── __init__.py │ ├── metamodel.py │ └── MyDsl.tx └── setup.py
Grammar MyDsl.tx
The grammar defines the structure...
Model: greetings+=Greeting; Greeting: 'Hello' name=ID'!';
metamodel.py
The metamodel is created based on the grammar...
from textx import metamodel_from_file from os.path import dirname, abspath, join def get_metamodel(): this_folder = dirname(abspath(__file__)) meta_model = metamodel_from_file(join(this_folder,"MyDsl.tx")) return meta_model
__init__.py
The entry point for the DSL "compiler" (it just outputs some model data)...
import argparse from mydsl.metamodel import get_metamodel def mydslc(): parser = argparse.ArgumentParser(description='generate code for the model.') parser.add_argument('model_files', metavar='model_files', type=str, nargs='+', help='model filenames') args = parser.parse_args() mm = get_metamodel() for model_file in args.model_files: model = mm.model_from_file(model_file) for greeting in model.greetings: print(" - hello for '{}'".format(greeting.name))
setup.py
The installer configuration (see textx_project_setupx.md for more details).
... setup(name='mydsl', ... entry_points={ 'console_scripts': [ 'mydslc=mydsl:mydslc', ] }, ...
Create installer
Create an installer to help pip find its dependencies:
python setup.py sdist
mydsl1 - a model referencing the "Greetings" from mydsl
File layout
├── mydsl1 │ ├── __init__.py │ ├── metamodel.py │ └── MyDsl1.tx └── setup.py
Grammar MyDsl1.tx
Note: we use the grammar rule "Greeting" from "mydsl". See metamodel.py how this is resolved ("refrenced_metamodels").
Model: imports+=Import greetings+=RefGreeting; RefGreeting: 'Hello' '-->' ref=[Greeting]; Import: 'import' importURI=STRING;
metamodel.py
The metamodel is created based on the grammar, the metamodel to be referenced and some scope providers (to allow to "import" other model files)...
from textx import metamodel_from_file import textx.scoping as scoping import textx.scoping.providers as scoping_providers from os.path import dirname, abspath, join import mydsl def get_metamodel(): this_folder = dirname(abspath(__file__)) # get the "mydsl" meta model other_meta_model = mydsl.get_metamodel() # create the meta model and reference "mydsl" meta_model = metamodel_from_file( join(this_folder,"MyDsl1.tx"), referenced_metamodels=[other_meta_model]) # register scope provider (allow import models into mydsl1 models) meta_model.register_scope_providers( {"*.*": scoping_providers.PlainNameImportURI()}) # register file endings scoping.MetaModelProvider.add_metamodel("*.mydsl", other_meta_model) scoping.MetaModelProvider.add_metamodel("*.mydsl1", meta_model) return meta_model
__init__.py
The entry point for the DSL "compiler" (it just outputs some model data)...
import argparse from mydsl1.metamodel import get_metamodel def mydsl1c(): parser = argparse.ArgumentParser(description='generate code for the model.') parser.add_argument('model_files', metavar='model_files', type=str, nargs='+', help='model filenames') args = parser.parse_args() mm = get_metamodel() for model_file in args.model_files: model = mm.model_from_file(model_file) for greeting in model.greetings: print(" - hello for referenced '{}'".format(greeting.ref.name))
setup.py
The installer configuration (see textx_project_setupx.md for more details). The language "mydsl1" depends on "mydsl".
... setup(name='mydsl1', ... install_requires=["textx","arpeggio","mydsl"], ... entry_points={ 'console_scripts': [ 'mydsl1c=mydsl1:mydsl1c', ] }, ...
Usage
Install both DSLs and compilers
The option "find-links" is used to point to the local version of mydsl (created above; setup.py of "mydsl1" includes this dependency):
pip3 install . --find-links=file:///$(pwd)/../mydsl/dist
Model file data.mydsl
Hello Pi! Hello Tim!
Model file data.mydsl1
import "data.mydsl" Hello --> Pi Hello --> Tim
Model file error.mydsl1
import "data.mydsl" Hello --> NoName
Example using mydslc and mydsl1c
$ mydslc model/data.mydsl - hello for 'Pi' - hello for 'Tim' $ mydsl1c model/data.mydsl1 - hello for referenced 'Pi' - hello for referenced 'Tim' $ mydsl1c model/error.mydsl1 ... textx.exceptions.TextXSemanticError: model/error.mydsl1:2:11: error: Unknown object "NoName" of class "Greeting"