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:
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.
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:
The concept may hold a collection of XML Elements and it will present them on the screen as a vertical collection.
- open the DemoApp template
- add a static void method 'addContent(Container)'
in the 'main()' method find the statement:
replace the statement above with statement below:
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:
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:
This rule will replace an XMLDocument with our desired DemoApp class template.
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:
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.
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
Attaching the Label to a Rule
Attach the mapping label 'main_class' to the Root mapping rule using the rule's inspector:
Using the Mapping Label for Finding the desired output node
Now, return to Weaving Rule and enter the following code into the context function:
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.
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-Shit+F:
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:
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
- 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
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
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
re-auto-complete 'setText' in the statement if the method call is not resolved automatically
attach the 'weave_Label' template to the second weaving rule in the 'main' mapping configuration
- 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:
Click on the error message to view the error in generated code:
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
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
Generate files form the 'test7' model - this time we should get no error.
Preview the generated text for 'test7':
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:
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.
We can use the LABEL node macro to store the generated method in the mapping label:
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:
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):
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:
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:
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:
Now you can make the generator model a generate files from the 'test7' model in the 'test_models' solution.
Now you get correct and complete code for our simple application.