MPS projects consist of two main components: languages (possibly with generators) and plugins (tool windows, actions, etc.). To support them you would often also have tests and build scripts. Today I want to touch briefly on what goes where in MPS and where to put shared code.
Each language is defined in a language module with its structure, editor, constraints, and type system rules placed in corresponding aspects.
Aspects often use blocks of Java code for defining logic. Any code that needs to be shared between these blocks can be extracted into utility classes and methods1. These can be put right in the aspect model beside all the domain-specific root nodes.
If you need to share code between two aspects of the same language, create a custom utility model in the language.
This is any model whose name does not match any built-in aspect. My first choice is often
Another place where to put code is the behavior aspect of the language. This is where you define polymorphic methods on
nodes, whose implementation depends on the concept. It can be used to house utility code as well if you don’t want to
create a separate
MPS will create a generator for your language by default. You may delete it if you don’t intend to use it.
If you extract code from the body of a generator macro into a utility class, do not put the class into the generator
model. The class has to go into a separate model that does not have the
@generator stereotype, otherwise it will be
treated as a generator template and will be unavailable at run time.
If the generator produces Java code and that Java code needs extra classes to run, these classes are called the language runtime. They must be put into a separate solution and the solution must be registered as a runtime solution of the language.
(Runtime solutions are property of the language, not the generator. I think this is wrong but that’s the way it is.)
The relationship between languages and runtimes is many-to-many: a language can have multiple runtime solutions, a solution can serve as a runtime for multiple languages.
For plugins there are two options. The most common option is to use a plugin solution. This is a solution whose
solution kind is set to any value other than None. The plugins must be put into a model named
[solution name].plugin, so if your solution is called
myPluginSolution, the plugins go into a model named
myPluginSolution.plugin. MPS offers a wizard to create a new plugin solution.
Another option is to put plugins into the plugin aspect of a language. I don’t use this option very often because I prefer to keep my languages free of dependencies on infrastructure libraries such as the IDEA platform or Swing classes.
Tests and build scripts
Tests go into a separate solution, into models with
@tests stereotype. As usual, the test models can also define any
supporting Java classes directly.
Build scripts are also placed in a separate solution. A single model in that solution may contain multiple build scripts. For example, one build script for building all the modules and another one for packaging the built modules into an RCP.
If you produce multiple libraries from one MPS project (an advanced use case), you probably want each library to have a separate build solution, don’t put multiple build scripts into the same model or even the same solution in that case.
It is best practice to define at least one devkit for the users of your tool. First, it makes it easy for them to add all the necessary languages to a new model in one go. Second, it makes it easy for you to change the set of languages later on. You can add a new language or remove an existing language, update the devkit, and your users will not have to adjust the languages they use in their models.
Sharing code between modules
Code that needs to be called by multiple modules (languages or solutions) can go into a separate ordinary solution, added as a dependency to the calling modules. This works for any kind of module (language, plugin, test, runtime solution, etc. etc.)
Languages in MPS can extend other languages, this lets you split your language into “core” and “extra” parts, for example.
Did I miss anything?
Did I forget to mention an important component? Do you disagree with something I wrote? Hit Reply and let me know!
Another candidate for extraction is code that has grown so large that it became inconvenient to keep in one block or edit in the inspector. ↩︎