Generator

Generator User Guide

About Generator

Generator is a part of language specification that defines the (denotational) semantics for a language's concepts.

MPS follows the model-to-model transformation approach.
Generator specifies the translation of constructions in the input language to constructions in the output language.
The process of model-to-model transformation may involve many intermediate models and results in the output model where all constructions are in language whose semantics are already defined elsewhere.

For instance, most concepts in baseLanguage (classes, methods etc) are "machine understandable," wherefore baseLanguage is often used as an output language.

Target assets are created by applying model-to-text generation, and then file generation, to the output model.
MPS provides destructive update of generated assets only.

Model-to-text and files generation must be supported by the output language.
At the time of writing, the model-to-text language aspect is implemented as java classes and cannot be developed in MPS language framework as yet.
The File Generator aspect can be implemented as part of the language plug-in (see File generator plugins).

For instance, baseLanguage's model-to-text aspect generates a Java Code, and File Generator aspect generates *.java files at the following location:
<generator output path>\<model name>\<ClassName>.java
where:
Generator output path - is specified in the module which owns the input model (see MPS modules).
Model name - is a path segment created by replacing '.' with the file separator in the input model's name.

Generator Module

Unlike any other language aspect, the generator aspect is not a single model.
Generator specification can comprise many generator models as well as utility models.
Generator model contains templates, mapping configurations and other constructions of the generator language.

Generator model is distinguished from a regular model by the model stereotype - 'generator'
(shown after the model name as <name>@generator).

The screenshot below shows the generator module of the smodel language as an example.

research bundled languages yourself
You can research the smodel (and any other) language's generator by yourself:
  • download MPS (here);
  • create new project (can be empty project);
  • use the Go To->Language command in the main menu to navigate to the smodel language (its full name is jetbrains.mps.lang.smodel)

Creating a New Generator

New generator is created by using the New->New Generator command in the language's popup menu.

Technically, it is possible to create more than one generator for one language, but at the time of writing MPS does not provide full support for this feature.
Therefore languages normally have only one (if any) generator.
For that reason, the generator's name is not important. Everywhere in the MPS GUI a generator module can be identified by its language name.

When creating a new generator module, MPS will also create the generator model 'main@generator' containing an empty mapping configuration node.

Generator Properties

As a module, generator can depend on other modules, have used languages and used devkits (see Module meta-information).

The generator properties dialog also has two additional properties:

Generator Language

The transformation is described by means of templates.
Templates are written using output language and are edited using the same cell editor as any other 'regular code' in that language. Therefore, the 'template editor' right away has the same level of tooling support - syntax/error highlighting, auto-completion, etc.

Generator Rules

Applicability of each transformation is defined by generator rules.
There are five types of generator rules:

  • conditional root rule
  • root mapping rule
  • weaving rule
  • reduction rule
  • abandon root rule

Each generator rule consists of premise and consequence (except for the abandon root rule, whose consequence is predefined and is not specified by the user).

All rules except for the conditional root rule contain a reference on concept of input node (or just input concept) in its premises.
All rule premises also contain an optional condition function.

Rule consequence commonly contains a reference to an external template (i.e. a template declared as a root node in the same or different model) or so-called in-line template (conditional root rule and root mapping role can only have reference to an external template).
There are also several other versions of consequences.

The following screenshot shows the contents of a generator model and a mapping configuration example.

Macros

Template code can be parameterized by means of macros. The generator language defines three kinds of macros:

  • property macro - computes property value;
  • reference macro - computes the target (node) of a reference;
  • node macro - is used to control template filling at generation time. There are several versions of node macro - LOOP-macro is an example.

Macro implements a special kind of so-called annotation concept and can wrap property, reference or node cells (depending on the kind of macro) in a template code.

Code wrapping (i.e. the creation of a new macro) is done by pressing Ctrl+Shift+M or by applying the 'Create macro' intention.

The following screenshot shows an example of a property macro.

Macro functions and other parameterization options are edited in the inspector view.

Property macro, for instance, requires specifying the value function, which will provide the value of the property at generation time.
In the example above, output class node will receive the same name that the input node has.

The node parameter in all functions of the generator language always represents the node to which the transformation is currently being applied - the input node.

Some macros (such as LOOP and SWITCH-macro) can replace the input node with a new one, so that subsequent template code (i.e. code that is wrapped by those macros) will be applied to the new input node.

Two Kinds of External Templates

External templates are created as a root node in the generator model.

There are two kinds of external templates in MPS.

One of them is root template.
Any root node created in generator model is treated as a root template unless this node is a part of the generator language (i.e. mapping configuration is not a root template).
Root template is created as a normal root node (via Create Root Node menu in the model's popup).

The following screenshot shows an example of a root template.

This root template will transform input node (a Document) into a class (baseLanguage).
The root template header is added automatically upon creation, but the concept of input node is specified by the user.

It is good practice to specify the input concept, because this allows MPS to perform a static type checking in a macro function's code.

Root template (reference) can be used as a consequence in conditional root rule and root mapping rule. ( When used in a conditional root rule, the input node is not available).

The second kind of template is defined in the generator language and its concept name is 'TemplateDeclaration'.
It is created via the 'template declaration' action in the Create Root Node menu.

The following screenshot shows an example of template declaration.

The actual template code is 'wrapped' in a template fragment. Any code outside template fragment is not used in transformation and serves as a context for template fragments.

Template declaration is used in consequence of Weaving rule and Reduction rule. It is also used as an included template in INCLUDE-macro.

Template Switch

A template switch is used when two or more alternative transformations are possible in a certain place in template code.
In that case, the template code that allows alternatives is wrapped in a SWITCH-macro, which has reference to a Template Switch.
Template Switch is created as a root node in generator model via the Create Root Node menu (this command can be seen on the 'menu' screenshot above).

The following screenshot shows an example of a template switch.

Mapping Script

Mapping script is user code which is executed either before a model transformation (pre-processing script) or after it (post-processing script).

Mapping script provides the ability to perform a no-template-based model transformation.

Pre-processing scripts are also commonly used for collecting certain information from input model that can be later used in the course of template-based transformation.
The information collected by script is saved as a transient-, step- or session-object (see generation context).

Utilities (Re-usable Code)

If you have duplicated code (in rules, macros, etc.) and want to, say, extract it to re-usable static methods, then you must create this class in a separate, no-generator model.

If you create the utility class in the generator model (i.e. in a model with the 'generator' stereotype), then it will be treated as a root template (unused) and no code will be generated from it.

Generating Generator

MPS generator engine (or the Generator language runtime) uses mixed compilation/interpretation mode for transformation execution.

Templates are interpreted and filled at runtime, but all functions in rules, macros, and scripts must be pre-compiled.

To avoid any confusion, always follow this rule: after any changes made to the generator model, the model must be re-generated (Shift+F9). Even better is to use Ctrl+F9, which will re-generate all modified models in the generator module.

Demos

If you're feeling like it's time for more practical experience, check out the generator demos.
The demos contain examples of usage of all concepts discussed above.

Generation Process

The process of generation of target assets from an input model (generation session) includes 5 stages:

  • Defining all generators that must be involved;
  • Defining the order of priorities of transformations;
  • Model transformation;
  • Text generation;
  • File generation.

We will discuss the first three stages of this process in detail.

Defining the Generators Involved

To define the required generators, MPS examines the input model and determines what languages are used in the model.
Doing this job MPS doesn't make use of 'Used Languages' specified in the model properties dialog. Instead MPS examines each node in the model and gathers languages that are actually used.

From each 'used language' MPS obtains its generator module. If there are more than one generator module in a language, MPS chooses the first one (multiple generators for the same language are not fully supported in the current version of MPS).

If any generator in this list depends on other generators (as specified in the 'depends on generators' property), then those generators are added to the list as well.

After MPS obtains the initial list of generators, it begins to scan the generator's templates in order to determine what languages will be used in intermediate (generated) models.
The languages detected this way are handled in the same manner as the languages used in the original input model.
This procedure is repeated until no more 'used languages' can be detected.

Explicit Engagement

In some rare cases, MPS is unable to detect the language whose generator must be involved in the model transformation.
This may happen if that language is not used in the input model and the template code of other (detected) languages.

In this case, you can explicitly specify the generator engagement via the 'Languages Engaged on Generation' section in the input model's properties dialog.

Defining the Order of Priorities

As we discussed earlier, a generator module contains generator models, and generator models contain mapping configurations.
Mapping configuration (mapping for short) is a set of generator rules.
It is often required that some mappings must be applied before (or not later than, or together with) some other mappings.
The language developer specifies such a relationship between mappings by means of mapping constraints in the generator properties dialog (see also the Dividing Generation Process into Steps demo).

After MPS builds the list of involved generators, it divides all mappings into groups, according to the mapping priorities specified.
All mappings for which no priority has been specified fall into the last (least-priority) group.

tip
You can check the mapping partitioning for any (input) model by selecting Show Mapping Partitioning action in the model's popup menu.
The result of partitioning will be shown in the MPS Output View.

Mapping Priority Rules Notation Guide (link)

Model Transformation

Each group of mappings is applied in a separate generation step. The entire generation session consists of as many generation steps as there were mapping groups formed during the mapping partitioning.
The generation step includes three phases:

  • Executing pre-mapping scripts;
  • Template-based model transformation;
  • Executing post-mapping scripts.

The template-based model transformation phase consists of one or many micro-steps.
The micro-step is a single-pass model transformation of input model into a transient (output) model.
While executing micro-step MPS follows the next procedure:

  1. Apply conditional root rules (Only once - on the 1-st micro-step);
  2. Apply root mapping rules;
  3. Copy input roots for which no explicit root mapping is specified (this can be overridden by means of the 'keep input root' option in root mapping rules and by the 'abandon root' rules);
  4. Apply weaving rules;
  5. Validate references in the output model (all reference-macro are executed here).

There is no separate stage for the application of reduction rules. Instead, every time MPS copies an input node into the output model, it attempts to find an applicable reduction rule.
MPS performs the node copying when it is either copying a root node or executing a COPY_SRC-macro.
Therefore, the reduction can occur at either stage of the model transformation.

MPS uses the same rule set (mapping group) for all micro-steps within the generation step.
After a micro-step is completed and some transformations have taken place during its execution, MPS starts the next micro-step and passes the output model of the previous micro-step as input to the next micro-step.
The whole generation step is considered completed if no transformations have occurred during the execution of the last micro-step, that is, when there are no more rules in the current rule set that are applicable to nodes in the current input model.

The next generation step (if any) will receive the output model of previous generation step as its input.

tip

Intermediate models (transient models) that are the output/input of generation steps and micro-steps are normally destroyed immediately after their transformation to the next model is completed.
To keep transient models, enable the following option:
Settings -> Generator Settings -> Save transient models on generation

See also:

Generator Language

The language elements description and reference data
Previous Next

Labels

 
(None)