MPS 2021.3 Help

Structure

Since MPS frees you from defining a grammar for your intented languages, you obviously need different ways to specify the structure of your languages. This is where the Structure Language comes in handy. It gives you all the means to define the language structure. As we discussed earlier, when coding in MPS you're effectively building the AST directly, so the structure of your language needs to specify the elements, the bricks, you use to build the AST.

The bricks are called Concepts and the Structure Language exposes concepts and concept interfaces as well as their members: properties, references, and children.

Concept1

Concepts and Concept Interfaces

Now let's look at those in more detail. A Concept defines the structure of a concept instance, a node of the future AST representing code written using your language. The Concept says which properties the nodes might contain, which nodes may be referred to, and what children nodes are allowed (for more information about nodes see the Basic notions section).

Apart from Concepts, there are also Concept Interfaces. Concept interfaces represent independent traits, which can be inherited and implemented by many different concepts. You typically use them to bring orthogonal concepts together in a single concept. For example, if your Concept instance has a name by which it can be identified, you can implement the INamedConcept interface in your Concept and you get the name property plus associated behavior and constraints added to your Concept.

Concepts inheritance

Just like in OO programming, a Concept can extend another Concept, and implement many Concept Interfaces. A Concept Interface can extend multiple other Concept Interfaces. This system is similar to Java classes, where a class can have only one super-class but many implemented interfaces, and where interfaces may extend many other interfaces.

If a concept extends another concept or implements a concept interface, it transitively inherits all members (i.e if A has member m, A is extended by B and B is extended by C, then C also has the member m)

Concept interfaces with special meaning

There are several concept interfaces in MPS that have a special meaning or behavior when implemented by your concepts. Here's a list of the most useful ones:

Concept Interface

Meaning

IDeprecatable

Used if instances of your concept can be deprecated. It's isDeprecated behavior method indicates whether or not the node is deprecated. The editor sets a strikeout style for reference cells if isDeprecated of the target returns true.

INamedConcept

Used if instances of your concept have an identifying name. By default, this name represents nodes as possible targets in the code completion list for references. Additionally, the name is shown in the Project View.

INamedValidIdentifier

Used if instances of your concept have an identifying name. The interface extends INamedConcept and adds constraints that guarantee a valid Java identifier to be used as the name. Belongs to BaseLanguage and should only be utilized in BaseLanguage extensions.

INamedAspect

Used for named concepts that are part of some aspect of a language definition. The interface ensures valid Java identifiers to be used.

IType

Is used to mark all concepts representing types

IWrapper

Deleting a node whose immediate parent is an instance of IWrapper deletes the parent node as well.

Concept members

Properties

Property is a value stored inside a concept instance. Each property must have a type, which for properties is limited to: primitives, such as boolean, string and integer; enumerations, which can have a value from a predefined set; and constrained data types (strings constrained by a regular expression). You may define a getter and setter for a property in the Constraints of a concept.

References

Holding scalar values would not get as far. To increase expressiveness of our languages nodes are allowed to store references to other nodes. Each reference has a name, a type, and a cardinality. The type restricts the allowed type of a reference target. Cardinality defines how many references of this kind a node can have. References can only have two types of cardinalities: 1:0..1 and 1:1.

For references there is no direct way to traverse them in the reverse direction. The jetbrains.mps.lang.findUsages language and the execute finder should be used for that. (These do not work reliably during generation).

Smart references

A node containing a single reference of 1:1 cardinality and with no alias defined is called a smart reference. These are somewhat special references. Provided the language author has not specified an alias for them, they do their best to hide from the language user and be as transparent as possible. MPS treats the node as if it was a the actual reference itself, which simplifies code editing and code-completion. For example, default completion items are created whenever the completion menu is required: for each possible reference target, a menu item is created with matching text equal to the presentation of a target node.

In order to make a reference smart when it does not meet the above mentioned criteria for being treated as smart automatically, the concept declaration has to be annotated with the @smart reference attribute: A typical use-case would be a concept that customizes the presentation of the reference or holds additional references.

Smart ref1

Children

To compose nodes into trees, we need to allow children to be hooked up to them. Each child declaration holds a target concept, its role and cardinality. Target concept specifies the type of children. Role specifies the name for this group of children. Finally, cardinality specifies how many children from this group can be contained in a single node. There are 4 allowed types of cardinality: 1:1, 1:0..1, 1:0..n, and 1:1..n.

Programatically, node.children retrieves all nodes that are children to the current node. The reverse of node.children is node.parent or node.ancestor(s)

Specialized references and children

Sometimes, when one concept extends another, we not only want to inherit all of its members, but also want to override some of its traits. This is possible with children and references specialization. When you specialize a child or reference, you narrow its target typ and possibly also change its name. For example, if you have concept A which extends B, and have a reference r in concept C with target type B, you might narrow the type of reference r in C's subconcepts. It works the same way for concept's children.

Specialization1
You need to add the specializes: section to your link declaration and refer to the link from a super-concept that you are modifying.

Alias

The alias, referred to from code as conceptAlias, optionally specifies a string that will be recognized by MPS as a representation of the Concept. The alias will appear in completion boxes and MPS will instantiate the Concept, whenever the alias or a part of it is typed by the user. 

Constrained Data Types

Constrained Data Type allows you to define string-based types constrained with a regular expression. MPS will then make sure all property values with this constrained data type hold values that match the constraint.

Custom data type

Enumerations

Enumerations allow you to define properties that hold values from pre-defined sets.

Enumt1

Each enumeration member has an name and an optional presentation.

  • Name (left column) - this string value is used to refer programmatically to this member.

  • Presentation (right column) - this string value is used to represent the enum members in a user model (completion menu, editor). If no presentation is explicitly defined, the name is used instead.

Aside from a set of members, each enumeration can optionally define the default member. You can consider a default member as an implicit value that is present for all properties for which there is no other value is explicitly set.

Programmatic access

To access enumerations and their members programmatically, use the enum operations and the enummember type defined in the jetbrains.mps.lang.smodel language.

Enumt2

Enumeration members can be queried for their name and presentation. Checking a value of a property against an enum data type value can be done with the is operation.

Enumt3

Handy from name, from presentation and members operations on an enumeration give you access to the members.

Enumt4

An enum switch statement provides a convenient control structure - given an enumeration member, it’s easy to switch over to a particular member instances using the enum switch statement:

Enumt5

or the enum switch expression:

Enumt6

If some enumeration members are omitted and no otherwise branch is specified, a warning reporting a non-exhaustive switch is raised. Similarly, an error is raised when a single value occurs more than once in a switch.

Enumt7

Enumeration Data Types (deprecated, use Enumerations instead)

Enumeration Data Types allow you to use properties that hold values from pre-defined sets.

Enum1

Each enumeration data type member has a value and a presentation. Optionally an identifier can be specified explicitly.

Presentation vs. Value vs. Identifier

  • Presentation -  this string value will be used to represent the enum members in the UI (completion menu, editor)

  • Value - this value, the type of which is set by the member type property, will represent the enum members in code

  • Identifier - this optional value will be used as the name of the generated Java enum. This value is typically derived from either the presentation or the value, since it is meant to be transparent to the language users and has no meaning in the language. It only needs to be specified when the id deriving process fails to generate unique valid identifiers.

  • Name - when accessing enum data type's members from code, name refers to either presentationvalue or identifier, depending on which option member identifier is active

Deriving identifiers automaticaly

When deriving identifiers from either presentation or values, MPS will make best efforts to eliminate characters that are not allowed in Java identifiers. If the derived identifiers for multiple enum data type members end up being identical, an error will be reported. Explicit identifiers should be specified in such cases.

Programmatic access

To access enumeration data types and their members programmatically, use the enum operations defined in the jetbrains.mps.lang.smodel language.

Enum32

Checking a value of a property against an enum data type value can be done with the is operation. To print out the presentation of the property value, you need to obtain the corresponding enum member first: 

Enum33

Attributes

Attributes, sometimes called Annotations, allow language designers to express orthogonal language constructs and apply them to existing languages without the need to modify them. For example, the generator templates allow for special generator marks, such as LOOP, ->$ and $[], to be embedded within the target language:

Generator1

The target language (BaseLanguage in our example here) does not need to know anything about the MPS generator, yet the generator macros can be added to the abstract model (AST) and edited in the editor. Similarly, anti-quotations and Patterns may get attributed to BaseLanguage concepts.

MPS provides three types of attributes:

  • LinkAttribute - to annotate references

  • NodeAttribute - to annotate individual nodes

  • PropertyAttribute - to annotate properties

By extending these you can introduce your own additions to existing languages. For good examples of attributes in use, check out the Description comments cookbook as well as the Requirement tracking language cookbook.

Last modified: 14 September 2021