How to Start Programming with MPS
In this tutorial we will use the Meta Programming System (MPS) to create "The Simplest" Language that you can use to create various kinds of The Simplest Language programs. For example, a simple text program that writes to Java's System.out console, or a JFC Swing "The Simplest Language" program that displays a message box on the screen, or it could be something more complicated. The first thing we will do is to describe a language and a corresponding editor for writing "The Simplest Language" program specifications. After that, we will define the simplest possible generator for the language, which will create "The Simplest Language" programs that write to System.out.
As you will see, this is not the easiest The Simplest Language tutorial in existence. There are several steps that must be performed, some of which are far from intuitive. And in the end, you don't actually get the text, you get a program which, if compiled and run, would output the text. If that seems complicated, please read Sergey Dmitriev's article about Language Oriented Programming, and also Martin Fowler's article about Language Workbenches. The purpose of this tutorial is to show how MPS can be used to accomplish Language Oriented Programming, in the simplest example possible.
Note: The examples in this tutorial are based on Windows. You can also use MPS on Mac and Linux machines.
Using MPS for this tutorial requires installing a plugin for IntelliJ IDEA, which is a popular Java IDE created by JetBrains. This plugin only works with the latest Early Access Program (EAP) version of IntelliJ IDEA, which is code-named Irida. Download and install Irida from here (requires free registration). The MPS integration plugin will be installed when you install MPS in the next step.
Download and install the latest version of MPS from here.
Note: MPS is under constant development at this time. The screenshots you see in this tutorial are likely to be different than the latest version of MPS. If you have any problems, bug reports, suggestions, or comments, please feel free to participate in the discussion forums.
Start MPS. The first time MPS is run, it will ask you if you would like to install the MPS plugin for IntelliJ IDEA.
We will be using this plugin in this tutorial, so press 'Yes' and choose the path to the IntelliJ IDEA installation directory. You must shut down IntelliJ IDEA before installing the plugin.
Note: MPS requires IntelliJ IDEA to be running simultaneously to perform various Java-related tasks. Once the plugin has been successfully installed, start up IntelliJ IDEA. You need to be running IntelliJ IDEA before proceeding with the next step.
After the plugin is installed, MPS starts up with its main window. We will immediately begin by creating a new project using the File > New Project... menu.
Enter a name and a path for the project, leaving all the checkboxes selected, and then press OK. For example, we used "TheSimplestLanguage" for the project name and chose a similar path on the file system.
Since you leave checkboxes selected, MPS will try to create a new solution and a new language for your project.
The New Language dialog appears. Give the language a suitable namespace (e.g. TheSimplestLanguage) in the Language Namespace box. The Language Path automatically adjusts to match the namespace. In the Language Options section, ensure that Editor is selected, and then press OK. It will ask you to create a new directory. Click Yes.
The New Solution dialog appears. Give the solution a name, such as 'main'.
The solution 'main' is newly created. Now we should add languages used by the solution. Right-click the solution in the project explorer and select Solution Properties in the popup menu. The Solution Properties dialog appears. Add language root by pressing 'Enter' on the cell for language roots. Use either Ctrl-Space or the button with an ellipsis to select a directory with the language. In our case, it will be "C:\TheSimplestLanguage\languages\TheSimplestLanguage". Click OK.
The language namespace and a solution namespace are created as folders under the Project folder. Expand these folders to examine the default contents.
Languages are composed of concepts. Our HelloWorld language will be very simple. It will have a single concept which specifies the only thing that can change in a Hello World program, which is the text to display. Create a new concept for our language by right-clicking the Structure folder and selecting Create Root Node > jetbrains.mps.bootstrap.structureLanguage > ConceptDeclaration.
A new, unnamed concept is created in the editor panel. Click in the field next to 'concept declaration' and give the concept the name TextConcept. In the 'extends' field, make the concept inherit from the BaseConcept concept pressing Ctrl + Space and selecting BaseConcept from the list (there is only one item in the list). Press Enter to select it. This is a simple example of code-completion. Use the same technique to change the 'is root' field from false to true.
Next we will add a property to the concept. Click on the empty 'properties' list, which looks like <<...>>, and add a new item to the list by pressing Ctrl-Enter. Give the property the name myText, and use code-completion (Ctrl-Space again) to select the 'string' type for the property. Your editor should look something like this:
Our TextConcept concept is now defined. Next, let's create an editor for our concept. Right-click the editor model. Select Create Root Node > jetbrains.mps.bootstrap.editorLanguage > ConceptEditorDeclaration.
Use code-completion to set the 'editor for concept' field to TextConcept. Next, we want to set the layout for the editor. Again, use code-completion to select 'collection (horizontal)' under the 'Node cell layout' section. The horizontal collection is initially empty, so press Ctrl-Enter to add an item to it. Use code-completion (you get the idea, right?) to select the 'constant' cell model for this item. A constant cell simply contains some text, so set the text to 'my text = '. Add a second cell to this horizontal collection by pressing Ctrl-Enter again. This time, choose 'property' for the cell model. Enter the name of the property you want to reference, which is 'myText' in this case. You can type it, or again use code-completion. The final editor should look like this:
At this point, our "The Simplest" language contains a concept called TextConcept and an editor for that concept (automatically named TextConcept_Editor). Next we will generate some Java code from our language definitions to give these concepts some concrete meaning. Right-click the language in the project explorer and select Generate Language.
MPS goes through the process of generating code in IntelliJ IDEA. When it is complete, you can check out the code that was generated by going to the IntelliJ IDEA window. Open the TextConcept.java file which is located in the source_gen folder.
That's great, but how do we know it works? Essentially, we have a language, but no programs are written in it yet. Let's put our language to the test by writing a program with it. MPS uses the more-generic term 'model' instead of 'program'. Models are grouped into modules, so-called 'solutions'. You can consider any 'solution' model as essentially a program. So let's create a new model. Right-click the solution in the project explorer and select New Model.
The New Model dialog appears. Give the model a name, such as 'test'. In the 'Root' field, use code-completion to select the model root. Use Ctrl-Enter to add an item to the empty 'Languages' list, and code-completion again to add TheSimplestLanguage to the list of imported languages. Click OK to create the model.
Our model starts empty, so we'll create a new concept for it. This should start to look familiar now: Right-click the test model, and select Create Root Node > TheSimplestLanguage > TextConcept. This is creating a new instance of the TextConcept concept from the language TheSimplestLanguage.
In the editor, you will see an instance of the TextConcept concept editor that we created as part of our language. Set the 'myText' to some text, such as "some text"
We've just created a simplest program/model written in our TheSimplestLanguage by using the TextConcept editor. Now how do we get the model to do something? We have to give meaning to our TheSimplestLanguage models, by specifying their concrete semantics/behavior. We will do this by creating a generator for the TheSimplestLanguage language, which will take models written in TheSimplestLanguage and generate Java code to print the given myText to the System.out console. Essentially, the generator defines the meaning (or semantics) of the model by specifying how the model becomes real code.
To create a new generator, right-click on "generators" child node of our language and press New Generator.
In the 'Target language' field, choose jetbrains.mps.baseLanguage. In the 'Templates model' field, choose 'new template model'. It asks for the template model suffix. Enter 'baseLanguage' and press OK.
Expand the new folder labeled with text "generator -> jetbrains.mps.baseLanguage" to see the templates model "TheSimplestLanguage.generator.baseLanguage@templates".
The generators in MPS use templates, which are like parameterized code fragments, to transform models from one language to another target language. For example, we will write a template that converts TheSimplestLanguage models into Base Language models (which will in turn generate true Java code). The generator uses a 'template mapping configuration' to help it decide which templates should be applied to which concepts. Together, the templates and the template mapping configuration enable the generator to automatically transform one language into another.
The first thing we will do is to create a simple template mapping configuration. Select it under template model node.
Set the name of the new MappingConfiguration to HelloWorldGenerator. Add a mapping rule to the mapping rules list. MPS currently uses what are called 'query methods' to select which nodes of the model should be transformed. Query methods are currently written in Java, until such a time when they will be able to be written directly in MPS using a query language. Select 'New query method' in the 'for each' field of the mapping rule. Use 'AllTextConcepts' for the query method ID.
IntelliJ IDEA will be opened, and the new query method will be shown. Initially it is empty. We need to write some Java code to implement the query method. Use IntelliJ IDEA to write the method shown in the screenshot below, or copy and paste the code from here:
Now we will use the MPS Base Language, which is a Java-like language, to write the concrete code for each TextConcept instance in our test model. Each TextConcept instance will be mapped to a class in the Base Language.
Create a new Base Language class in the templates model.
Give the new class the name 'TextClass'. (Note: Since we only have one instance of the TextConcept concept, it is safe to hard-code the class name. If we had multiple instances, we would have to use a macro to generate unique class names.)
Give the class a static method with void type and the name 'main'.
Add "String args" as a parameter for the 'main' method by typing 'String' in the cell between the method's parenthesis, pressing Ctrl-Space to choose 'String' and typing 'args'.
Inside the 'main' method, we need to add the code which writes value of TextConcept instance property 'myText' to System.out. Click on the empty statement list. Press Ctrl-Space to see the list of possible statements. Select 'System.out'. With the cursor at the end of the word 'out', press '.' . The placeholder for method appears. Press Ctrl-Space on it and select 'println(String)' from the list of methods. Inside the method's parentheses, type some sample constant String like "Text". The final result should look like this:
We will turn this sample text into a macro by pressing Ctrl-Shift-M. This creates a property macro.
Our property macro also needs another query method to render the text of the macro. First, select the property macro cell by clicking on the '$' symbol. In the Inspector panel, add a 'New query method' to the propertyMacro field.
Use the name 'MyText' for the query ID. This creates a method in IntelliJ IDEA.
Back in MPS, we want to wire up our new template to the mapping rule that we had created previously. Go back to the MappingConfiguration and set the template in the mapping rule to the freshly written TextClass.
Give the rule itself a name, such as 'textRule' (type it in the place of <no rule name> placeholder).
Go to IntelliJ IDEA and add code to the MyText query method to retrieve the myText from the TextConcept instance called 'sourceNode'. You can enter it yourself in IntelliJ IDEA, or cut and paste this code:
We are almost done! At this point, we have created a generator – combined with some supporting Java code – which can take models written in the TheSimplestLanguage and transform them into models written in the Base Language. The Base Language is Java-like, and in fact maps very closely into Java. Once a model is transformed into the Base Language, the generator can automatically convert it into true Java code. Our final step is to configure our project in MPS to actually perform the generation. You might consider this last step analogous to creating a Run/Debug configuration in IntelliJ IDEA, but instead of running code, it generates code written in Java.
Right-click on the Project in the project explorer and select Project Properties. The Project Properties dialog appears.
Add a new generator configuration to the 'Generator configurations' list. Set the name to something like 'java', and set the output path to somewhere on your file system where you would like any generated files to be saved.
Add a new command to the 'Commands' list. For the source, choose TheSimplestLanguage, and for the target, choose jetbrains.mps.baseLanguage. Click OK to close the dialog.
Finally, we can generate our Java code. Right-click the test model and select Generate Text From Model > java.
The result should look something like this:
Note: This example doesn't actually generate Java files. To do that, use Generate From Model instead of Generate Text From Model in the last step. The file(s) will be generated in the output folder you chose.
Congratulations! You have just experienced Language Oriented Programming. We created a language definition, which included an editor for the concepts in the language. Then we created a program written in our TheSimplestLanguage. And finally, we created a generator to transform the program into actual Java code. The language definition and editor could be reused to write other programs. The generator could be modified to handle more complex models (such as multiple TextConcept instances), and you could even write new generators to transform the TheSimplestLanguage programs into different forms, such as a Java Swing application.
This is just the beginning of the journey. MPS is undergoing constant development, and could benefit greatly from any feedback you might have. You may want to join the discussion