MPS 2021.1 Help

Shapes - an introductory MPS tutorial

If you're new to MPS and want to try it out quickly, this is the right tutorial for you. Within two hours  you'll get a new language and functional code that uses that language. In the tutorial you'll start from scratch and by walking along a safe and a convenient path you'll design the core elements of a new language. We'll avoid advanced concepts, complicated constructs and dark corners in order to get to the finish line quickly. At the end you'll know what MPS is all about and what principles it builds on.

So, fasten your seat belts. We're in for a fast ride.

Prerequisities

We'll assume you've gone through the initial parts of the Fast Track to MPS Tutorial  and so are familiar with the MPS environment, understand the concept of a language and a solution and can command the MPS projectional editor. If not, consider spending the first 30 minutes or so of your time to check it out. Especially these keyboard shortcuts are key to your survival:

  • Ctrl+Space  - to complete an incomplete word or to turn an invalid (red) identifier into a correct (black) one

  • Alt+Enter  - to display a menu with handy options applicable at the current editor position

  • Ctrl+Up  - Expand the region of selected text

  • Ctrl+Down  - Shrink the region of selected text

  • Tab  - to navigate around editable elements in the editor

  • Ctrl+Z  - Undo

We also assume you've installed MPS and you have it running in front of you. So now we can set off.

Goal

You're going to implement a sample language for specifying graphical shapes. The language will allow its users to lay out visual two-dimensional shapes on a flat canvas. The definition will be then translated into a Java Swing application, which will visualize the layout on the screen.

Code

Result

The language could enable non-programmers to build Java applications without any knowledge of Java, Swing or the 2D Graphics API. You, on the other hand, will play the role of a language designer, who will prepare such an easy-to-use language building on his or her knowledge of Java. You automate the work of a UI programmer by providing a language and a generator that cover some of the cases that a UI programmer currently has to solve manually.
If you are not versed in Java or Swing, do not worry too much too early. We assumed this possibility and made the tutorial to guide you carefully. You'll be able to pass easily, you'll see.

Create a new project

We'll start with a fresh project. On the welcome screen you click Create New Project and then follow the wizard.

Welcome Screen

New Project dialog

You'll get an empty project containing and empty Language Definition  and an empty Solution.

New project

Solutions  hold your programs. To write these programs, you use languages, either defined in the same project or imported ones. In our tutorial will first define a language and than use it to write code that we can execute.

Languages and programs under the hood

First, here's a bit of background knowledge that you should know before going on. This is a piece of code that you will be able to create once you implement the Shapes language fully.

Sample code

The language that we're building must allow for painting definitions, which consist of individual commands, each on a separate line and each defining a single shape to draw. Our language needs to cover each such command with a Concept. Concepts  define the abstract syntax of a language, that is the set of allowed language logical constructs. A program then consists of Abstract Syntax Trees, which hold instances of these Concepts.

Abstract Syntax tree

The AST of the short program above shows the abstract syntax - it consists of Nodes, each Node  is an instance of a Concept  that the language defines. Concepts define properties, children and references and Nodes  then give the concrete values.

To wrap up the little theory here:

  • Languages  consist of Concepts

  • Concepts  define the logical (abstract) elements with their properties, children  and references

  • Programs (solutions)  consist of ASTs, which consist of Nodes

  • Nodes  are instances of Concepts  giving concrete values to  propertieschildren  and  references  of their  Concepts

Graphical shape

Our language is going to be pretty straightforward. We'll need only a few concepts:

  • Canvas  - to define the top-most node representing the whole Painting definition  and holding all Shapes

  • Shape  - representing a command to draw a shape on the Canvas, it will serve as a common super-concept  to all concrete shapes, such as a circle or a square

  • Circle  - representing a command to draw a circle

  • Square  - representing a command to draw a square

  • ColorReference  - representing one of several pre-defined Colors defined in java.awt.Color

We'll start our practical exercise with the Shape  concept. Just like in object-oriented programming, Concepts  can extend one another and thus inherit the capabilities of their super-concept. The Shape  concept will serve as such a common super-concept, holding the color  property, since all shapes in our language will need the color  property, so we can conveniently inherit it.

New | Concept menu item

Right-click the Structure  aspect of the language and create a new Concept. You'll get a new Concept  definition open in the editor.

New concept definition opens

We should give the Concept  a descriptive name, Shape  in our case will work fine.
So just type  Shape where MPS indicates a missing name - the red  <no name> field.

Giving a name to the concept

We created the Shape  concept as a common super-concept  for all shapes in our language and by itself Shape  will not be used directly in ASTs. We'll mark Shape   abstract  to indicate explicitly that no instances (nodes) of Shape  can be created.

It is the time to practice the Alt+Enter  keyboard shortcut now.

Can you see the light-bulb symbol on the first line of the concept declaration? (You may need to move your cursor to the first line for the light-bulb to show up. It is a bit shy.) The light-bulb is hiding a contextual menu with tips of what you can possibly do to your code at the cursor position.  Alt+Enter gives you access to the light-bulb menu (we call it  Intentions menu) from the keyboard.

Intentions menu
Added the abstract keyword

When positioned on the name of the Concept, the Alt+Enter  keyboard shortcut brings up a contextual menu, which gives you the option to apply the "Make Abstract" intention to the Concept. Once you choose that option, the concept is marked as abstract. Now we could add properties, children and references that should be shared by all sub-concepts of Shape, but we will leave that for later to make our learning curve flat.

That's our first concept! Hurray! Time to add another one. How about Circle? Sure, by right-clicking on the Structure  aspect we create another Concept  and give it a name - Circle.

Circle

Following the same steps, you need to right-click the Structure  aspect of the language and add a new concept. Name it Circle.

Adding another concept

Circles should inherit capabilities from Shape, so we need to indicate than in the extends  clause. Point at the beginning of the cell holding the "BaseConcept" text and hit the most useful keyboard shortcut in MPS - Control + Space  - to invoke code completion. MPS will show you a list of options applicable as replacements for the "BaseConcept" text. We need to select  Shape  here to make  Circle  extend  Shape.

Code completion helps you quickly change the parent concept

If you do not see  Shape in the completion menu, it is because your cursor is not on the first position of the word.

Code completion depends on the position of the caret

The characters to the left of the cursor are used as a filter to show only options that match the these characters. The further to the right your cursor goes, the fewer options will be displayed in the completion menu.

Parent concept changed

You have to move your cursor with the left arrow or, if you press  Control + Space again, MPS will move the cursor to the very left of the current word for you.


Adding an alias

So this is our first concrete concept that will be used by users of our language. To give the concept a nice textual representation in code-completion dialogs and enable MPS to be smart about creating an instance of Circle  whenever the user types "circle", you need to give the concept an alias 'circle'.

Creating properties

Each circle needs to specify its coordinates on the screen and its radius. We'll create properties that will hold these integer values. Navigate to the properties  section, place the caret at the "<< ... >>" symbol that represents an empty collection of values and press Enter. This will create an empty property.

Expanding the properties section
Setting the name and the type for a property

You give the property a name "x", then hit Tab and provide the type of the property - "integer". While typing "integer" you may hit the Control + Space  keyboard shortcut to have the name of the type completed by MPS.

Auto-completion helps you enter the type
Adding the other properties

Then add properties for "Y" and "radius" and you'll be done with your first concrete concept.

Square

Now you should try yourself to create a concept for square. Repeat the steps we did for Circle, just create different properties - upperLeftX and  upperLeftY  to hold the coordinates of the upper-left corner followed by size  to specify the length of the sides of the square. Ultimately you should get to this:

Defining Square

Do not forget to extend the  Shape concept on the first line. You may have noticed that while the completion menu (Control + Space) is displayed, you can still type characters, and they will be used to filter out the entries available in the completion menu.

Canvas

Having created two shapes we can move on to defining a concept to hold them all in a painting. We'll create another concept, called Canvas, that will represent a scene composed of shapes. The user will be able to create multiple scenes (Canvasses ), which will be mutually independent and will not share shapes. Each Canvas  will hold a name and a list of shapes that it contains.

So yet again right-click the Structure  aspect of the language to create a new concept and give it a name Canvas.

Creating the Canvas concept

Navigate (using Tab) to the "<none>" text in the implements  section and specify INamedConcept  (either type it in or use Control + Space ). INamedConcept is a Concept Interface  that Canvas  will now implement. Concept Interfaces, just like Concepts, add new capabilities to Concepts  that implement them. INamedConcept  in our case enriches Canvas  with the name  property, so Canvas  instances (called  Nodes) will have a name  property so the user can easily distinguish between them.

Implementing an interface
Setting the 'instances can be root' option

Since Canvas  will represent the painting scene and will by itself not be part of any other concept (it will not have any parent in the model), we mark it that  instances can be root. This will allow Canvas  instances to be the roots of ASTs.

To indicate that Canvasses  may hold Shapes, we'll create a child collection of shapes. Again, hit Enter  in the "<< ... >>" cell of the children  section, type in the name of the child and the type of nodes it may hold. Do not forget about the* * Control + Space  key shortcut to bring up the code-completion dialog.

Expand the children section
Specify the role and target for children shapes

You should end up with a concepts definition like this:

Concepts definition

Now we've created enough concepts to have a minimalistic language ready for use. Let's give it a try.

An early test ride

We need to build the language before we can use it. In the future, after you've made a change to your language definition, remember to repeat the process of "Rebuild" in so that the change could take effect. Right-click on the top-most node (the very most root, representing the whole project, including the Language and the Solution) in the Project View  and choose Rebuild Project.

Rebuild project menu item

Once built, the language will be available for use in the model inside the sandbox  Solution. Create a new Canvas  by right-clicking on the model and choosing the root concept to instantiate. Notice, the root concepts are those that show up in this menu and can be instantiated at the top-most level within a model.

Creating a new Canvas instance

Now give the Canvas  instance a name and you've got your first painting. You can start using the concepts defined in the language to place visual shapes on the canvas.

Giving the Canvas instance a name

Canvas instance

Again, Control + Space is the  keyboard shortcut to use heavily here. Use it each time you hesitate what to type. Notice that the code-completion dialog offers us the two types of shapes that we have created in the language.

Code completion prompts the two existing shapes

Sh31

Sh32


You can insert additional shapes by pressing Enter  at the end of the last shape in the list of shapes, which in our case is the closing "}" symbol for the "circle". Notice that  Control + Space can spare you typing the names of the shapes.

Sh33
Sh34

Cool! I hope you're celebrating your success properly. Now, how about tuning those editors to make the code look better on the screen?

Editors

MPS uses a projectional editor. You might have noticed that already by feeling that the editor behaves slightly differently from what you would have expected. Unlike text-based languages, MPS never represents code as plain text. Instead, code in MPS means AST - Abstract Syntax Tree. Always.

This comes with huge benefits for language design, language composability and non-parseable notations, which you can read more about in the MPS documentation.

Here we should focus on the editing aspect of projectional languages. Since plain-text editors cannot represent ASTs reliably and since editing ASTs directly would be highly inconvenient, projectional languages have to provide editors for all Concepts  that are part of the language. Sometimes even multiple alternative editors for a single Concept, but that's not our goal here. MPS can do a good job in many cases to provide a default editor for Concepts  that do not have one. This is very nice for language prototyping. For convenient use by an end-user we, however, should spend some time preparing an explicit editor.

Shape

The Shape  concept does not need an editor, since it is an abstract  concept. So we will leave this one untouched.

Circle

For Circle  we could create an editor that nicely places all necessary properties on a single line. We'll open the Circle  concept in the editor, click the green '+' button in the lower left corner and pick Editor -> Concept Editor.

Sh9a

Sh36

You'll get an empty editor definition for the Circle  concept. Remember, Control + Space  will be needed heavily to edit things here.

Sh37

The editor for a concept in MPS consists of visual cells, each of which represents some piece of information belonging to the underlying concept. As nodes are composed hierarchically in an AST, so are their editors composed on the screen with sub-nodes' editors nested inside their ancestors' ones.

For Circle, we'd like to show value of all the properties (x, y, radius) plus some arbitrary text around them, all on a single line.
So first we'll choose a layout for these cells - indent layout  will work just fine here. So hit Control + Space  in the red cell and pick it up from the list. You may speed the search up by typing a square bracket followed by a minus symbol.

Sh38

Now type "circle" to enter constant text that will be placed at the beginning of the line.

Sh39

Hit Enter  to create a new cell. Type "x:" to mark that the following cell contains the value of the x  property.

Sh40
Sh41

Hit Enter again to create a new cell. Remember to press  Enter first each time you want to add a new cell and then select the kind of cell to insert from the completion menu (Control + Space ).

Now you have to pick the x  property from the code-completion menu to bind the cell to the right property.  Do not just type {x}, use  Control + Space to display the completion menu and select the x property from the menu: 

Sh42

Now you can continue on your own to insert cells holding constant text and well as values of the y  and radius  properties. Remember, Enter  will insert new cells, Control + Space  will bring up the code-completion menu. You should end up with an editor like this.

Sh43

Square

Square also needs an editor. Open the Square  concept in the editor, hit the '+' symbol in the lower left corner and create a new Concept Editor. Following the instructions in the previous section, enter the editor definition as follows:

Sh44

You can certainly do this on your own.

Canvas

The editor for the  Canvas  concept will be slightly different, since it holds a collection of shapes that all need to be given some space on the screen. Canvas thus spreads across multiple lines, each line will show one shape from the Canvas' collection of shapes. You, however, start in the same way as before, open the Canvas  concept, hit the '+' symbol to create a new Concept Editor, insert an indent layout  and enter some text to get the following:

Sh45

Now bind the red cell to the name  property. Since the property is inherited from the INamedConcept concept interface, it will be further down in the completion menu (Control + Space), but it is surely there.

Sh46

You ensure that you have inserted the  name property correctly if you can see the curly braces wrapping it. (Beware, you are not supposed to insert these curly braces yourself, they are added by MPS for all property cells.)
The following cell will hold a collection of shapes that have been added to the Canvas. Note how we compose editors here - Canvas  only marks an area, in which individual shapes should be edited, and leaves that up to the shapes how they use that area.
Since the shapes should be organized vertically, each on a single line, you should pick vertical collection layout  from the completion menu.

Sh47

Be careful here. If you have selected a different layout than vertical, the shapes will not be nicely organised one below another, but perhaps one next to another on the same line, which, while quite innovative, may not be the most intuitive way to use your language..

Sni mek obrazovky 2018 12 06 v 14 51 05

You need to bind the red cell to the shapes  child collection of Canvas.

Sni mek obrazovky 2018 12 06 v 14 51 26


Sni mek obrazovky 2018 12 06 v 14 51 50

Now, to place the collection below the name of the Canvas, you should use the Alt+Enter  shortcut (or the light-bulb symbol) the bring up the intention pop up menu and pick "Add On New Line".

Sni mek obrazovky 2018 12 06 v 14 52 08

If you accidentally press "Add New Line", undo that action with  Ctrl+Z .
You'll get the final editor definition:

Sni mek obrazovky 2018 12 06 v 14 52 24

If you version looks different, there may be several reasons:

  • You inserted the  name property from the completion menu. If you only typed "name", the cell is most likely not bound to the  name property and will only be ever showing the text "name" when used.

  • You set the "Add On New Line" intention. If you accidentally chose "Add New Line" instead, the  shapes cell will not be drawn below the "Painting ..." first line, but next to it.

  • If you set the "Add On New Line" property on a different cell that the  shapes cell, the layout will also look different from the expected one.

  • You chose a different layout for the shapes collection than vertical. Different layouts use different symbols. Vertical uses "(>", while "(-" and "(/" belong to the indent and horizontal layouts, respectively.

In all these cases repetitive  Undo Ctrl+Z will help.

The second run

Now you can rebuild the project again (right-click on the very top node in the Project View) and look at the sandbox code in   MyDrawing.

Sh53

Look how the code layout changed:

Sh54

It is the same code (AST), but it is organized on the screen differently. While previously it was an ugly tree-like text with lots of curly braces, now the code reflects the editor definition that we provided to MPS.

If your view is different, revisit the three editors. Given that they are fairly small, feel free to delete then and do them over, if needed. You can delete a whole editor definition if you locate it in the Project view under the  Editor aspect model and press  Delete or right-click and choose delete in the context menu. Alternatively, you may keep pressing the  Delete and  Backspace buttons inside the editor definition until you remove all the suspiciously looking code.

Coloring

Now we should come back to Shape  and add support for colors. Since both Circle  and Square  extend Shape, they will both inherit the color.

Since we would like to allow the users of our language to pick the color for the shape from a list of pre-defined colors, we can't just use a textual property to hold the color value. Instead we'll add a reference to Shape  and have this reference point to one of the pre-defined color constants. Let's start with creating such constants that will represent pre-defined colors.

Concept for colors

One way would be to use MPS enums to define the colors. This would, however, not allow users to define their own colors. All the colors would be defined in the enum that would be a part of the Shapes language. Instead, we will use full a blown concept for colors and nodes of that concept will define the individual colors. These color nodes can be defined as part of the language (inside so called accessory models) or directly in user models next to instances of the  Canvas concept.

First we're going to create a concept that will represent a color constant. We'll call it  Color and it will be rootable, so that we can place it inside models:

Shx1

An editor is also needed:

Shx2

Pre-defined colors

Now, rebuild your project. The Color concept that we have just added needs to be compiled, so that we can use it to create some colors. We need to provide concrete nodes of the  Color concept, which will represent the individual color constants and which the user of our language will be able to refer to from their  Canvasses. We will utilize  Accessory models for this.  Accessory models are models inside a language definition that hold arbitrary nodes, which become part of the language and are visible to the language users.

So first, we need to create an  Accessory model  in the language:

Shx7

The model demands a name. Please make sure the  stereotype box is empty, if it is not disabled by MPS:

Shx8

The  Shapes language, which declares the  Color concept, is the only language that we need imported in the accessory model:

Shx10

Switch to the  Used Languages tab. It is either empty or contains a devkit that we can leave in the list. Click the  plus symbol and select the  Shapes language from the list. The language must have been added to the list of used languages.

Then switch to the  Advanced tab and select the  Do Not Generate checkbox:

Gen shap

Then click  OK  to close the dialog.

Create colors

Now you should be able to create color constants in the newly created accessory model:

Shx11

If you do not see  Color in the  New menu, you most likely forgot to rebuild the language. Or, you have missed the  can be root property of the  Color concept, which has to be set to  true.

Shx12

The first touch on dependencies

A crucial piece of knowledge in MPS is how to handle dependencies and imported languages. To display the dependencies of a module or a model, you have to navigate to it in the left-hand side Project View  panel (Alt+1 to open the panel). Try that for the  Shapes language. Select it in the tree:

Sh70

Hit  Alt+Enter  (or right-click on it and choose Module/Model Properties) and you'll get a dialog with the properties of the module/model. The  Dependencies  tab shows the modules/models that the current module/model depends on (aka import in Java, require in Ruby, etc.).

Shx100

Yours is most likely empty now. You add elements using the '+' button. In the small search dialog type a few characters of the name of the desired dependency to narrow down the search and hit  Enter  when you locate the correct one.

Sh72

In the  Used Languages section you indicate, which languages (syntaxes) you want to be able to use in your module/model.

Shx101

We do not need to change anything here, so let's continue by clicking the OK button.

Concept for color reference

Now our language needs a way to indicate the desired color of a shape in our code. Thus Shape should be referring to one of the color constants in the accessory model of the Shapes language. MPS will automatically populate the completion menu with all available color constants, whenever the user is about to specify a color for a Shape.

Create a new concept in the  Structure aspect model and name it ColorReference:

Shx3

ColorReference  keeps a reference (a pointer going across the AST hierarchy) pointing to a single color constant (node of the Color concept).

In order to display and edit the colors we also need an editor for ColorReference.

Shx4

The reference should simply display the name of the color constant that it refers to, so we pick target  from the code-completion menu and specify that the name  property of the color constant is what we want to show to the user.

Shx5

You should get an editor definition as follows:

Shx6

Updating Shape

The Shape  concept is a good place to put our new ColorReference, since both Circle  and  Square  will inherit it. Open it in the editor an make the change:

Sh96

Shape  may also define an editor component  to define the editor for the color and Circle  as well as Square  will be able to reuse that editor component  in their editors thus avoiding duplication.

Hit the '+' symbol and create a new Editor Component.

Sh97

Sh98

The editor component definition a language that you are already familiar with and so you will be easily able to specify an indent layout, define a constant text cell followed by a cell bound to the color  property.

Embedding the editor component

The editor component defined for Shape  should now be added into the editors for Circle  and Square.

Sh99

Sh100

Sh101

The editor component  becomes just another cell in the editor's layout.

A third run

Now it is the best time to rebuild the language. Right-click on the Language node in the Project View  and select "Rebuild Language".

Opening the MyDrawing  program should show you the empty cells for colors in red. Try Control + Space  in them and you will get a list of colors that you can pick from.

Shx13

Shx14


Now our language is fully defined. We can create a  Canvas  and add Circles  and Squares  to it, specifying their positions, sizes and colors. That's quite an achievement for such a short time.

What we're missing yet is the translation of these programs into Java, so that we could run them and see the shapes nicely drawn on the screen. If you continue you'll soon realize that we're almost there.

Generator

Our language now needs a generator so that we could generate code that could be compiled and run. We will choose BaseLanguage as the target for our DSL. BaseLanguage is a copy of Java distributed with MPS and so it can be easily transformed into textual Java sources for the Java compiler to compile into binaries. We could possibly choose any other target platform and language, provided you plug-in a definition of that target language into your project.

The generator will be very straightforward and will only need a few rules and a single mapping configuration.

Tg1

Your language already contains a skeleton of an empty generator. You can open the mapping configuration, that will specifying what rule to apply when. We will be adding configuration entries here gradually.

Here's the idea behind the generator that we will implement:

  • A Canvas  gets translated into a Java class, which will extend Java's JFrame  class and hold a JPanel  that all the shapes will be drawn on

  • Each Shape  gets translated into a method call on the Graphics  object to draw the shape on the JPanel

  • A ColorReference  gets translated into a reference to the appropriate color constant in the java.awt.Color  class.

Let's start with the class for Canvas. You need to add a new entry in the root mapping roles, since Canvas  is a root concept. Hit  Enter to insert a new empty rule:

Tg2

You need to use an  Intention (Alt+Enter  the be able to select "New Root Template" from the pop-up menu.

This rule defines that a  Canvas  node should be replaced with a Java class. The template defines with what class:

Tg3

The root template should be generating a Java class, so you need to pick "class".

Tg4

This is the finishes root mapping rule. The map_Canvas  is a name of the root template that was created in the generator. You should open it up so we could make changes to it.

Tg5

First we need to set the dependencies of the generator module  to depend on the JDK module. Without this dependency you would not be able to compile your sandbox eventually.

Note:  Remember,  Alt+Enter  will bring up the properties of the node selected in the left-hand  Project View.

Shxx4

Second, the  generator model  must depend on java.swing and java.awt, as specified below. Without these dependencies you would not be able to type the Java Swing code needed to implement the generator templates:

 

Shxx3

With the dependencies you can start typing the Java code that will be part of the generated Java class. We will then parametrize the code with values from the Canvas, to make it reflect the user's intent.

The class needs to extend JFrame.

Tg8

Now you'll add the main  method to get a runnable Java class. You can use the "psvm" live template to enter the method quickly.

Tg9

Tg10

Inside the method, we'll need to instantiate  map_Canvas.

Tg11

We'll also need a method to initialize the frame. Type "method" and use Control + Space  to complete the method definition:

Tg12

Call the method Initialize()  and make sure that the method is called from main.

Tg13

All the shapes will be drawn on a JPanel, so we now need to add one as a field.

Tg14

Tg15

Notice, we use an anonymous inner class to be able to customize the JPanel  a bit.

Important:  To create an anonymous inner class in BaseLanguage, position your cursor right after new JPanel()  and before the ending semicolon. Then hit the "{" (left curly brace) key and MPS will add the ending "}" symbol. Now keep the cursor between these "{" and "}" symbols to add methods to the panel's anonymous inner class.

We'll override the paintComponent  method of JPanel, because this is the method where Java allows us to easily draw the shapes on the JPanel. Hit Ctrl+O , while the cursor is inside the JPanel 's anonymous class body between the "{" and "}" symbols, to invoke the Override method dialog  for the JPanel  and select the paintComponent  method.

Tg16

Make sure your paintComponent()  method is correctly nested inside the JPanel 's anonymous inner class as displayed in the screen-shots. Also make sure it is called paintComponent, not paintComponents.

Enter the following code into the method:

Tg17

Now, please, fill in the  initialize  method and we have a template ready:

Tg18

Parametrizing the template

The code in template currently does not use any values from the input model. It simply hardcodes the code and so the code generated by the template will always be the same, no matter what shapes are created in the user model. This has to change. The template must react to the input model and the generated code must reflect the shapes, their sizes and colors. The properties and children of Canvas  should be inserted into the template through macros. MPS gives you three types of macros:

  • property macros  - to insert properties from the input model

  • node macros  - to replace nodes in the template with nodes from the input model

  • reference macros  - to adjust references in the template to point to nodes in the input model

We'll gradually use all of these.

To start with, we'll customize the name of the generated class and the Title  of the frame with the name  of the Canvas. Place the cursor on the name of the class - map_Canvas  and hit Alt+Enter.

Tg19

Tg20

Now select the node.name  property macro from the pop-up menu.

Tg21

The "map_Canvas" text is now wrapped (annotated) with a property macro, which changes the name property to the name  of the Canvas. The Inspector panel (Alt + 2) can be used to enter or modify the property macro, as well. Currently it returns the value of node.name, which is the name of the current  Canvas.

Now you can wrap the "Title" text to customize the title of the frame. Using the Ctrl+Up  key shortcut select the text "Title" without the surrounding " characters and with Alt+Enter  insert the correct property macro:

Tg22

The code should now look like this:

Tg23

Drawing shapes

Our template assumes the code that draws shapes should be placed inside the paintComponent  method of the JPanel  field. The statement "System.out.println("Draw here");" serves as a placeholder for the real code that will draw all shapes. We will use the COPY_SRC  macro to replace the placeholder statement with a statement that draws a single shape and we'll leverage the  LOOP  macro to repeat that for all shapes defined in the current Canvas.

Now, please select the placeholder statement including its closing semicolon using the Ctrl+Up  key shortcut, hit Alt+Enter  and choose the appropriate  Node macro  option to insert a LOOP macro  to loop through all the child Shapes  of the current Canvas.

Shxx1

Again, the Inspector shows the binding code.

Tg25

If the  LOOP macro is underlined in red, most likely you did not select the whole line before applying the intention. Undo, select the whole line including the semicolon and apply the Add LOOP macro intention again.
The LOOP macro  will repeat the "System.out.println("Draw here");" statement for each shape listed in node.shapes.

We, however, need to have the "System.out.println("Draw here");" statements replaced with code that draws each of these shapes. The COPY_SRC macro  will do just that. Please, select the whole statement within the LOOP macro  again, including the semicolon, hit Alt+Enter  and choose Node macro.

Shxx2

Type in COPY_SRC  (Control + Space) and you get a macro that will replace "System.out.println("Draw here");" with the current shape for all shapes that the LOOP macro  provides.

Tg27

Tg28

Make sure your  COPY_SRC macro wraps the whole statement, including the semicolon, like it is displayed in the picture. If not, undo, select the whole statement and insert the  COPY_SRC macro again.

Generating circles

Now we get Canvas  to be translated into a Java class and we also made a place for Shapes  to add the code that will draw them. The time is up for us to define the actual translation rules for Shapes  themselves, so that we get a "graphics.drawCircle()" method inserted in the generated code as a replacement for the Circle  shape. You need to open the main  mapping configuration and add a new entry to the "reduction rules" section:

Tg29

Alt+Enter  to create a new template:

Tg30

The new reduct_Circle  template will show up in the left-hand Project View.

Tg31

You may also create a reduction rule for Square:

Tg32

Open the reduce_Circle  template. We now need to specify the Java code that will replace Circles. Remember, that the Java code will be placed in map_Canvas  inside the paintComponent  method.

Tg33

First, we'll enter a BlockStatement, that will wrap our template:

Tg34

We will need a local variable of the Graphics  type as a place holder for the paintComponent  parameter of the same name. And yet again a BlockStatement.

Tg35

Tg36

Tg37

To draw a circle in Java, we'll use the Graphics  object to set the color first and that invoke its drawOval  method. Please enter the code below:

Tg41

Tg38

Then use Ctrl+Up  to select the inner BlockStatement, hit Alt+Enter  (light-bulb) and pick "Create Template Fragment" from the intentions menu. This will mark the selected fragment of the code as the actual template, which will eventually be placed into map_Canvas

Tg39

 

Parametrization

The code now needs to be parametrized with actual values from the Circle  node.

Tg42

The first value "10" should be replaced with the x  coordinate of the Circle  node. A property macro  will do that. Similarly the second value "10" should be replaced with the y  coordinate.

Tg43

The third and fourth values "10" should both be replaced with the radius  value of the circle  node.

Tg44

Finally, the "Color.red" color placeholder reference should be replaced with the actual target of the color  reference of the  Circle  node. We will use a reference macro  to replace the references. Please put the cursor on the "red" word and hit Alt+Enter.

Tg45

The reference macro will replace the reference to  red with a reference to a node that we specify in the  Inspector window:

Shx102

The  referent function returns either a string value (a name of the desired declaration to refer to) or  node<StaticFieldDeclaration> (a node representing a StaticFieldDeclaration ), because  red itself is a reference to  node<StaticFieldDeclaration>. In fact, all color constants in the  java.awt.Color class are declared as StaticFieldDeclarations.

So our task inside the reference macro will be to retrieve the StaticFieldDeclaration from within the  Color class that corresponds with the color that the Circle has set as its color child. We will do this programmatically and so we will need to set dependencies on the right modules and models, which will allow us to write the required code.

First, the generator module has to depend on BaseLanguage in order to be able to refer to the StaticFieldDeclaration concept, which is declared in that language:

Shx104

Second, the generator model needs to be able to refer to the concepts defined in structure of the Shapes language:

Shx105

With these languages imported we should be able to enter the code that discovers the correct static field declaration within the  Color class:

Sni mek obrazovky 2018 12 07 v 20 29 44

The  node-ptr/.../ construct allows you to obtain a node in the imported models of a specified concept represented with the given name. Since there only exists one  Color class in JDK, the reference identified as  node-ptr/Color/ will be unique and will be pointing into the model to the Color class.

Note: Make sure you pick the right  Color  element from the completion menu. It must be  java.awt.Color, not the  Color concept from the  Shapes language.

Sni mek obrazovky 2018 12 07 v 20 30 23

Node pointer represent persistent references to nodes. To get a reference to the real node in memory, the node pointer must be resolved in the model repository. Use the following code to obtain the repository and resolve the  Color node from it:

Sni mek obrazovky 2018 12 07 v 20 31 33

The  downcast operator gives you access to the underlying Java API, which is currently the only way to get a repository in this place.

Sni mek obrazovky 2018 12 07 v 20 44 23

Using the collections language you can complete a concise query:

Sni mek obrazovky 2018 12 07 v 20 33 23

Since  node is an instance of the  Circle concept,  node.color is the circle's reference to the color (an instance of  ColorReference) and  node.color.target is a  Color (an instance of the Shapes.Color concept) from the  accessory model.

In brief, the query searches the first static field declaration within the static field declarations of the java.awt.Color class with the same name as is the name of the color specified for the  Circle.

Reducing Squares

We'll start mimicking how generator is done for Circles. Identically provide the following code for the reduce_Square  template.

Hint: Start by inserting a  BlockStatement


Tg47

The values passed into "drawRect" should be replaced with property macros with the upperLeftX, upperLeftY  and size  properties of the Square.

Tg40

Generating code

Now we're done defining the generator. If you rebuild the language, open  MyDrawing, right-click it and choose "Preview Generated Text",

  

Tg48

you'll get a nicely structured Java code that properly initializes a  JFrame  and draws all the shapes:

Ppview2

If your code is different, look into one of your generator templates, they are probably different from the ones presented in the tutorial. Maybe your macros are not attached to the correct pieces of code or the values specified in the  Inspector  window for the macros differ from the ones in the screen-shots.

If the code does not compile, make sure your generator module depends on the JDK module, as we defined earlier.

A more robust generation for Squares

The way we handled the  graphics local variable in the templates was not quite right. We relied, perhaps too optimistically, on the name of the variable to be the same in map_Canvasreduce_Circle and reduce_Square. What if the names of the variable in these three templates were not the same? A more robust solution is needed

As indicated earlier in the section for the  Circle  generator, we'll use the reduce_Square  template to properly tie the graphics  local variable with the graphics  parameter that the map_Canvas  template generates. Relying on name match is not very robust.

We basically need to go through three steps:

  1. Define a storage for created graphics  parameters

  2. Store the graphics  parameter in the map_Canvas  template

  3. Retrieve the proper graphics  parameter in the reduce_Square  template

We'll start by creating a mapping label in the mapping configuration. This will be the storage of  ParameterDeclarations, each identified by the  Canvas from which it was generated. You may also think of this mapping label as of a dictionary that maps  Canvases to  ParameterDeclarations.

Shapes3001

The graphicsParam  mapping label stores ParameterDeclarations  mapped by Canvases  that they belong to.

The map_Canvas  template now needs to store the graphics  parameter in the mapping label. The MAP_SRC  macro can be leveraged for that with great success:

Shapes3002

Wrap the parameter declaration (including the type) with the MAP_SRC  macro (Alt+Enter, pick node macro, type MAP_SRC_) . Down in the _Inspector  window you then select the mapping label  to use for storing the generated parameter declaration. The current source node, which is an instance of the Canvas  concept, will be used as the key to identify the generated graphics  parameter declaration in the mapping label.

Finally, we need to retrieve the parameter declaration from the mapping label in the reduce_Square  template. To indicate clearly that we are no longer relying on naming match, we can use a different name for the variable than graphics. We'll go with g  in the sample:

Shapes3003

If you Alt+Enter  on the g  reference and pick reference macro, you'll be able to retrieve the proper graphics  parameter from the mapping label:

Shapes3004

Then repeat the reference macro creation for the second g reference.  Both reference macros attached to the g variable reference that you have just created must specify in the Inspector the details on how to obtain the desired target of the reference. The  genContext object gives access to helpful methods and properties of the current generation session. Use the "get output for label and input" operation on  genContext and provide the  graphicsParam mapping label as well as the  Canvas holding the currently generated  Square.

Shapes3005

We could now replicate the retrieval of the graphics  parameter for the reduce_Circle  template, as well.

Running the code

It is nice to see generated code, but you might actually prefer seeing it running. MPS can compile and run generated Java code easily. We only need to indicate that Canvas  is generated into a runnable Java class and thus Canvas  itself should be treated as runnable, or as a "main" class. We only need to make Canvas  implement the IMainClass  interface and MPS will take care of the rest. The IMainClass  interface comes from the  jet brains.mps.execution.util  language and so we need to add it to the list of dependencies of our language and set the scope to Extends:

Tg50

Use the Alt+Enter  to get the properties dialogs. Notice that the language needs to be marked as Extends.

The Convas  concept can now have the IMainClass  interface added to the implements  section.

Tg52

Rebuild the language and then right-click on MyDrawing  in the Project View  and click "Run".

Tg53

You will get a running Java application with your drawing on it as a reward for your efforts.

Tg54

An alternative generator - generating XML

Just to give you an idea how the generator could be utilized to generate code in a declarative language, such as xml, here's a simple generator for the Shapes language generating xml. We start from an empty generator. The Java templates and rules have all been deleted:

Sni mek obrazovky 2018 12 18 v 12 55 47

First, the  jetbrains.mps.core.xml language must be imported (Control + L ). It is the projectional equivalent to xml, as we know it, just like  BaseLanguage is a projectional equivalent to Java.

A root mapping rule must be created to convert  Canvas es into  xml files.

Sni mek obrazovky 2018 12 18 v 12 56 04

Sni mek obrazovky 2018 12 18 v 12 56 16

The template named  map_Canvas gets created.

Sni mek obrazovky 2018 12 18 v 12 56 34

Xml code must be typed into the template in order to create the required code:

Sni mek obrazovky 2018 12 18 v 12 56 44

Sni mek obrazovky 2018 12 18 v 12 57 01

In order to insert the  name attribute, type "space" followed by "name=":

Sni mek obrazovky 2018 12 18 v 12 57 15

A property macro must be set on the contents of the  name attribute value:

Sni mek obrazovky 2018 12 18 v 12 57 28

Sni mek obrazovky 2018 12 18 v 12 57 48

Some more xml needs to be inserted:

Sni mek obrazovky 2018 12 18 v 12 58 08

We create a placeholder for all the shapes:

Sni mek obrazovky 2018 12 18 v 12 58 25

With  Ctrl+Up select the placeholder xml element:

Sni mek obrazovky 2018 12 18 v 12 58 42

Then insert a COPY_SRCL macro to loop through all the shapes of a  Canvas and trigger their reduction rules:

Sni mek obrazovky 2018 12 18 v 12 59 28

Sni mek obrazovky 2018 12 18 v 12 59 40

The  name of the generated xml file can also be customized using a  property macro.

Sni mek obrazovky 2018 12 18 v 14 40 49

Sni mek obrazovky 2018 12 18 v 14 40 57

Reduction rules for  Square and  Circle now need to be created:

Sni mek obrazovky 2018 12 18 v 12 59 57

Sni mek obrazovky 2018 12 18 v 13 00 20

The templates for Circle and  Square must hold an  XmlElement as their root:

Sni mek obrazovky 2018 12 18 v 13 01 32

Then the xml template must be fully constructed:

Sni mek obrazovky 2018 12 18 v 13 02 04

template fragment must be created around the whole xml code:

Sni mek obrazovky 2018 12 18 v 13 02 48

Sni mek obrazovky 2018 12 18 v 13 03 07

The  TF symbols indicate the  template fragment:

Sni mek obrazovky 2018 12 18 v 13 03 16

Property macros should be used to parametrize the template:

Sni mek obrazovky 2018 12 18 v 13 03 27

The property macro must be further specified in the  Inspector so that the correct value is used and converted from  integer to  string:

Sni mek obrazovky 2018 12 18 v 13 04 02

The same must be repeated for the  y xml attribute.

For radius, we will use a dedicated xml element:

Sni mek obrazovky 2018 12 18 v 13 05 08

Sni mek obrazovky 2018 12 18 v 13 05 25

And quire similarly for the  color  reference:

Sni mek obrazovky 2018 12 18 v 13 07 31

The template for  Square  will be very similar:

Sni mek obrazovky 2018 12 18 v 13 09 27

The generator should be holding four root nodes now:

Sni mek obrazovky 2018 12 18 v 13 09 53

When you rebuild your language and preview the generated code for your sandbox, you should be getting an xml file similar to this:

Sni mek obrazovky 2018 12 18 v 13 10 22

What to do next

Congratulations! You've just completed your introductory tutorial into MPS.

Now you can continue on your own adding more shapes to the language. Point, Line, Triangle, Rectangle or shapes with colorful fills might be nice additions to our little language.

If you want to understand MPS more thoroughly, it might be a good time to try the in-depth  Calculator Tutorial, which explores many of the advanced concepts and will teach you much more about code generation, type-system and scoping.

Last modified: 23 March 2021