MPS 2023.3 Help

Custom language aspect cookbook

Alongside the usual language aspects, such as StructureEditorType-system, etc., it is possible for language authors to create custom language aspects (e.g. interpreter, alternative type-system, etc.), have them generated into a language runtime and then use these generated aspects from code.

What is a custom aspect?

Language definitions in MPS can be thought of as a collection of aspects: structure, editor, typesystem, generator. Each of the aspects consists of declarations used by the corresponding aspect subsystem. For example, the type-system aspect consists of type-system rules and is used by the type-system engine.

Each aspect of a language is defined in a separate aspect model. For example, the editor aspect of language L is defined in the L.editor model.

Each aspect is described using a set of aspect's main languages. E.g. there's the j.m.lang.editor language to describe the editor aspect.

Declarations in an aspect model may or may not be bound to some concept (like an editor of the concept is bound to a concept, mapping configuration in the generator aspect is not bound).

The aspect can be generated into a language's aspect runtime, which represents this aspect at runtime, in other words, when the language is used.

Since version 3.3, MPS allows language authors to define new aspects for their languages.

Development cycle of custom aspects

  1. Create a language to describe the aspect - you may reuse existing languages or create ones specific to the needs of the aspect. For example, each of the core MPS aspects uses its own set of languages, plus a few common ones, such as BaseLanguage or smodel.

  2. Declare that this language (possibly with the help of some others) can be used to define some aspect of other languages' definitions - an appropriate aspect descriptor must be created in the language.

  3. Develop a generator in the created language to generate aspect's runtime classes (if needed).

  4. Develop the aspect subsystem that uses the aspect's runtime.

We'll further go through each step in detail.

Look around the sample project

If you open the customAspect sample project, you will get six modules.

CustomAspectx1.png

The documentation language and its runtime solution are the key building blocks used to define the new documentation aspects for other language. The aspect subsystem is represented by the pluginSolution, which defines an action that shows the documentation for the concept of the currently focused node. The  sampleLanguage solution is an example of a language that utilizes this new aspect - it uses it to document its own concepts. The  sandbox solution shows the sampleLanguage's usage and allows the user to view the documentation as defined by the documentation aspect. The build solution defines a build script to package the sample project as an MPS plugin.

Language runtime

Before we move on, let's consider for a second how language aspects work at runtime.

  • Language descriptor - For each language in MPS, a language descriptor is generated by MPS (the class is called Language.java).

  • Aspect interface - Each aspect defines an API for its runtime as a set of interfaces. It must implement a marker interface ILanguageAspect.

  • Language.createAspect() - Let's focus on the createAspect() method of the language descriptor here. Given an aspect interface as a parameter, the method returns a concrete implementation of this aspect for this language (let's call it AspectDescriptor).

  • Aspect descriptor - The AspectDescriptor is an arbitrary class implementing the Aspect interface. We suggest that an AspectDescriptor contains no code except getters for entities used described by this aspect.

This is how a typical language runtime looks like:

CustomAspectx3.png

The createAspect() method checks the type of the parameter expecting one of interfaces declared in aspects and returns a corresponding newly instantiated implementation.

This is how the interfaces defined in aspects may look like (this example is defined in the Intentions aspect):

CustomAspectx2.png

Using the language aspects

Now, let's suppose we want to use some of the aspects from code. For example, while working with the editor, we'd like to acquire a list of intentions, which could be applied to the currently selected node.

  1. We first find all the language runtimes corresponding to the languages imported.

  2. then get the intentions descriptors for each of them.

  3. and finally get all the intentions from the descriptors and check them for applicability to the current node.

The overall scheme is: Languages->LanguageRuntimes->Required aspect->Get what you want from this aspect

So your custom aspect need to hook into this discovery mechanism so that the callers can get hold of it.

Implementing a custom aspect

Let's look in detail into the steps necessary to implement your custom aspect using the customAspect sample project:

  1. To make MPS treat some special model as a documentation aspect (that is our new custom aspect), an aspect declaration should be created in the documentation language. To do so, we create a plugin aspect in the language and import the jetbrains.mps.lang.aspect language.

    CustomAspectx4.png
  2. Create an aspect declaration in the plugin model of the language and fill in its fields. This tells MPS that this language can be used to implement a new custom aspect for other languages. 

    CustomAspectx5.png
  3. After making/rebuilding the documentation language, it's already possible to create a documentation aspect in the sample language and create a doc concept in it.

    CustomAspectx6.png
  4. Instances of the ConceptDocumentation concept can be created in the documentation aspect

    CustomAspectx7.png
  5. Now, we should move to the language runtime in order to specify the functionality of the new aspect as it should work inside MPS. In our example, let's create an interface that is able to retrieve and return the documentation for a chosen concept. To do so, we create a runtime solution, add it as a runtime module of our documentation language and create an interface in it. Note that the interface must extend the ILanguageAspect interface. Our interface here needs only a single method, that takes a concept as a parameter and returns a string with the corresponding documentation text.

    CustomAspectx8.png
  6. In the generator for the documentation language we then need to provide an implementation of the interface. A conditional root rule and the following template will do the trick and generate the documentation descriptor class:

    CustomAspectx9.png

    The condition ensures that the rule only triggers for the models of your custom aspect, i.e. in our case models that hold the documentation definitions (jetbrains.mps.samples.customAspect.sampleLanguage.documentation ).The useful feature here is the concept switch construction, which allows you to ignore the concept implementation details. It simply loops through all documented concepts (the LOOP macro) and for each such concept creates a matching case (exactly ->$[ConceptDocumentation]) that returns a string value obtained from the associated ConceptDocumentation.

  7. So we have an interface and an implementation class. Now, we need to tie them together - we have to generate the part of the createAspect method of the Language runtime class, which will instantiate our concept, i.e. whenever the documentation aspect is required (by passing the DocumentationAspectDescriptor interface), it will return the generated DocumentationDescriptor class.


    To understand how the following works, look at how the class Language.java is generated (see Language class in model j.m.lang.descriptor.generator.template.main). The descriptor instantiation is done by a template switch called AspectDescriptor_Instantiate in the jetbrains.mps.lang.descriptor class, which we have to extend in our new aspect language so that it works with one more aspect model:

    CustomAspectx10.png

    Essentially, we're adding a check for the  DocumentationAspectDescriptor interface to the generated Language class and return a fresh instance of the DocumentationDescriptor, if the requested aspectClass is our custom aspect interface.


  8. The only thing left is using our new aspect. For that purpose, an action needs to be created that will show documentation for a concept of a node under cursor on demand:

    clx202.png

    The model must be imported in order to be able to specify the context parameters. The action must be created as part of a (newly created) plugin solution (more on plugin solutions at Plugin) with a StandalonePluginDescriptor and hooked into the menu through an ActionGroupDeclaration:

    clx201.png
  9. clx200.png

    This way the IDE Code menu will be enhanced.

  10. Let's now try it out! Rebuild the project, create or open a node of the DocumentedConcept concept in the sandbox solution and invoke the Show Documentation action from the Code menu:

    Screen-Shot-2015-09-10-at-04-22-19.png
Last modified: 07 March 2024