MPS 2021.3 Help

Generator User Guide Demo7

Generator User Guide Demo 7

In this final demo we will go back to Demo 3 and implement the desired capabilities in a slightly different way, so that we explore another area of the MPS generator - the weaving rules and root mapping rules.

In Demo 2, which we will build on here, we were generating java statements like:

container.add(new JButton());

to create a Swing component and add it to the application's content pane.

In Demo 7, just like in Demo 3, we are going to add support for component properties, which will require a generation of more complex initialization code - not just a constructor invocation. Moreover, the generated property initialization code is going to be different for different types of components. Therefore we will choose a generation strategy that is capable of handling such requirements - instead of making use of a SWITCH macro we will use Weaving Rules, which will 'inject' component's initialization code into the DemoApp class.

New Language

Again, we need to setup a new language and copy some of the Demo 2 generator artifacts to it.

  • create a new language: 'generator_demo.demoLang7'

  • in the language properties dialog add extended dependencies on 'jetbrains.mps.sampleXML' as well as on 'jetbrains.mps.baseLanguage'

  • create a new generator for this language, if it does not exist (see Demo 1 for details)

  • delete the (empty) mapping configuration 'main' from the demoLang7 generator (as in Demo 2, we will copy all needed parts from the demoLang2 generator to the demoLang7 generator)

  • copy-paste the mapping configuration 'main' from the demoLang2 generator to the demoLang7 generator

  • copy-paste the 'DemoApp' template from the demoLang2 generator to the demoLang7 generator

Enhance the language

This time, instead of having all input XML Elements represented as standalone roots in the input model, we're going to wrap them into a single root concept, called XMLDocument. The demoLang7 language will introduce this new concept as well as its editor:

Gdg14

The concept may hold a collection of XML Elements and it will present them on the screen as a vertical collection.

Screen shot 2017 07 26 at 1 26 17 am

addContent(container)

  • open the DemoApp template

  • add a static void method 'addContent(Container)'

  • in the 'main()' method find the statement:

    $LOOP$[container.add($SWITCH$[null]);
  • replace the statement above with statement below:

    addContent(container);

Gdc1

New Test Model

Before going on with the generator let's create a new input test model:

  • go to the 'test_models' solution

  • create a new model 'test7'

  • int the Used Languages tab add 'jetbrains.mps.sampleXML' and 'jetbrains.mps.samples.generator_demo.demoLang7'

  • add a new XMLDocument root node and type the code:

Gdg15

Now, let's define the semantics for these documents.

Root mapping rules

We used to utilize conditional root rules for instantiating the DemoApp class template in the previous demos. In Demo 7 we'll use a different mechanism - root mapping rules. In the 'main' mapping configuration delete the entry in the conditional root rules section as well as the one in the abandon roots section and instead add a root mapping rule:

Gdg16

This rule will replace an XMLDocument with our desired DemoApp class template.

Weaving Rules

Weaving rules are a vehicle that can add extra nodes into the output model. In our case we'll utilize them to insert code for adding swing components to their containers.

  • return to the demoLang7 generator and open the mapping configuration 'main' in the editor

  • create a new Weaving Rule (press Insert or _Enter _while cursor is in the weaving rules section)

  • choose the rule applicable concept - 'Element'

  • enter the rule condition as shown below:

Gdg9

Weaving Context

Weaving Rules inject additional generated code into other generated code. The Weaving Context is the exact place to which the Weaving Rule will inject the code. (the injected nodes will be the children of the context node).

In our case, we will inject the component's set-up code into our DemoApp class. Thus, we have to find the generated DemoApp class in the output model and pass this class to our weaving rule as its weaving context.

Mapping Label

We will use a mapping label to find the generated 'DemoApp' node in the output model. As already discussed in Demo 3, they serve as registries for generated nodes so they could be retrieved later by other parts of the generator. This loosens the coupling between independent generator parts.

Declaring Mapping Label

Before a mapping label can be used it must be declared in the mapping configuration:

  • go to the mapping labels section of the main mapping configuration

  • add a new mapping label (press Insert or Enter)

  • give it a name: 'main_class'

  • select 'ClassConcept' as the label's output concept, since we're going to store ClassConcepts in the mapping label

Gdg18

Attaching the Label to a Rule

Attach the mapping label 'main_class' to the Root mapping rule using the rule's inspector:

Gdg17

Using the Mapping Label for Finding the desired output node

Now, return to Weaving Rule and enter the following code into the context function:

Gdg19

This will now correctly resolve the DemoApp class corresponding to the processed XMLDocument and pass it into the weaving rule, so that it can add new methods to it. Notice the use of the jetbrains.mps.smodel query node.ancestor, which retrieves the closest node's ancestor of the specified concept in the AST.

External Template

Now let's create a template for this weaving rule, which will be used to generate the code to add to the DempApp class:

  • type 'weave_Button' in the red 'choose consequence' cell

  • press Alt-Enter and choose 'New Template' (apply intention)

  • open the template in the editor (use Ctrl-<left-click> or Ctrl+B on reference cell)

The template should already have a Class set as the template's content node, because that is the type of elements stored in the main_class mapping label. If not - choose 'ClassConcept' as the template's content node.

  • give the class a name

  • add a static method 'createComponent()'

  • create a template fragment: select the whole method declaration and press Ctrl+Shift+F:

Gdg6

IF-macro

The following steps are very similar to what we've done in Demo 3, since we're effectively generating the same code just using a different technique - weaving rules instead of reduction rules.

Inside the 'createComponent()' method we are going to create and initialize a JButton component.

Optionally, if the input Element has an attribute 'text', then we will generate a statement:

component.setText( _text_ )

where the text is the string specified in the 'text' attribute in input Element.

  • enter the following code in the body of the 'createComponent()' method

  • create an IF-macro around the 'component.setText("text");' statement (select the whole statement, press Ctrl-Shift+M)

  • enter the code for the condition function of the IF-macro, which will check the presence of the 'text' attribute

Gdg7

  • create a property-macro inside the string literal "text"

  • in the value function of the property-macro enter code that returns the value of the 'text' attribute of the input Element

Gdg8

Complete the Generator

To complete this step we have to create another Weaving Rule, which would be applicable to the 'label' Element.

  • open the 'main' mapping configuration ineditor

  • select the weaving rule, which we have just created for 'button' (select the whole node: put cursorinside the rule, use Ctrl+W to expand selection)

  • press Ctrl+D (duplicate) to create an identical rule right next to the original one

  • in the condition function, in the statement

    node.name.equals("button");

    replace "button" with "label"

  • in theprojecttree select the template node 'weave_Button'

  • duplicate this template node using the Clone Root command in the popup menu

  • in the editor rename this new template to 'weave_Label'

  • in the template code replace the statement

    JButton component = new JButton();
  • with

    JLabel component = new JLabel();

    Gdg12

  • re-auto-complete 'setText' in the statement if the method call is not resolved automatically

    $IF$[component.setText("$[text]");]
  • attach the 'weave_Label' template to the second weaving rule in the 'main' mapping configuration

    Gdg11
  • re-generate generator model

First Test (Error)

Try to rebuild the language and generate files from model 'test7'. Generation should run with no problems but compilation will fail with an error:

generator_demo\test7\DemoApp.java : Duplicate method createComponent() in type DemoApp (line: 37)

Click on the error message to view the error in generated code:

public static Component createComponent() { // <-- error JButton component = new JButton(); component.setText("Hello"); return component; } public static Component createComponent() { // <-- error JLabel component = new JLabel(); component.setText("world!"); return component; }

The problem is that our weaving rules are always injecting method declaration with the same name: 'createComponent()'

Generating Unique Names

To make the names of each generated 'createComponent()' method unique we will create another property-macro:

  • open the 'weave_Button' template in editor

  • add a property-macro to the name of 'createComponent()' method

  • enter the code in its value function as shown

Gdg22

The templateValue is going to be "createComponent" (i.e. the method's name as written in the template).

  • make similar changes in the 'weave_Label' template

  • re-generate generator model

Second Test

Generate files form the 'test7' model - this time we should get no error.

Preview the generated text for 'test7':

public class DemoApp { public static void main(String[] args) { JFrame frame = new JFrame("Demo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container container = frame.getContentPane(); container.setLayout(new FlowLayout()); addContent(container); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } public static void addContent(Container container) { } public static Component createComponent0() { JButton component = new JButton(); component.setText("Hello"); return component; } public static Component createComponent1() { JLabel component = new JLabel(); component.setText("world!"); return component; } }

The code has no compilation problems, but it is still not working, because the body of the 'addContent()' method is empty. We're not calling the creational methods that we've just weaved in. Let's fix that by weaving the following code:

container.add( createComponent0() ); container.add( createComponent1() );

into the body of the 'addContent()' method.

Second Template Fragment

To generate a method call of the 'createComponent()' method, we will add another weaving rule and a template. The calls to all the generated 'createComponent()' methods must be weaved into the 'addContent()' method of the main generated class, thus we first have to store the 'addContent()' method in a mapping label in order to refer to it from weaving rules.

G7f1

We can use the LABEL node macro to store the generated method in the mapping label:

G7f3

Now a new weaving rule needs to be added to the weaving rules section of the main mapping configuration that will insert the calls to the individual 'createComponent()' methods into the 'addContent()' method:

G7f4

Notice that the context retrieves the 'addContent()' method from the mapping label and returns the method's body as the node that will have the template fragments weaved in.

The weave_ElementInitialization template will insert the call to the individual 'createComponent()' methods into its context node (a method body, i.e. a StatementList):

G7f5

 

Add a parameter 'Container container' to the method declaration ( This parameter must have the same name as parameter in the actual 'addContent()' method in the 'DemoApp' template).

 

The actual 'createComponent()' method must be obtained from somewhere - a mapping label would work well, so we need to first create a new mapping label to hold these methods and insert the methods into the mapping label:

G7f6

The LABEL node macro will help us have the 'createComponent()' methods stored in the createComponentMethods mapping label for both Buttons and Labels in their respective weaving rules/templates:

G7f2

This should be done for both weave_Button and weave_Label.

Now we can return to the weave_ElementInitialization template and specify a reference macro on the call to 'createComponent()' to obtain the appropriate 'createComponent()' method from the createComponentMethods mapping label:

G7f7

Third Test

Now you can make the generator model a generate files from the 'test7' model in the 'test_models' solution.

Gdg21

Now you get correct and complete code for our simple application. 

Last modified: 10 September 2021