MPS 2023.3 Help

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

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.

Models

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 coding 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.

To give your code some structure, programs in MPS are organized into models. Think of models as somewhat similar to compilation units in text based languages. To give you an example, BaseLanguage, the bottom-line language in MPS, which builds on Java and extends it in many ways, uses models so that each model represents a Java package. Models typically consist of root nodes, which represent top level declarations, and non-root nodes. For example, in BaseLanguage classes, interfaces, and enums are root nodes. (You can read more about nodes here).

Models need to hold their meta information:

  • models they use (imported models)

  • languages (and also devkits) they are written in (in used languages section)

  • a few extra params, such as the model file and special generator parameters

This meta information can be altered in Model Properties of the model's popup menu or using Alt+Enter when positioned on the model.

Modules

Models themselves are the most fine-grained grouping elements. Modules organize models into higher level entities. A module typically consists of several models acompanied with meta information describing module's properties and dependencies. MPS distinguishes several types of modules: solutions, languages, devkits, and generators.

We'll now talk about the meta-information structure as well as the individual module types in detail.

Module meta information

Now when we have stuff organized into modules, we need a way to combine the modules together for better good. Relationships between modules are described through meta information they hold. The possible relationships among modules can be categorized into several groups:

  • Dependency - if one module depends on another, and so models inside the former can import models from the latter. The reexport property of the dependency relationship indicates whether the dependency is transitive or not. If module A depends on module B with the reexport property set to true, every other module that declares depency on A automatically depends on B as well.

  • Extended language dependency - if language L extends language M, then every concept from M can be used inside L as a target of a role or an extended concept. Also, all the aspects from language M are available for use and extension in the corresponding aspects of language L.

  • Generation Target dependency - a relation between two languages (L2 and L1), when one needs to specify that Generator of L2 generates into L1 and thus needs L1's runtime dependencies.

  • 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.

  • Generator output path - generator output path is a folder where all newly generated files will be placed. This is the place you can look for the stuff MPS generates for you.

  • Facets - Facets specify additional technologies or settings that should be available for the module. Facets associated with a module are recorded inside the module descriptor file, which is thus the ultimate source of facet configuration information. All facets, including the Tests and Java ones are completely optional.

    The Tests facet ensures that the generated code will be placed in a directory named test_gen instead of the source_gen location used for ordinary code.

    Beware that unchecking the Java module facet in the Language module properties would exclude the language from the classloading mechanism and thus result in completely different experience.

Now we'll look at the different types of modules you can find in MPS.

Solutions

Solution is the simplest possible kind of module in MPS. It is just a set of models holding code and unified under a common name. There are four types of solutions:

  • Ordinary solutions - these solutions hold end user code. The IDE typically generates the code on demand and compiles any generated Java code. The compiled code is not loaded into the IDE, but it can be run on demand in a separate Java process.

  • Sandbox solutions - these solutions are mostly used by language designers during the process of language design to play with the code in order to interactively evolve their languages. The IDE typically generates the code, but does not attempt to compile the generated code.

  • Runtime solutions - these solutions contain code that other modules (Solutions, Languages or Generators) depend on. The code can consist of MPS models as well as of Java classes, sources or jar files. The IDE will generate and compile the code and reload the classes, whenever they get compiled or changed externally.

  • Plugin solutions - these solutions extend the IDE functionality in some way. They can contribute new menu entries, add side tool panel windows, define custom preference screens from the Project settings dialog, etc. Again, MPS will generate and compile the code and keep reloading the classes, whenever they change. Additionally, the IDE functionality will be updated accordingly.

Languages

Language is a module that is more complex than a solution and represents a reusable language. It consists of several models, each defining a certain aspect of the language: structure, editor, actions, typesysteand so on, and so on

Languages can extend other languages. An extending language can then use all concepts from the extended language - derive its own concepts, use inherited concepts as targets for references and also place inherited concepts directly as children inside its own concepts.

Languages frequently have runtime dependencies on third-party libraries or solutions. You may, for example, create a language wrapping any Java library, such as Hibernate or Swt. Your language will then give the users a better and smoother alternative to the standard Java API that these libraries come with.

Now, for your language to work, you need to include the wrapped library with your language. You do it either through a runtime classpath or through a runtime solution. Runtime classpath is suitable for typical scenarios, such as Java-written libraries, while runtime solutions should be prefered for more complex scenarios.

  • Runtime classpath - makes library classes available as stubs language generators

  • Runtime solutions - these models are visible to all models inside the generator

Language aspects

Language aspects describe different facets of a language:

  • structure - describes the nodes and structure of the language AST. This is the only mandatory aspect of any language.

  • editor - describes how a language will be presented and edited in the editor

  • actions - describes the completion menu customizations specific to a language, that is what happens when you type Control + Space

  • constraints - describes the constraints on AST: where a node is applicable, which property and reference are allowed, and so on

  • behavior - describes the behavioral aspect of AST, that is AST methods

  • typesystem - describes the rules for calculating types in a language

  • intentions - describes intentions (context dependent actions available when light bulb pops up or when the user typesAlt+Enter)

  • plugin - allows a language to integrate into MPS IDE

  • data flow - describes the intended flow of data in code. It allows you to find unreachable statements, uninitialized reads and so on

You can read more about each aspect in the corresponding section of this guide.

Generators

Generators define possible transformations of a language into something else, typically into another languages. Generators may depend on other generators. Since the order in which generators are applied to code is important, ordering constraints can be set on generators. You can read more about generation in the corresponding section.

DevKits

DevKits have been created to make your life easier. If you have a large group of interconnected languages, you certainly appreciate a way to treat them as a single unit. For example, you may want to import them without listing all of the individual languages. DevKits make this possible. When building a DevKit, you list languages to include.

As expected, DevKits can extend other DevKits. The extending DevKit will then carry along all the inherited languages as if they were its own ones.

Projects

This one is easy. A project wraps modules that you need to group together and work with them as a unit. You can open the Properties of a project (Alt+Enter on the Project node in the Project View panel) and add or remove modules that should be included in the project. You can also create new modules from the project nodes' context pop-up menu.

Java compilation

MPS was born from Java and is frequently used in Java environment. Since MPS models are often generated into java files, a way to compile java is needed before we can run our programs. There are generally two options:

  • Compiling in MPS (recommended)

  • Compiling in IntelliJ IDEA (requires IntelliJ IDEA)

When you compile your classes in MPS, you have to set the module's source path. The source files will be compiled each time the module gets generated, or whenever you invoke compilation manually by the make or rebuild actions.

Last modified: 07 March 2024