MPS Project Structure
When designing languages and writing code, good structure helps you navigate around and combine the pieces together. MPS is similar to other IDEs in this regard.
Project is the main organizational unit in MPS. Projects consist of one or more modules, which themselves consist of models. Model is the smallest unit for generation/compilation. We describe these concepts in detail right below.
Here's a major difference that MPS brings along - programs are not in text form. Ever.
You might be used to the fact that any programming is done in text. You edit text. The text is than parsed by a parser to build an AST. Grammars are typically used to define parsers. AST is then used as the core data structure to work with your program further, either by the compiler to generate runnable code or by an IDE to give you clever code assistance, refactorings and static code analysis.
Now, seeing that AST is such a useful, flexible and powerful data structure, how would it help if we could work with AST from the very beginning, avoiding text, grammar and parsers altogether? Well, this is exactly what MPS does.
Programs in MPS are stored inside models. Models are similar to compilation units of text based languages. For example, in MPS' counterpart of Java - baseLanguage - each model represents a Java package. Each model consists of root nodes which are similar to top level declarations in text base languages. For example, in baseLanguage, root nodes are classes, interfaces, and enums. (You can read more about nodes here ). Also models store meta information about themselves:
- languages they are written in (in used languages section);
- models they use (imported models);
- devkits they use (devkits section).
To organize models as higher level entities, we have a module concept. A module consists of a set of models plus meta information that describes its properties and dependencies. MPS operates with several kinds of modules: solutions, languages, devkits, and generators.
Module meta information
To describe relations between modules, we store meta information inside them. Here are the kinds of relations in which modules can participate:
- Dependency - if one module depends on another, models inside the former can import models from the latter. This relation is parameterized by reexport property. If module A depends on module B with reexport property set to true, every module that depends on A automatically has B in its dependencies list.
- Used language - if module A uses language L, then models inside A can use language L.
- Used devkit - if module A uses devkit D, then models inside A can use devkit D.
- Extended language - if language L extends language M, then every concept from M can be used inside L as a target of a role or extended concept. Also, all the aspects from language M are available for use and extension from the corresponding aspects in language L.
- Generator output path - generator output path is a folder where newly generated files will be put.
Solution is the simplest possible kind of module in MPS. It is just a set of models with a name.
Language is a module that is more complex than a solution. It consists of aspect models: structure, editor, actions, typesystem, etc. Languages can extend other languages, which means they can use concepts from an extended language. They can derive concepts from an extended language, use them as a target of a reference, and store them as children.
Since usually we generate language in some code and this code requires some support classes, languages can have runtime classpath and runtime solutions. Runtime classpath just makes available classes from it as java_stub to language's generators. Runtime solutions' models are visible to models inside the generator. Using runtime classpath is recommended when your runtime is written in java and is quite simple. In more complex situations, it is recommended to create a runtime solution.
Language aspects describe different facets of a language. The only mandatory aspect is structure. Without it language is meaningless. Here are the available aspects:
- structure - describes the language AST
- editor - describes how a language will be presented in editor
- actions - describes the completion menu customizations specific to a language
- constraints - describes the constraints on AST: where a node is applicable, which property and reference are allowed to it etc.
- behavior - describes the behavioral aspect of AST, i.e. AST methods
- typesystem - describes the rules for calculating types in a language
- intentions - describes intentions (context dependent actions available when light bulb pops up)
- plugin - allows a language to integrate into MPS IDE
- data flow - describes data flow aspect of a language. It allows you to find unreachable statements, uninitialized reads etc.
- scripts - ???
You can read more about each aspect in the corresponding section of this guide.
Generators describe transformations of a language. They can depend on other generators and have generator constraints which describe in which order transformations should be applied. You can read more about generation in the corresponding section.
When you have a large set of interconnected languages, you want a way to import them without listing all of these languages in the used languages section of a model. Devkits make this possible. In devkit, you list languages which will be automatically imported on devkit addition. Devkits can extend other devkits, in which case all languages from these devkits will be available in models used by extended devkit.
Project is just a set of modules with which you work.
Since we often generate MPS models to java files, we have to compile them. There are two options for this task: compiling classes in MPS (recommended) or compiling them in IntelliJ IDEA (requires IntelliJ IDEA). When you compile your classes in MPS, you have to set the module's source path. These source files will be compiled on module generation, or when you invoke compilation manually with make or rebuild action.