- Basic notions
- MPS Project Structure
- Introduction
- Project
- Models
- Modules
- Projects
- Concepts and Concept Interfaces
- Concept members
- Annotation concepts
- Can be child/parent/ancestor/root
- Property constraints
- Referent constraints
- Default scope
- Concept instance methods
- Concept constructors
- Concept static methods
- SModel Language
- Treatment of null values
- Types
- Operation parameters
- Queries
- Features access
- Null checks
- IsInstanceOf check and type casts
- Parent
- Children
- Sibling queries
- Ancestors
- Descendants
- Containing root and model
- Model queries
- Search scope queries
- Concept literal
- Concept operation
- Concept hierarchy queries
- Is Role operation
- Containing link queries
- Reference operations
- Downcast to lower semantic level
- Modification operations
- Pattern Language
- Editor
- Editor Overview
- Types Of Cell Models
- Cell layouts
- Styles
- Style properties
- Boolean style properties
- Padding properties.
- Punctuation properties.
- Color style properties
- Indent layout properties
- Other style properties
- Cell actions
- Cell menus
- Actions
- Substitute Actions
- Side Transform Actions
- Node Factories
- Generator User Guide
- Introduction
- Overview
- Generator Language Reference
- Mapping Configuration
- Generator Rule
- Root Template
- External Template
- Mapping Label
- Macro
- Template Switch
- Generation Context (operations)
- Finding Output Node
- Generating of Unique Name
- Template Parameters
- Getting Contextual Info
- Transferring User Data
- Logging
- Utilities (Re-usable Code)
- Mapping Script
- The Generator Algorithm
- Mapping Priorities
- Examples
- Defining A Typesystem For Your Language
- What is a typesystem?
- Types
- Inference Rules
- Equations And Inequations
- Weak And Strong Subtyping
- Subtyping Rules
- Comparison Inequations And Comparison Rules
- Quotations
- Antiquotations
- Examples Of Inference Rules
- Type Variables
- Meet and Join types
- "When Concrete" Blocks
- Overloaded Operators
- Replacement Rules
- Advanced features of typesystem language
- Using a typesystem
- Data Flow
- Links:
- TextGen language aspect
- Introduction
- Operations
- Examples
- Actions and action groups
- Editor Tabs
- Tools
- Preferences components
- Custom plugin parts
- File Generators
- Generation Listeners
- Generate Plugins concept
- Run Configurations
- Introduction
- General purpose run configurations
- Run configurations for languages generated into java
- Using intentions
- Intention types
- Common Intention Structure
- name
- for concept
- available in child nodes
- child filter
- description
- isApplicable
- execute
- Regular Intentions
- Surround With - Intentions
- Generate - Intentions
- Where to store my intentions?
- Examples
- Using Find Usages Subsystem
- Finders
- Where to store my finders?
- Finder structure
- name
- for concept
- description
- long description
- is visible
- is applicable
- find
- searched nodes
- get category
- What does the MPS Find Usages subsystem do automatically?
- Specific Statements
- Examples
- Stubs Language
- Stub Creator
- name
- modelDescriptors
- updateModel
- rootNodeDescriptors
- Stub Solution
- module
- creator
- roots
- Usage
- Performance
- Debugger
- Changes highlighting
- Base Language
- Closures
- Introduction
- Function type
- Closure literal
- Closure invocation
- Closure conversion
- Yield statement
- Functions that return functions
- Runtime
- Differences from the BGGA proposal
- Collections Language
- Introduction
- Null handling
- Skip and stop statements
- Collections Runtime
- Sequence
- List
- Set
- Sorted Set
- Map
- Sorted Map
- Stack
- Queue
- Deque
- Iterator
- Modifying Iterator
- Enumerator
- Mapping
- Custom Containers
- Primitive Containers
- Tuples
- Dates language
- Regular expressions language
- GText language
- Unit Test Language
- Type Extension Methods
- XML Language
- Checked dots
- Overloaded operators
- Custom constructors (since 1.5.1)
- Language test language
- Build Languages
- Table of contents
- Build Language
- Packaging Language
- Custom MPS Language
- GWT Support
Basic notions
This chapter describes the basic MPS notions: nodes, concepts, and languages. These are key to proper understanding of how MPS works. They all only make sense when combined with the others and so we must talk about them all together. This section aims to give you the essence of each of the elements. For further details, you may consider checking out the sections devoted to nodes, concept (structure language), and languages (project structure).
Abstract Syntax Tree (AST)
MPS differentiates itself from many other language workbenches by avoiding the text form. Your programs are always represented by an AST. You edit the code as an AST, you save it as an AST you compile it as, well, as an AST. This allows you to avoid defining grammar and building a parser for your languages. You instead define your language in terms of types of AST nodes and rules for their mutual relationships. Almost everything you work with in the MPS editor is an AST-node, belonging to an Abstract Syntax Tree (AST). In this documentation we use a shorter name, node, for AST-node.
Node
Nodes form a tree. Each node has a parent node (except for root nodes), child nodes, properties, and references to other nodes.

The AST-nodes are organized into models. The nodes that don't have a parent, called root nodes. These are the top-most elements of a language. For example, in BaseLanguage (MPS' counterpart of Java), the root nodes are classes, interfaces, and enums.
Concept
Nodes can be very different from one another. Each node stores a reference to its declaration, its concept. A concept sets a "type" of connected nodes. It defines the class of nodes and coins the structure of nodes in that class. It specifies which children, properties, and references an instance of a node can have. Concept declarations form an inheritance hierarchy. If one concept extends another, it inherits all children, properties, and references from its parent.
Since everything in MPS revolves around AST, concept declarations are AST-nodes themselves. In fact, they are instances of a particular concept, ConceptDeclaration.

Language
Finally we get the language definition. A language in MPS is a set of concepts with some additional information. The additional information includes details on editors, completion menu, intentions, typesystem, generator, etc. associated with the language. This information forms several language aspects.
Obviously, a language can extend another language. An extending language can use any concepts defined in the extended language as types for its children or references, and its concepts can inherit from any concept of the extended language. You see, languages in MPS form fully reusable components.
MPS Project Structure
Introduction
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 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.
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 they are written in (in used languages section)
- devkits they use (devkits section)
This meta information can be altered in Model Properties of the model's pop-up 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 whenther 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.
- 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 an extended concept. Also, all the aspects from language M are available for use and extension in the corresponding aspects of language L.
- 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.
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 unified under a common name.
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, typesystem, etc.
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, i.e. what happens when you type Control + Space
- constraints - describes the constraints on AST: where a node is applicable, which property and reference are allowed, 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 or when the user types Alt + Enter)
- plugin - allows a language to integrate into MPS IDE
- data flow - describes the intented flow of data in code. It allows you to find unreachable statements, uninitialized reads etc.
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 simply 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 simply wraps modules that you need to group together and work with them as a unit.
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.
Previous Next
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, children, concept(-wide) properties, and concept(-wide) links.
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 notionssection). Concepts also define concept-wide members - concept properties and concept links, which are shared among all nodes of the particular Concept. You may think of them as "static" members.
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. Sets a |
| INamedConcept | Used if instances of your concept have an identifying name. This name appears in the code completion list. |
| 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).
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.
Smart references
A node containing a single reference of 1:1 cardinality is called a smart reference. These are somewhat special references. 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.
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.
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 type. 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.
Concept links and properties
Sometimes we want to define concept-wide members. For example, we might want to describe how all instances of a particular type will be presented in a completion menu. Or we frequently need to mark a concept as abstract. Also concept-wide members can help you express some domain specific purpose of a concept. This is what concept links and properties are for. Concept link and properties are declared in a concept declaration and the value you assign to them are valid for all instances of that concept. Also, if you have a concept-wide member declaration specified in a particular concept, different concrete values can then be set to the member in its subconcept. For example, the "abstract" concept property, which is of a boolean type, is declared in the BaseConcept concept. Some sub-concepts, like BaseAssignmentExpression have the "abstract" concept-wide property set to true, while others, like e.g. AssignmentExpression not. It is then up to the language designer to reflect the property values in the language behavior.
Annotation concepts
TO DO..
The Structure Language may sometimes be insufficient to express advanced constraints on the language structure. The Constraints aspect gives you a way to define such additional constraints.
Can be child/parent/ancestor/root
These are the first knobs to turn when defining constraints for a concept. They determine whether instances of this concept can be hooked as children (parents, ancestors) nodes of other nodes or root nodes in models.You specify them as boolean closures to invoke each time when evaluating allowed possition for a node in the AST.
can be child
Return false if an instance of the concept is not allowed to be a child of specific nodes.
| parameter | description |
|---|---|
| operationContext | IOperationContext |
| scope | current context (IScope) |
| parentNode | the parent node we are checking |
| childConcept | concept of the child node (can be a subconcept of this concept) |
| link | LinkDeclaration of the child node (child role can be taken from there) |
can be parent
Return false if an instance of concept is not allowed to be a parent of specific concept node (in a given role).
| parameter | description |
|---|---|
| operationContext | IOperationContext |
| scope | context (IScope) |
| node | the parent node we are checking (instance of this concept) |
| childConcept | the concept of the child node we are checking |
| link | LinkDeclaration of the child node |
can be ancestor
Return false if an instance of the concept is not allowed to be an ancestor of specific nodes.
| parameter | description |
|---|---|
| operationContext | IOperationContext |
| scope | context (IScope) |
| node | the ancestor node we are checking (instance of this concept) |
| childConcept | the concept of the descendant node |
can be root
This constraint is available only for rootable concepts (instance can be root is true in the concept structure description). Return false if instance of concept cannot be a root in the given model.
| parameter | description |
|---|---|
| operationContext | IOperationContext |
| scope | context (IScope) |
| model | model of the root |
Property constraints
Technically speaking, "pure" concept properties are not properties in its original meaning, but only public fields. Property constraints allow you to make them real properties. Using these constraints, the behavior of concept's properties can be customized. Each propertz constraint is applied to a single specified property.
property - the property to which this constraint is applied.
get - this method is executed to get property value every time property is accessed.
| parameter | description |
|---|---|
| node | node to get property from |
| scope | context (IScope) |
set - this method is executed to set property value on every write. The property value is guaranteed to be valid.
| parameter | description |
|---|---|
| node | node to set property |
| propertyValue | new property value |
| scope | context (IScope) |
is valid - this method should determine whether the value of the property is valid. This method is executed every time before changing the value, and if it returns false, the set() method is not executed.
| parameter | description |
|---|---|
| node | node to check property |
| propertyValue | value to be checked |
| scope | context (IScope) |
Referent constraints
Constraints of this type help to add behavior to concept's links and make them look more properties-like.
referent set handler - if specified, this method is executed on every set of this link.
| parameter | description |
|---|---|
| referenceNode | node that contains link. |
| oldReferentNode | old value of the reference. |
| newReferentNode | new value of the reference. |
| scope | context: IScope interface to object that shows you models, languages and devkits you can see from the code. |
search scope - defines the set of nodes to which this link can point. The method can return either a sequence<node<>> or ISearchScope.
| parameter | description |
|---|---|
| model | the model that contains the node with the link. This is included for convenience, since both referenceNode and enclosingNode keep the model too. |
| scope | IScope interface that shows models, languages and devkits you can see from the code. |
| referenceNode | the node that contains the actual link. It can be null when a new node is being created for a concept with smart reference. In this situation smart reference is used to determine what type of node to create in the context of enclosingNode, so the search scope method is called with a null referenceNode. |
| enclosingNode | parent of the node that contains the actual link, null for root nodes. Both referenceNode and enclosingNode cannot be null at the same time. |
| linkTarget | the concept that this link can refer to. Usually it is a concept of the reference, so it is known statically. If we specialize reference in subconcept and do not define search scope for specialized reference, then linkTarget parameter can be used to determine what reference specialization is required. |
If search scope is not set for the reference then default scope from the referenced concept is used. If the default search scope is also not set then "global" scope is used: all instances of referent concept from all imported models.
validator (since 1.5) - Each reference is checked against it's search scope and if, after changes in the model, a reference ends up pointing out of the search scope, MPS marks such a reference with an error message. Sometimes it is not efficient to build the whole search scope just to check if the reference is in scope. The search scope can be big or it may be much easier to check if the given node is in scope than to calculate what nodes are in scope. You can create quick reference check procedure here to speed up reference validation in such situations.
| parameter | description |
|---|---|
| model scope referenceNode enclosingNode linkTarget |
context of reference usage, the same meaning as in the search scope method. The main difference: referenceNode cannot be null here because validator is not used during node creation. |
| checkedNode | the node to be validated (referenceNode has a reference to checkedNode of type linkTarget) |
It is possible to create validation routine only if you have created a list of nodes in the corresponding search scope. If ISearchScope is returned from search scope method, then isInScope(SNode) method of ISearchScope interface will be used for validation, you should override this method with your validation routine.
It is not possible to define validation routine without defining search scope.
presentation - here you specify how the reference will look like in the editor and in the completion list. Sometimes it is convenient to show reference differently depending on context. For example, in Java all references to an instance field f should be shown as this.f, if the field is being shadowed by the local variable declaration with the same name. By default, if no presentation is set, the name of the reference node will be used as its presentation (provided it is an INamedConcept).
| parameter | description |
|---|---|
| model scope referenceNode enclosingNode linkTarget |
the context of reference usage, the same meaning as in the search scope function. |
| parameterNode | the node to be presented (referenceNode has a reference to parameterNode of type linkTarget) |
| visible | true - presentation of existing node, false - for new node (to be created after selection in completion menu) |
| smartReference | true - node is presented in the smart reference |
| inEditor | true - presentation for editor, false - for completion menu |
ISearchScope
Low level interface that can be implemented to support search scope. We recommend to subclass AbstractSearchScope (implements ISearchScope) abstract class instead of direct implementation of ISearchScope interface. The only abstract method in AbstractSearchScope class is
@NotNull List<SNode> getNodes(Condition<SNode> condition) - return list of nodes in the current search scope satisfying condition, similar to [search scope] but with the additional condition.
Other useful methods to override:
boolean isInScope(SNode node) - the same function as in [validator] method.
IReferenceInfoResolver getReferenceInfoResolver(SNode, AbstractConceptDeclaration);
resolve info - TODO
Default scope
Suppose we have a link pointing to an instance of concept C and we have no search scope defined for this link in referent constraints. When you edit this link, all instances of concept C from all imported models are visible by default. If you want to restrict set of visible instances for all links to concept C you can set default scope for the concept. As in referent constraint you can set search scope, validator and presentation methods. All the parameters are the same.
During syntax tree manipulation, common operations are often extracted to utility methods in order to simplify the task and reuse functionality. It is possible to extract such utilities into static methods or create node wrappers holding the utility code in virtual methods. However, in MPS a better solution is available: the behavior language aspect. It makes it possible to create virtual and non-virtual instance methods, static methods, and concept instance constructors on nodes.
Concept instance methods
A Concept instance method is a method, which can be invoked on any specified concept instance. They can be both virtual and non-virtual. While virtual methods can be overridden in extending concepts, non-virtual ones cannot. Also a virtual concept method can be declared abstract, forcing the inheritors to provide an implementation.
Concept instance methods can be implemented both in concept declarations and in concept interfaces. This may lead to some method resolution issues. When MPS decides which virtual method to invoke in the inheritance hierarchy, the following algorithm is applied:
- If the current concept implements a matching method, invoke it. Return the computed value.
- Invoke the algorithm for an extended concept, if there is one. In case of success return the computed value.
- Invoke the algorithm for all implemented concept interfaces. In case of success return the computed value.
- Return failure.
Concept constructors
When a concept instance is created, it is often useful to initialize some properties/references/children to the default values. This is what concept constructors can be used for. The code inside the concept construction is invoked on each instantiation of a new node of a particular concept.
Concept static methods
Some utility methods do not belong to concept instances and so should not be created as instance methods. For concept-wide functionality, MPS provides static concept methods. See also Constraints
SModel Language
The purpose of SModel language is to query and modify MPS models.
Treatment of null values
In an OO-language like Java it's pretty common to have a lot of checks for null values in the form of expr == null and expr != null statements. These are necessary to prevent null pointer exceptions from occuring. However, they at the same time increase the code clutter and often make code difficult to read. In order to alleviate this problem, we MPS treats null values in a liberal way. For example, if you ask a null node for a property, you will get back a null value. If you ask a null node for its children list, you will get empty list, etc.
Types
SModel language has the following types:
- node<ConceptType> - corresponds to an AST node
- nlist<ConceptType> - corresponds to a list of AST nodes
- model - corresponds to an instance of the MPS model
- search scope - corresponds to a search scope of a node's reference
- reference - corresponds to an AST node that represents reference instance
- concept<Concept> - corresponds to an AST node that represents a concept
- enummember<Enum Data Type> - corresponds to an AST node that represents an enumeration member
Most of the SModel language operations are applicable to all of these types.
Operation parameters
A lot of the operations in the SModel language accept parameters. The parameters can be specified once you open the parameter list by entering < at the end of an operation.
Queries
Features access
The SModel language can be used to access the following features:
- children
- properties
- references
- concept properties
- concept references
To access them, the following syntax is used:
If the feature is a property, then the type of whole expression is the property's type. If the feature is a reference or a child of 0..1 or 1 cardinality, then the type of this expression is node<LinkTarget>, where LinkTarget is the target concept in the reference or child declaration. If the feature is a child of 0..n cardinality, then the type of this expression is nlist<LinkTarget>.
You can use so-called implicit select to access features of the child nodes. For example, the following query:
will be automatically transformed by MPS to something like:
resulting in a plain collection of all non-null model elements accessible through the specified chain of link declarations.
Null checks
Since nulls are treated liberally in MPS, we need a way to check for null values. The isNull and isNotNull operations are our friends here.
IsInstanceOf check and type casts
Often, we need to check whether a node is an instance of a particular concept. We can't use Java's instanceof operator since it only understands java objects, not our MPS nodes. To perform this type of check, the following syntax should be used:
Also, there's the isExactly operation, which checks whether a node's concept is exactly the one specified by a user.
Once we've checked a node's type against a concept, we usually want to cast an expression to a concept instance and access some of this concept's features. To do so, the following syntax should be used:
Another way to cast node to particular concept instance is by using as cast expression:
The difference between the regular cast (using colon) and the as cast is in a way it handles the situation when the result of the left-side expression cannot be safely cast to the specified Concept instance: A NullPointer exception will be thrown by the regular cast in this case, while null will be returned by the as cast.
Parent
In order to find a node's parent, the parent operation is available on every node.
Children
The children operation can be used to access all direct child nodes of the current node. This operation has an optional parameter linkQualifier. With this parameter result of children<linkQualifier> operation is equivalent to node.linkQualifier operation call and so will recall only the children belonging to the linkQualifier group/role.
Sibling queries
When you manipulate the AST, you will often want to access a node's siblings (that is, nodes with the same role and parent as the node under consideration). For this task we have the following operations:
- next-sibling/prev-sibling - returns next/previous sibling of a node. If there is no such sibling, null is returned.
- next-siblings/prev-siblings - returns nlist of next/previous siblings of a node. These operations have an optional parameter that specifies whether to include the current node.
- siblings - returns nlist of all siblings of a node. These operations have an optional parameter that specifies whether to include the current node.
Ancestors
During model manipulation, it's common to find all ancestors (parent, parent of a parent, parent of a parent of a parent, etc) of a specified node. For such cases we have two operations:
- ancestor - return a single ancestor of the node
- ancestors - returns all ancestors of the node
Both of them have the following parameters to narrow down the list: - concept type constraint: concept=Concept, concept in [ConceptList]
- a flag indicating whether to include the current node: +
Descendants
It's also useful to find all descendants (direct children, children of children etc) of a specified node. We have the descendants operation for such purposes. It has the following parameters:
- concept type constraint: concept=Concept, concept in [ConceptList]
- a flag indicating whether to include current node: +
Containing root and model
To access top-most ancestor node of a specified node you can make use of containing root operation. Containing model is available as a result of the model operation.
Model queries
Often we want to find all nodes in a model which satisfy a particular condition. We have several operations that are applicable to expressions of model type:
- roots(Concept) - returns all roots in a model which are instances of the specified Concept
- nodes(Concept) - returns all nodes in a model which are instances of the specified Concept
Search scope queries
In some situations, we want to find out which references can be set on a specified node. For such cases we have the search scope operation. It can be invoked with the following syntax:
Concept literal
Often we want to have a reference to a specified concept. For this task we have the concept literal. It has the following syntax:
Concept operation
If you want to find the concept of a specified node, you can call the concept operation on the node.
Concept hierarchy queries
We can query super/sub-concepts of expression with the concept type. The following operations are at your disposal:
- hierarchy - returns inheritance hierarchy of a concept
- super-concepts - returns all super-concepts of the specified concept. There is an option to include/exclude the current concept.
- sub-concepts - returns sub-concepts
Is Role operation
Sometimes we may want to check whether a node has a particular role. For this we have the following syntax:
Containing link queries
If one node was added to another one (parent) using the following expression:
then you can call the following operations to access the containment relationship information:
- containingRole - returns a string representing the child role of the parent node containing this node ("childLinkRole" in above case)
- containingLink - returns node<LinkDeclaration> representing a link declaration of the parent node containing this node
- index - returns int value representing index of this node in a list of children with corresponding role. Identical to the following query upon the model represented above:
Reference operations
Accessing references
Following operation were created co access reference instance representing a reference from source node to target one. Operations are applicable on source node:
- reference< > - returns an instance of reference type representing specified reference. This operation requires "linkQuelifier" parameter used as reference specification. Parameter can be either link declaration of source node's concept or expression returning node<LinkDeclaration> as a result
- references - returns sequence<reference> representing all references specified in source node.
Working with
Having an instance of reference type you can call following operations on it:
- linkDeclaration - returns node<LinkDeclaration> representing this reference
- resolveInfo - returns string resolve info object
- role - returns reference role - similar to reference.linkDeclaration.role;
- target - returns node<> representing reference target is it was specified and located in model(s)
Downcast to lower semantic level
SModel language generates code that works with raw MPS classes. These classes are quite low-level for the usual work, but in some exceptional cases we may still need to access them. To access the low-level objects, you should use the downcast to lower semantic level construct. It has the following syntax:
Modification operations
Feature changes
The most commonly used change operation in SModel is the act of changing a feature. In order to set a value of a property, or assign a child or reference node of 0..1 or 1 cardinality, you can use straight assignment (with =) or the set operation. In order to add a child to 0..n or 1..n children collection, you can either use the.add operation from the collection language or call add next-sibling/add prev-sibling operations on a node<> passing another node as a parameter.
New node creation
There are several ways to create a new node:
- new operation: new node<Concept>()
- new instance operation on a model: model.newInstance()
- add new(Concept) and set new(Concept) operations applied to feature expressions
- replace with new(Concept) operation
- new root node(Concept) operation applied to a model. In this case the concept should be rootable
- new next-sibling<Concept>/new prev-sibling<Concept> operations adding new sibling to an existing node
Copy
To create a copy of an existing node, you can use the copy operation.
Replace with
To replace a node with an instance of another node, you can use the 'replace with' operation. If you want to replace and create at the same time, there is a shortcut operation 'replace with new(Concept)', which takes a concept as a parameter.
Delete and detach operations
If you want to completely delete a node from the model, you can use the delete operation. In order to detach a node from it's parent only, so that you can for example attach the node to another parent later, you use the detach operation.
Pattern Language
The pattern language has a single purpose - to define patterns of model structures. Those patterns form visual representations of nodes you want to match. A pattern matches a node if the node's property values are equal to those specified in the pattern, node's references point to the same targets that the ones of the pattern do and the corresponding children match the appropriate children of the pattern.
Also patterns may contain variables for nodes, references and properties, which then match any node/reference/property. On top of that the variables will hold the actual values upon a successful match.
PatternExpression
The single most important concept of the pattern language is PatternExpression. It contains a pattern as its single arbitrary node. Also, the node can specify the following variables:
- #name - a node variable, a placeholder for a node. Stores the matching node
- #name - a reference variable, a placeholder for a reference. Stores the reference's target, i.e. a node.
- $name - a property variable, a placeholder for a property value. Stores the property value, i.e. a string.
- *name - a list variable, a placeholder for nodes in the same role. Stores the list of nodes.
Antiquotations may be in particular useful when used inside a pattern, just like inside quotations (see Antiquotations).
Examples
1. The following pattern matches against any InstanceMethodDeclaration without parameters and a return type:

Captured variables:
| $methodName | string | method's name |
| #statementList | node<StatementList> | statements |
2. The following pattern matches against a ClassifierType with the actual classifier specified inside an antiquotation expression and with any quantity of any type parameters:

Captured variables:
| *l | nlist<Type> | class type's parameters |
| #ignored | node<Type> | used as wildcard, its contents is ignored. Means that parameters are arbitrary |
Using patterns
Match statement
Patterns are typically used as conditions in match statements. Pattern variables can be referenced from inside of the match statement.
For example:

this piece of code examines a node n and checks whether it satisfies the first or the second condition. Then the statement in the corresponding (matching) block is executed. A pattern variable $name is used in a first block to print out the name of a node. In our case the node holds a variable declaration.
Other usages
Patterns are also used in several other language constructs in MPS. They may appear:
- as conditions on applicable nodes of typesystem/replacement/subtyping/other rules of typesystem language. (See Inference rules)
- as supertype patterns in coerce statement and coerce expression. (See Coerce)
- as conditions on node in generator rules.
You can also use patterns in your own languages.
Basically what happens is that a class is generated from a PatternExpression and the expression itself is reduced to a constructor of this class. This class extends GeneratedMatchingPattern and has a boolean method match(SNode), which returns a boolean value indicating whether the node matches the pattern. It also holds a method getFieldValue(Stirng) to get the values stored in pattern variables after a successful match.
So to develop your own language constructs using patterns, you can call these two methods in the generator template for your constructs.
Editor
Once the structure for your language is defined, you will probably go and create means to allow developers to build ASTs with it. Manipulating the ASTs directly would not be very intuitive nor productive. To hide the AST and offer the user comfortable and intuitive interaction is the role for language editors.
Editor Overview
An editor for a node serves as its view as well as its controller. An editor displays the node and lets the user modify, replace, delete it and so on. Nodes of different concepts have different editors. A language designer should create an editor for every concept in his/her language.
In MPS, an editor consists of cells, which themselves contain other cells, some text, or a UI component. Each editor has its concept for which it is specified. A concept may have no more than one editor declaration (or can have none). If a concept does not have an editor declaration, its instances will be edited with an editor for the concept's nearest ancestor that has an editor declaration.
To describe an editor for a certain concept (i.e. which cells have to appear in an editor for nodes of this concept), a language designer will use a dedicated language simply called editor language. You see, MPS applies the Language Oriented Programming principles to itself.
The description of an editor consists of descriptions for cells it holds. We call such descriptions "cell models." For instance, if you want your editor to consist of a unique cell with unmodifiable text, you create in your editor description a constant cell model and specify that text. If you want your editor to consist of several cells, you create a collection cell model and then, inside it, you specify cell models for its elements. And so on.
Types Of Cell Models
Editor Components and editor component cells
Sometimes two or more editor declarations for different concepts have a common part, which is duplicated in each of those editors. To avoid redundancy, there's a mechanism called editor components. You specify a concept for which an editor component is created and create a cell model, just as in concept editor declaration. When written, the component could then be used in editor declarations for any of the specified concept's descendants. To use an editor component inside your editor declarations, one will create a specific cell model: editor component cell model, and set your editor component declaration as the target of this cell model's reference.
Cell layouts
Each collection cell has property "cell layout", which describes how child nodes will be placed. There is several layouts:
- indent layout - places cells like text.
- horizontal layout - places cells horizontally in row.
- vertical layout - places cells vertically.
Styles
Each cell model has some appearance settings that determine the cell's presentation. They are, for instance, font color, font style, whether a cell is selectable, and some others. Those settings are combined into an entity called stylesheet. A stylesheet could be either inline, i.e. be described together with a particular cell model, or it could be declared separately and used in many cell models.
It is good practice to declare a few stylesheets for different purposes. Another good practice is to have a style guideline in mind when developing an editor for your language, as well as when developing extensions for your language. For example, in Base Language there are styles for keywords (applied to those constant cells in Base Language editor which correspond to keywords in Java), static fields (applied to static field declarations and static field references), instance fields, numeric literals, string literals, and so forth. When developing an extension to Base Language, you should apply keyword style to new keywords, field style to new types of fields, and so forth.
A stylesheet is quite similar to CSS stylesheets; it consists of a list of style classes, in which the values for some style properties are specified. A style class may extend another style class: if some style property value is unspecified in a descendant class, its value will be inherited from the extended class.
Style properties
Boolean style properties
- Is selectable - whether the cell can be selected. True by default.
- Is editable - whether one can modify text in a cell or not. By default is false for constant cell models, true for other cell models.
- Draw border - whether border will be drawn around a cell
- Draw brackets - whether brackets will be drawn around a cell
- First position allowed/last position allowed - for text-containing cells, specifies whether it is allowed that a caret is on the first/last position (i.e. before/after the whole text of a cell)
You can either choose a property value from a completion menu or specify a query i.e. a function which returns a boolean value.
Padding properties.
- Left/right padding. It's a floating point number which specifies the padding of a text cell, i.e. how much space will be between cell's text and cell's left and right sides, respectively.
Punctuation properties.
All cells in collection are separated with one space by default. Sometimes we need cells placed together.
- Punctuation left - if this property is true, space from left side of the cell is deleted and first position in cell becomes not allowed.
- Punctuation right - if this property is true, space from right side of the cell is deleted and last position in cell becomes not allowed.
- Horizontal gap - specifies gap size between cells in collection. Default value is 1 space.
For example in code
we don't want spaces between "(" and "1", and between "1" and ")". So we should add property punctuation-right to the cell "(", and property
punctuation-left to the cell ")".
Color style properties
- Text foreground color - cell text's color (affect text cells only)
- Text background color - cell text's background color (affects text cells only)
- Background color - the background color of a cell. Affects any cell. If a text cell has non-zero padding and some text background color, the cell's background color will be the color of its margins.
You can either choose a color from the completion menu or specify a query i.e. a function which returns a color.
Indent layout properties
- Indent layout indented - all lines will be placed with indent. This property can be used for indent in code block.
- Indent layout new line - after this cell will be placed new line.
- Indent layout on new line - this cell will be placed on new line
- Indent layout children new line - all children of collection will be placed on new line
- Indent layout no wrap - line won't be wrapped before this cell
Other style properties
- Font size
- Font style. Can be either plain, bold, italic, or bold italic.
- Layout constraint.
- For flow layout
- none - default behavior
- punctation - means that previous item in flow layout should always be placed on the same line as the item which this constraint is assigned to.
- noflow - excludes a cell from flow layout. Current line is finished and item is placed below it. After this item a new line is started and normal flow layout is applied. This style can be used to embed a picture inside of text.
- For flow layout
- Underlined. Can be either underlined, not underlined, or as is ('as is' means it depends on properties of the enclosing cell collection).
Cell actions
Every cell model may have some actions associated with it. Such actions are meant to improve usability of editing. You can specify them in an inspector of any cell model.
Key maps
You may specify a reference to a key map for your cell model. A key map is a root concept - a set of key map items each consisting of a keystroke and an action to perform. A cell generated from a cell model with a reference to a certain key map will execute appropriate actions on keystrokes.
In a key map you must specify a concept for which a key map is applicable. For instance, if you want to do some actions with an expression, you must specify Expression as an applicable concept; then you may specify such a key map only for those cell models which are contained inside editor declarations for descendants of Expression, otherwise it is a type error.
If a key map property "everyModel" is "true," then this key map behaves as if it is specified for every cell in the editor. It is useful when you have many descendants of a certain concept which have many different editors, and your key map is applicable to their ancestor. You need not specify such a key map in every editor if you mark it as an "every model" key map.
A key map item consists of the following features:
- A function which is executed when a key map item is triggered (returns nothing)
- A set of keystrokes which trigger this key map item
- A boolean function which determines if a key map item is applicable here (if not specified, then it's always applicable). If a key map item is not applicable the moment it is triggered, then it will not perform an action.
- You may specify caret policy for a key map item. Caret policy says where in a cell a caret should be located to make this key map item enabled. Caret policy may be either first position, last position, intermediate position, or any position. By default, caret policy is "any position." If a caret in a cell does not match the caret policy of a key map item the moment it is triggered, then this key map item will not perform an action.
Action maps
A cell model may contain a reference to an action map. An action map overrides some default cell actions (delete and right transform) for a certain concept. An action map consists of several action map items. In an action map, you must specify a concept for which the action map is applicable.
An action map item contains:
- an action description which is a string,
- and a function which performs an action (returns nothing).
An action map item may override one of two default actions: default delete action or right transform (see Actions). For instance, when you have a return statement without any action maps in its editor, and you press Delete on a cell with the keyword "return," the whole statement is deleted. But you may specify an action map containing a delete action map item, which instead of just deleting return statement replaces it with an expression statement containing the same expression as the deleted return statement.
action DELETE description : <no description>
execute : (node, editorContext)->void {
node < ExpressionStatement > expressionStatement = node . replace with new ( ExpressionStatement ) ;
expressionStatement . expression . set ( node . expression ) ;
}
Cell menus
One may specify a custom completion menu for a certain cell. Open an inspector for your cell declaration, find a table named Common, find a row named menu, create a new cell menu descriptor. Cell menu descriptor consists of menu parts, which are of differend kinds, which are discussed below.
Property values menu part
This menu part is available on property cells, it specifies a list of property values for your property which will be shown in completion. One should write a function which returns a value of type list<String>.
Property postfix hints menu part
This menu part is available on property cells, it specifies a list of strings which serve as "good" postfixes for your property value. In such a menu part one should write a function which returns a value of type list<String>. Such a menu is useful if you want MPS to "guess" a good value for a property. For instance, one may decide that it will be a good variable name which is a variable type name but with the first letter being lowercased, or which ends with its type name: for a variable of type "Foo" good names will be "foo", "aFoo", "firstFoo", "goodFoo", etc. So one should write in a variable declaration's editor in a menu for property cell for variable name such a menu part:
property postfix hints
postfixes : (scope, operationContext, node)->list<String> {
list < String > result ;
node < Type > nodeType = node . type ;
if ( nodeType != null ) {
result = MyUtil.splitByCamels( nodeType . getPresentation() );
} else {
result = new list < String > { empty } ;
}
return result ;
}
where splitByCamels() will be a function which returns a list of postfixes of a string starting with capitals (for instance MyFooBar -> MyFooBar, FooBar, Bar).
Primary replace child menu
It's a cell menu part which returns primary actions for child (those by default, as if no cell menu exists).
Primary choose referent menu
It's a cell menu part which returns primary actions for referent (those by default, as if no cell menu exists).
Replace node menu (custom node's concept)
This kind of cell menu parts allows to replace an edited node (i.e. node on which a completion menu is called) with instances of a certain specified concept and its subconcepts. Such a cell menu part is useful, for example, when you want a particular cell of your node's editor to be responsible for replacement of a whole node. For instance, consider an editor for binary operations. There's a common editor for all binary operations which consists of a cell for left operand, a cell for operation sign which is a cell for concept property "alias" and a cell for right operand.
[> % leftExpression % ^{{ alias }} % rightExpression % <]
It is natural to create a cell menu for a cell with operation sign, which will allow to replace an operation sign with another one, (by replacing a whole node of course). For such a purpose one will write in the cell for operation sign a replace node menu part:
replace node (custom node concept) replace with : BinaryOperation
The former left child and right child are added to newly created BinaryOperation according to Node Factories for BinaryOperation concept.
Replace child menu (custom child's concept)
Such a cell menu part is applicable to a cell for a certain child and specifies a specific concept which and subconcepts of which will be shown in completion menu (and instantiated when chosen and the instance will be set as a child). To specify that concept one should write a function which returns a value of a type node<ConceptDeclaration>.
Replace child menu (custom action).
This kind of cell menu parts is applicable to a cell for a certain child and allows one to customize not only child concept, but the whole replace child action: matching text (text which will be shown in completion menu), description text (a description of an action, shown in the right part of completion menu), and the function which creates a child node when the action is selected from completion menu. Hence, to write such a menu one should specify matching text, description text and write a function returning a node (this node should be an instance of a target concept specified in a respective child link).
Generic menu item
This kind of cell menu part allows one to make MPS perform an arbitrary action when a respective menu item will be selected in a completion menu. One should specify matching text for a menu item and write a function which does what one wants. For instance, one may not want to show a child list cell for class fields if no class fields exist. Hence one can't use its default actions to create a new field. Instead, one can create somewhere in a class' editor a generic menu item with matching text "create field" which creates a new field for a class.
generic item
matching text : add field
handler : (node, model, scope, operationContext)->void {
node . field . add new ( <default> ) ;
}
Action groups
An action group is a cell menu part which returns a group of custom actions. At runtime, during the menu construction, several objects of a certain type, which are called parameter objects, are collected or created. For that parameter object type of an action group functions, which return their matching text and description text, are specified. A function which is triggered when a menu item with a parameter object is chosen is specified also.
Thus, an action group description consists of:
- a parameter object type;
- a function which returns a list of parameter objects of a specified type (takes an edited node, scope and operation context);
- a function which takes a parameter object of a specified type and returns matching text (a text which will be shown in a completion menu);
- a function which takes a parameter object of a specified type and returns description text for a parameter object;
- a function which performs an action when parameter object is chosen in a completion menu.
A function which performs an action may be of different kinds, so there are three different kinds of cell action group menu parts:
- Generic action group. Its action function, given a parameter object, performs an arbitrary action. Besides the parameter object, the function is provided with edited node, its model, scope and operation context.
- Replace child group. It is applicable to child cells and its action function, given a parameter object, returns a new child, which must have a type specified in a respective child link declaration. Besides the parameter object, the function is provided with edited node, its model, current child(i.e. a child being replaced), scope and operation context.
- Replace node group. Its action function, given a parameter object, returns a node. Usually it is some referent of an edited node (i.e. node on which a completion menu is called). Besides the parameter object, the function is provided with edited node, its model, scope and operation context.
Cell menu components
When some menu parts in different cells are equal one may want to extract them into a separate and unique entity, to avoid duplications. For such a purpose cell menu components are meant. A cell menu component consists of a cell menu descriptor (a container for cell menu parts) and a specification of an applicable feature. A specification of applicable feature contains a reference to a feature (i.e. child link declaration, reference link declaration or property declaration), to which a menu is applicable. For instance if your menu component will be used to replace some child its child link declaration should be specified here; etc.
When a cell menu component is created, it can be used in cell menus via cell menu component menu part, which is a cell menu part which contains a reference to a certain menu component.SModel language
Actions
MPS editor has quite sensible defaults in completion actions, node creation policy. But when you want to customize them, you have to work with the actions language. This language is also used to define Left and Right Transform actions (RT/LT-actions for short), which allows editing binary operations in a text-like way.
Substitute Actions
Substitute actions are actions which are available when you press Ctrl+Space in the editor. MPS has the following default behavior for them:
- If your selection is inside of a position which allows concept A, then all enabled subconcepts of A will be available in the completion menu.
- All abstract concepts are excluded
- All concepts with dontSubstituteByDefault property are removed
- All concepts for which the 'can be a child' constraint returns false are excluded
- All concepts for which the 'can be a parent' constraint of a parent node returns false are excluded
- If a concept has a 1:1 reference, then it is not added to the completion menu. Instead, an item is added for each element of a scope for that reference. We use a name smart reference for such items.
When you want to customize this behavior, you have to create a node substitute actions root. Inside it you can create substitute actions builders. Each of them has a substitute node concept, where you type a name of a concept which you want to substitute. It has a condition; when this condition is satisfied, your actions will be added to the completion menu. Also it has an actions part where action behavior is specified. Each action has an associated concept which can be used for action filtering (see remove by condition).
Add concept
If a concept isn't available because of default settings, you can add it with the 'add concept' construct.
Remove defaults
Use this construct if you want to completely override defaults. It removes all default actions and adds only those actions which are specified in the actions language.
Remove By Condition
Use this construct if you want to remove only some of the actions. It can be useful when you extend a language and want to remove some of its actions in a particular context.
Custom items
If you aren't satisfied with default items, you can create your own. In this case you can override everything: output concept, matching text, description text, icon, and behavior on item invocation. When you create custom items, you have to specify output concept so it can be used to filter out your actions from extending language.
Simple
Simple item adds one item to substitute menu. You can specify all the properties of substitute action (matching text, description, icon etc). It can be useful for entering literals (boolean, integer, float, string, char).
Parametrized
This concept allows you to create an item in substitute menu based on a query. A query should return a list of something. For example, if you want to create completion for files in a directory, you can use it. This concept is similar to simple item but it has additional parameter parameterObject in all of its blocks.
Wrapper
Sometimes we want to add all the completion items from a context of one type of concepts into a context of other concepts. Let's consider a couple of examples from baseLanguage. For example, we want to see all available items for expression in statement's context, since we may wrap all of them in ExpressionStatement. Or we can add all items of Type's completion menu, since we can create a local variable declaration. Wrapper blocks has a concept from whose context we want to add completion items. It also has a wrapper block with a nodeToWrap parameter, which the author of wrapper block should wrap.
Concepts Menu
Sometimes you want to add items for subconcepts of a particular item but want to override its handler. Concepts menu block allows you to do so.
Generic
If the above actions are not suitable, you can resort to generic item. It has a block which returns a list of INodeSubstitueAction items.
Side Transform Actions
When you edit code in a text editor, you can type it either from left to right:
1 <caret> press +
1+<caret> press 2
1+2<caret>
or from right to left
<caret>1 press 2
2<caret>1 press +
2+<caret>1
In order to emulate this behavior, MPS has side transform actions: left and right transforms. They allow you to create actions which will be available when you type on left or right part of your cell. For example, in MPS you can do the following:
1<caret> press + (red cell with + inside appears)
1+<caret> press 2 (red cell disappear)
1+2<caret>
or the following:
<caret>1 press + (red cell with + inside appears)
+<caret>1 press 2 (red cell disappear)
2<caret>+1
The first case is called right transform. The second case is called left transform.
In order to create transformation actions you have to create transform menu actions root. Inside it, you can create transform action builders. You can specify the following:
- whether it is left or right transform
- a concept which instance you want to transform
- a condition which defines where your actions will be applicable
Add custom items
Custom items is similar to its counterpart in substitute actions part of a language. It allows you to add either one or many items to menu. Let's consider them in detail.
Simple item
Simple item adds an item with matching text, description, icon, and substitute handler.
Parametrized item.
Parametrized item adds a group of items based on a query which returning a list of something. It's similar to simple item but has additional parameterObject parameter in every block.
Add concept
Add concept adds an item for every non-abstract subconcept of a specified concept. This item has a handler block where you can replace sourceNode with a newly created node. For example, this is useful when you want to create a transformation for each subconcept of a BinaryOperation, such as +, -, *, or /. The code for replacing sourceNode is the same in each of these cases - the only difference is the resulting concept.
Include transform for
This construct allows you to include all the right transform action associated with a particular node.
Remove By Condition
This allows you to filter actions in case of a language extension.
Remove Concept
This allows you remove all the actions associated with a particular concept.
Node Factories
When you have a baseLanguage expression selected, press Ctrl+Space on it and choose (expr). You will have your expressions surrounded by parenthesis. Node factories allows you to implement this and similar functionality by customizing instantiation of a new node. In order to create node factory, you first have to create a new node factories root node. Inside of this root you can create node factories for concepts. Each node factory consists of node creation block which has the following parameters: newNode (created node), sampleNode (currently substituted node; can be null), enclosing node (a node which will be a parent of newNode), and a model.
Generator User Guide
Introduction
Generator is a part of language specification that defines the denotational semantics for a language's concepts.
MPS follows the model-to-model transformation approach. Generator specifies the translation of constructions in the input language to constructions in the output language. The process of model-to-model transformation may involve many intermediate models and results in the output model where all constructions are in language whose semantics are already defined elsewhere.
For instance, most concepts in baseLanguage (classes, methods etc) are "machine understandable", wherefore baseLanguage is often used as an output language.
Target assets are created by applying model-to-text transformation, which must be supported by the output language. The language aspect for that is called TextGen and is available as a separate tab in concept's editor. MPS provides destructive update of generated assets only.
For instance, baseLanguage's TextGen aspect generates *.java files at the following location:
<generator output path>\<model name>\<ClassName>.java
where:
Generator output path - is specified in the module which owns the input model (see MPS modules).
Model name - is a path segment created by replacing '.' with the file separator in the input model's name.
Overview
Generator Module
Unlike any other language aspect, the generator aspect is not a single model. Generator specification can comprise many generator models as well as utility models. Generator model contains templates, mapping configurations and other constructions of the generator language.
Generator model is distinguished from a regular model by the model stereotype - 'generator' (shown after the model name as <name>@generator).
The screenshot below shows the generator module of the smodel language as an example.
| Research bundled languages yourself You can research the smodel (and any other) language's generator by yourself:
|
Creating a New Generator
New generator is created by using the New -> Generator command in the language's popup menu.
Technically, it is possible to create more than one generator for one language, but at the time of writing MPS does not provide full support for this feature. Therefore languages normally have only one (if any) generator. For that reason, the generator's name is not important. Everywhere in the MPS GUI a generator module can be identified by its language name.
When creating a new generator module, MPS will also create the generator model 'main@generator' containing an empty mapping configuration node.
Generator Properties
As a module, generator can depend on other modules, have used languages and used devkits (see Module meta-information).
The generator properties dialog also has two additional properties:
- depends on generators - specifies the dependencies on other generators; this allows making references on templates in another generator;
- mapping constraints - priority relationships between mapping rules can be specified. If such a relationship involves other generator rules, then declaring a dependency on that generator is also required. For details on mapping constraints, see Mapping Priorities, Generation Process: Defining the Order of Priorities, Demo 6: Dividing Generation Process into Steps.
Generating Generator
MPS generator engine (or the Generator language runtime) uses mixed compilation/interpretation mode for transformation execution.
Templates are interpreted and filled at runtime, but all functions in rules, macros, and scripts must be pre-compiled.
To avoid any confusion, always follow this rule: after any changes made to the generator model, the model must be re-generated (Shift+F9). Even better is to use Ctrl+F9, which will re-generate all modified models in the generator module.
Transformation
The transformation is described by means of templates. Templates are written using output language and are edited using the same cell editor as any other 'regular code' in that language. Therefore, the 'template editor' right away has the same level of tooling support - syntax/error highlighting, auto-completion, etc.
Templates applicability is defined using Generator Rules, which are grouped into Mapping Configurations.
Mapping Configurations
Mapping Configuration is a minimal unit, which can form a generation step. It contains Generator Rules, defines mapping labels and may include pre- and post-processing scripts.
Generator Rules
Applicability of each transformation is defined by generator rules.
There are six types of generator rules:
- conditional root rule
- root mapping rule
- weaving rule
- reduction rule
- pattern rule
- abandon root rule
Each generator rule consists of premise and consequence (except for the abandon root rule, whose consequence is predefined and is not specified by the user).
All rules except for the conditional root rule contain a reference on concept of input node (or just input concept) in its premises. All rule premises also contain an optional condition function.
Rule consequence commonly contains a reference to an external template (i.e. a template declared as a root node in the same or different model) or so-called in-line template (conditional root rule and root mapping rule can only have reference to an external template). There are also several other versions of consequences.
The following screenshot shows the contents of a generator model and a mapping configuration example.
![]()
Macros
Template code can be parameterized by means of macros. The generator language defines three kinds of macros:
- property macro - computes property value;
- reference macro - computes the target (node) of a reference;
- node macro - is used to control template filling at generation time. There are several versions of node macro - LOOP-macro is an example.
Macro implements a special kind of so-called annotation concept and can wrap property, reference or node cells (depending on the kind of macro) in a template code.
Code wrapping (i.e. the creation of a new macro) is done by pressing Ctrl+Shift+M or by applying the 'Create macro' intention.
The following screenshot shows an example of a property macro.
![]()
Macro functions and other parameterization options are edited in the inspector view. Property macro, for instance, requires specifying the value function, which will provide the value of the property at generation time. In the example above, output class node will get the same name that the input node has.
The node parameter in all functions of the generator language always represents the context node to which the transformation is currently being applied (the input node).
Some macros (such as LOOP and SWITCH-macro) can replace the input node with a new one, so that subsequent template code (i.e. code that is wrapped by those macros) will be applied to the new input node.
External Templates
External templates are created as a root node in the generator model.
There are two kinds of external templates in MPS.
One of them is root template. Any root node created in generator model is treated as a root template unless this node is a part of the generator language (i.e. mapping configuration is not a root template). Root template is created as a normal root node (via Create Root Node menu in the model's popup).
The following screenshot shows an example of a root template.
This root template will transform input node (a Document) into a class (baseLanguage). The root template header is added automatically upon creation, but the concept of input node is specified by the user.
It is good practice to specify the input concept, because this allows MPS to perform a static type checking in a macro function's code.
Root template (reference) can be used as a consequence in conditional root rule and root mapping rule. (
When used in a conditional root rule, the input node is not available).
The second kind of template is defined in the generator language and its concept name is 'TemplateDeclaration'. It is created via the 'template declaration' action in the Create Root Node menu.
The following screenshot shows an example of template declaration.
The actual template code is 'wrapped' in a template fragment. Any code outside template fragment is not used in transformation and serves as a context (for example you can have a Java class, but export only one of its method as a template).
Template declaration can have parameters, declared in the header. Parameters are accessible through the generation context.
Template declaration is used in consequence of weaving, reduction and pattern rules. It is also used as an included template in INCLUDE-macro (only for templates without parameters) or as a callee in CALL-macro.
Template Switches
A template switch is used when two or more alternative transformations are possible in a certain place in template code. In that case, the template code that allows alternatives is wrapped in a SWITCH-macro, which has reference to a Template Switch. Template Switch is created as a root node in generator model via the Create Root Node menu (this command can be seen on the 'menu' screenshot above).
The following screenshot shows an example of a template switch.
Generator Language Reference
Mapping Configuration
Mapping Configuration is a container for generator rules, mapping label declarations and references on pre- and post-processing scripts. A generator model can contain any number of mapping configurations - all of them will be involved in generation process if the owner generator module is involved. Mapping configuration is a minimal generator unit that can be referenced in mapping priority rules (see Generation Process: Defining the Order of Priorities).
Generator Rule
Generator Rule specifies a transformation of an input node to an output node (except for the conditional root rule which doesn't have an input node). All rules consist of two parts - premise and consequence (except for the abandon root rule which doesn't have a consequence). Any generator rule can be tagged by a mapping label.
All generator rules functions have following parameters:
- node - current input node (all except condition-function in conditional root rule)
- genContext - generation context - allows searching of output nodes, generating of unique names and others (see generation context)
Generator Rules:
| Rule | Description | Premise | Consequence |
|---|---|---|---|
| conditional root rule | Generates root node in output model. Applied only one time (max) during the generation step. | condition function (optional), missing condition function is equivalent to a function always returning true. | root template (ref) |
| root mapping rule | Generates a root node in output model. | concept - applicable concept (concept of input node) inheritors - if true then the rule is applicable to the specified concept and all its sub-concepts. If false (default) then the sub-concepts are not applicable. condition function (optional) - see conditional root rule above. keep input root - if false then the input root node (if it's a root node) will be dropped. If true then input root will be copied to output model. |
root template (ref) |
| weaving rule | Allows to insert additional child nodes into output model. Weaving rules are processed at the end of generation micro-step just before map_src and reference resolving. Rule is applied on each input node of specified concept. Parent node for insertion should be provided by context function. (see Model Transformation) |
concept - same as above inheritors - same as above condition function (optional) - same as above |
|
| reduction rule | Transforms input node while this node is being copied to output model. | concept - same as above inheritors - same as above condition function (optional) - same as above |
|
| pattern rule | Transforms input node, which matches pattern. | pattern - pattern expression condition function (optional) - same as above |
|
| abandon root rule | Allows to drop an input root node with otherwise would be copied into output model. | applicable concept ( condition function (optional) - same as above |
n/a |
Rule Consequences:
| Consequence | Usage | Description |
|---|---|---|
| root template (ref) |
|
Applies root template |
| external template (ref) |
|
Applies an external template. Parameters should be passed if required, can be one of:
|
| weave-each | weaving rule | Applies an external template to a set of input nodes. Weave-each consequence consists of:
|
| in-line template |
|
Applies the template code which is written right here. |
| in-line switch | reduction rule | Consists of set of conditional cases and a default case. Each case specify a consequence, which can be one of:
|
| dismiss top rule |
|
Drops all reduction-transformations up to the point where this sequence of transformations has been initiated by an attempt to copy input node to output model. The input node will be copied 'as is' (unless some other reduction rules are applicable). User can also specify an error, warning or information message. |
| abandon input |
|
Prevents input node from being copied into output model. |
Root Template
Root Template is used in conditional root rules and (root) mapping rules. Generator language doesn't define specific concept for root template. Any root node in output language is treated as a Root Template when created in generator model. The generator language only defines a special kind of annotation - root template header, which is automatically added to each new root template. The root template header is used for specifying of an expected input concept (i.e. concept of input node). MPS use this setting to perform a static type checking in a code in various macro-functions which are used in the root template.
External Template
External Template is a concept defined in the generator language. It is used in weaving rules and reduction rules.
In external template user specifies the template name, input concept, parameters and a content node.
The content node can be any node in output language. The actual template code in external templates is surrounded by template fragment 'tags' (the template fragment is also a special kind of annotation concept). The code outside template fragment serves as a framework (or context) for the real template code (template fragment) and is ignored by the generator. In external template for weaving rule, the template's context node is required (it is a design-time representation of the rule's context node), while template for reduction rule can be just one context-free template fragment. External template for a reduction rule must contain exactly one template fragment, while a weaving rule's template can contain more than one template fragments.
Template fragment has following properties (edited in inspector view):
- mapping label
- fragment context - optional function returning new context node which will replace main context node while applying the code in fragment.
Can only be used in weaving rules.
Mapping Label
Mapping Label is declared in mapping configuration and references on this declaration are used to label generator rules, macros and template fragments. Such marks allow finding of an output node by input node (see generation context).
Properties:
- name
- input concept (optional) - expected concept of input node of transformation performed by the tagged rule, macro or template fragment
- output concept (optional) - expected concept of output node of transformation performed by the tagged rule, macro or template fragment
MPS makes use of the input/output concept settings to perform static type checking in get output ... operations (see generation context).
Macro
Macro is a special kind of an annotation concept which can be attached to any node in template code. Macro brings dynamic aspect into otherwise static template-based model transformation.
Property- and reference-macro is attached to property- and reference-cell, and node-macro (which comes in many variations - LOOP, IF etc.) is attached to a cell representing the whole node in cell-editor. All properties of macro are edited using inspector view.
All macro have the mapping label property - reference on a mapping label declaration. All macro can be parameterized by various macro-functions - depending on type of the macro. Any macro-function has at least three following parameters:
- node - current input node;
- genContext - generation context - allows searching of output nodes, generating of unique names and others;
- operationContext - instance of jetbrains.mps.smodel.IOperationContext interface (used rarely).
Many of macro have mapped node or mapped nodes function. This function computes new input node - substitution for current input node. If mapped node function returns null or mapped nodes function returns an empty sequence, then generator will skip this macro altogether. I.e. no output will be generated in this place.
| Macro | Description | Properties (if not mentioned above) |
|---|---|---|
| Property macro | Computes value of property. | value function:
|
| Reference macro | Computes referent node in output model. Normally executed in the end of a generation micro-step, when the output model (tree) is already constructed. Can also be executed earlier if a user code is trying to obtain the target of the reference. |
referent function:
|
| IF | Wrapped template code is applied only if condition is true. Otherwise the template code is ignored and an 'alternative consequence' (if any) is applied. | condition function alternative consequence (optional) - any of:
|
| LOOP | Computes new input nodes and applies the wrapped template to each of them. | mapped nodes function |
| INCLUDE | Wrapped template code is ignored (it only serves as an anchor for the INCLUDE-macro), a reusable external template will be used instead. | mapped node function (optional) include template - reference on reusable external template |
| CALL | Invokes template, replaces wrapped template code with the result of template invocation. Supports templates with parameters. | mapped node function (optional) call template - reference on reusable external template argument - one of
|
| SWITCH | Provides a way to many alternative transformations in the given place in template code. The wrapped template code is applied if none of switch cases is applicable and no default consequence is specified in template switch. |
mapped node function (optional) template switch - reference on template switch |
| COPY-SRC | Copies input node to output model. The wrapped template code is ignored. | mapped node function - computes the input node to be copied. |
| COPY-SRCL | Copies input nodes to output model. The wrapped template code is ignored. Can be used only for children with multiple aggregation cardinality. |
mapped nodes function - computes the input nodes to be copied. |
| MAP-SRC | Multifunctional macro, can be used for:
|
mapped node function (optional) mapping func function (optional) - performes none-template based transformation. If defined then the wrapped template code will be ignored. Parameters: standard + parentOutputNode - parent node in output model. post-processing function (optional) - give an access to output node. Parameters: standard + outputNode |
| MAP-SRCL | Same as MAP-SRC but can handle many new input nodes (similar to LOOP-macro) | mapped nodes function mapping func function (optional) post-processing function (optional) |
| Note Reference resolving by identifier is only supported in BaseLanguage. The identifier string for classes and class constructors may require (if class is not in the same output model) package name in square brackets preceding the class name: [package.name]ClassName |
Template Switch
Template switch is used in pair with SWITCH-macro (can be re-used in many different SWITCH-macro). Template switch consists of set of cases and one default case. Each switch case is a reduction rule, i.e. template switch contains list of reduction rules actually (see reduction rule).
The default case consequence can be one of:
- external template (ref)
- in-line template
- abandon input
- dismiss top rule
.. or can be omitted. In this case the template code surrounded by corresponding SWITCH-macro will be applied.
Template switch can inherit reduction rules from other switches via the extends property. When generator is executing a SWITCH-macro it tries to find most specific template switch (available in scope). Therefore the actually executed template switch is not necessarily the same as it is defined in template switch property in SWITCH-macro.
In the null-input message property user can specify an error, warning or info message, which will be shown in MPS messages view in case when the mapped node function in SWITCH-macro returns null (by default no messages are shown and macro is skipped altogether).
Generation Context (operations)
Generation context (genContext parameter in macro- and rule-functions) allows finding of nodes in output model, generating unique names and provides other useful functionality.
Generation context can be used not only in generator models but also in utility models - as a variable of type gencontext.
Operations of genContext are invoked using familiar dot-notation: genContext.operation
Finding Output Node
| get output <mapping label> | Returns output node generated by labeled conditional root rule. Issues an error if there are more than one matching output nodes. |
| get output <mapping label> for ( <input node> ) | Returns output node generated from the input node by labeled generator rule, macro or template fragment. Issues an error if there are more than one matching output nodes. |
| pick output <mapping label> for ( <input node> ) | Returns output node generated from the input node by labeled generator rule, macro or template fragment. Difference with previous operation is that this operation can automatically resolve the many-output-nodes conflict - it picks the output node which is visible in the given context (see search scope). |
| get output list <mapping label> for ( <input node> ) | Returns list of output nodes generated from the input node by labeled generator rule, macro or template fragment. |
| get copied output for ( <input node> ) | Returns output node which has been created by copying of the input node. If during the copying, the input node has been reduced but concept of output node is the same (i.e. it wasn't reduced into something totally different), then this is still considered 'copying'. Issues an error if there are more than one matching output nodes. |
Generating of Unique Name
unique name from <base name> in context <node>
The uniqueness is secured throughout the whole generation session.
Clashing with names that wasn't generated using this service is still possible.
The context node is optional, though we recommend to specify it to guarantee generation stability. If specified, then MPS tries its best to generated names 'contained' in a scope (usually a root node). Then when names are re-calculated (due to changes in input model or in generator model), this won't affect other names outside the scope.
Template Parameters
| #patternvar | Value of captured pattern variable |
| param | Value of template parameter |
Getting Contextual Info
| inputModel | Current input model |
| originalModel | Original input model |
| outputModel | Current output model |
| invocation context | Operation context (jetbrains.mps.smodel.IOperationContext java interface) associated with module - owner of the original input model |
| scope | Scope - jetbrains.mps.smodel.IScope java interface |
| templateNode | Template code surrounded by macro. Only used in macro-functions |
| get prev input <mapping label> | Returns input node that has been used for enclosing template code surrounded by the labeled macro. Only used in macro-functions. |
Transferring User Data
During a generation MPS maintains three maps user objects, each have different life span:
- session object - kept throughout whole generation session;
- step object - kept through a generation step;
- transient object - only live during a micro step.
Developer can access user object maps using an array (square brackets) notation:
The key can be any object (java.lang.Object).
| binding user data with particular node The session- and step-object cannot be used to pass a data associated with a particular input node across steps and micro-steps because neither an input node nor its id can serve as a key (output nodes always have different id). To pass such data use methods: putUserObject, getUserObject and removeUserObject defined in class jetbrains.mps.smodel.SNode. The data will be transferred to all output copies of the input node. The data will be also transferred to output node if a slight reduction (i.e. without changing of the node concept) took place while the node copying. |
Logging
Creates message in MPS message view. If the node parameter is specified then clicking on the message will navigate to that node. In case of an error message, MPS will also output some additional diagnostic info.
Utilities (Re-usable Code)
If you have duplicated code (in rules, macros, etc.) and want to say, extract it to re-usable static methods, then you must create this class in a separate, non-generator model.
If you create an utility class in the generator model (i.e. in a model with the 'generator' stereotype), then it will be treated as a root template (unused) and no code will be generated from it.
Mapping Script
Mapping script is a user code which is executed either before a model transformation (pre-processing script) or after it (post-processing script). It should be referenced from Mapping Configuration to be invoked as a part of it's generation step. Mapping script provides the ability to perform a non-template based model transformation.
Pre-processing scripts are also commonly used for collecting certain information from input model that can be later used in the course of template-based transformation. The information collected by script is saved as a transient-, step- or session-object (see generation context).
Script sample:
Properties:
| script kind |
|
| modifies model | only available if script kind = pre-process input model If set true and input model is the original input model, then MPS will create a transient input model before applying the script. If set false but script tries to modify input model, then MPS will issue an error. |
Code context:
| model | Current model |
| genContext | Generation context to access transient/session or step objects. |
| invocation context | Operation context (jetbrains.mps.smodel.IOperationContext java interface) associated with module - owner of the original input model |
The Generator Algorithm
The process of generation of target assets from an input model (generation session) includes 5 stages:
- Defining all generators that must be involved
- Defining the order of priorities of transformations
- Step-by-step model transformation
- Generating text and saving it to a file (for each root in output model)
- Post-processing assets: compiling, etc.
We will discuss the first three stages of this process in detail.
Defining the Generators Involved
To define the required generators, MPS examines the input model and determines which languages are used in it. Doing this job MPS doesn't make use of 'Used Languages' specified in the model properties dialog. Instead MPS examines each node in the model and gathers languages that are actually used.
From each 'used language' MPS obtains its generator module. If there are more than one generator module in a language, MPS chooses the first one (multiple generators for the same language are not fully supported in the current version of MPS). If any generator in this list depends on other generators (as specified in the 'depends on generators' property), then those generators are added to the list as well.
After MPS obtains the initial list of generators, it begins to scan the generator's templates in order to determine what languages will be used in intermediate (transient) models. The languages detected this way are handled in the same manner as the languages used in the original input model. This procedure is repeated until no more 'used languages' can be detected.
Explicit Engagement
In some rare cases, MPS is unable to detect the language whose generator must be involved in the model transformation. This may happen if that language is not used in the input model or in the template code of other (detected) languages. In this case, you can explicitly specify the generator engagement via the Languages Engaged on Generation section in the input model's properties dialog (Advanced tab).
Defining the Order of Priorities
As we discussed earlier, a generator module contains generator models, and generator models contain mapping configurations. Mapping configuration (mapping for short) is a set of generator rules. It is often required that some mappings must be applied before (or not later than, or together with) some other mappings. The language developer specifies such a relationship between mappings by means of mapping constraints in the generator properties dialog (see also Mapping Priorities and the Dividing Generation Process into Steps demo).
After MPS builds the list of involved generators, it divides all mappings into groups, according to the mapping priorities specified. All mappings for which no priority has been specified fall into the last (least-priority) group.
| tip You can check the mapping partitioning for any (input) model by selecting Show Mapping Partitioning action in the model's popup menu. The result of partitioning will be shown in the MPS Output View. |
Model Transformation
Each group of mappings is applied in a separate generation step. The entire generation session consists of as many generation steps as there were mapping groups formed during the mapping partitioning. The generation step includes three phases:
- Executing pre-mapping scripts
- Template-based model transformation
- Executing post-mapping scripts
The template-based model transformation phase consists of one or more micro-steps. The micro-step is a single-pass model transformation of input model into a transient (output) model.
While executing micro-step MPS follows the next procedure:
- Apply conditional root rules (only once - on the 1-st micro-step)
- Apply root mapping rules
- Copy input roots for which no explicit root mapping is specified (this can be overridden by means of the 'keep input root' option in root mapping rules and by the 'abandon root' rules)
- Apply weaving rules
- Apply delayed mappings (from MAP_SRC macro)
- Revalidate references in the output model (all reference-macro are executed here)
There is no separate stage for the application of reduction and pattern rules. Instead, every time MPS copies an input node into the output model, it attempts to find an applicable reduction (or pattern) rule. MPS performs the node copying when it is either copying a root node or executing a COPY_SRC-macro. Therefore, the reduction can occur at either stage of the model transformation.
MPS uses the same rule set (mapping group) for all micro-steps within the generation step. After a micro-step is completed and some transformations have taken place during its execution, MPS starts the next micro-step and passes the output model of the previous micro-step as input to the next micro-step. The whole generation step is considered completed if no transformations have occurred during the execution of the last micro-step, that is, when there are no more rules in the current rule set that are applicable to nodes in the current input model.
The next generation step (if any) will receive the output model of previous generation step as its input.
| tip Intermediate models (transient models) that are the output/input of generation steps and micro-steps are normally destroyed immediately after their transformation to the next model is completed. To keep transient models, enable the following option: Settings -> Generator Settings -> Save transient models on generation See also: |
Mapping Priorities
Mapping priorities are a set of mapping priority rules specifying an order of priority between sets of generator rules (mapping configurations).
Mapping priorities are specified in generator module property dialog.
See also: Generator Properties, Generation Process: Defining the Order of Priorities, Demo 6: Dividing Generation Process into Steps
Each mapping priority rule consists of left part, right part and priority symbol in the middle.
For instance:
Left part of priority rule can only refer to mapping configurations in this generator.
| Notation | Refers to |
|---|---|
| * | All mappings in this generator |
| modelName . * | All mappings in model |
| modelName . mappingName | Mapping configuration |
| { ... } | Set of mappings. An entry can be either modelName . * or modelName . mappingName |
Right part of priority rule can refer to mapping configurations in this generator as well as other generators visible in scope (see Generator Properties: dependency on other generators).
| Notation | Refers to |
|---|---|
| * | All mappings in this generator |
| modelName . * | All mappings in model |
| modelName . mappingName | Mapping configuration |
| { ... } | Set of mappings. An entry can be either modelName . * or modelName . mappingName |
| [languageName / generatorName : * ] | All mapping in external generator |
| [languageName / generatorName : modelName] | All mapping in external generator model |
| [languageName / generatorName : modelName . mappingName] | External mapping configuration |
| [languageName / generatorName : { ... }] | All mapping in external generator |
| [*:*] | All possible mappings |
Priority symbol
| Notation | Description |
|---|---|
| < | Mappings in left part are processed before mappings in right part |
| <= | Mappings in left part are processed not later than mappings in right part |
| == | Mappings in left part and right part are processed together |
Examples
If you're feeling like it's time for more practical experience, check out the generator demos.
The demos contain examples of usage of all concepts discussed above.
Defining A Typesystem For Your Language
What is a typesystem?
A typesystem is a part of a language definition used to assign types to the nodes in the models written in your language. The typesystem language is also used to check certain constraints on nodes and their types. Information about types of nodes is useful for:
- finding type errors
- checking conditions on nodes' types during generation to apply only appropriate generator rules
- providing information required for certain refactorings (e.g. for the "extract variable" refactoring)
- and more
Types
Any MPS node may serve as a type. To enable MPS to assign types to nodes of your language, you should create a language aspect for typesystem. The typesystem model for your language will be written in the typesystem language.
Inference Rules
The main concept of the typesystem language is an inference rule. An inference rule for a certain concept is mainly responsible for computing a type for instances of that concept.
An inference rule consists of a condition and a body. A condition is used to determine whether a rule is applicable to a certain node. A condition may be of two kinds: a concept reference or a pattern. A rule with a condition in the form of concept reference is applicable to every instance of that concept and its subconcepts. A rule with a pattern is applicable to nodes that match the pattern. A node matches a pattern if it has the same properties and references as the pattern, and if its children match the pattern's children. A pattern also may contain several variables which match everything.
The body of an inference rule is a list of statements which are executed when a rule is applied to a node. The main kind of statements of typesystem language are statements used for creating equations and inequations between types.
Inference Methods
To avoid duplications, one may want to extract identical parts of code of several inference rules to a method. An inference method is just a simple Base Language method marked with an annotation "@InferenceMethod". There are several language constructions you may use only inside inference rules and replacement rules and inference methods, they are: "typeof" expressions, equations and inequations, "when concrete" statements, type variable declarations and type variable references, and invocations of inference methods. That is made for not to use such constructions in arbitrary methods which may be called in arbitrary context, maybe not during type checking.
Equations And Inequations
The main process which is performed by typesystem engine is the process of solving equations and inequations between types. A language designer tells the engine which equations it should solve by writing them in inference rules. To add an equation into an engine, the following statement is used: expr1 :==: expr2, where expr1 and expr2 are expressions which evaluate to a node.
Consider the following use case. You want to say that the type of a local variable reference is equal to a type of variable declaration. So, you write typeof (varRef) :==: typeof (varRef.localVariableDeclaration), and that's all. The typesystem engine will solve such equations automatically.
The above-mentioned expression typeof(expr) (where expr must evaluate to an MPS node) is a language construction which returns a so-called type variable which serves as a type of that node. Type variables become concrete types during the process of equation solving.
In certain situations you want to say that a certain type doesn't have to exactly equal another type, but also may be a subtype or a supertype of that type. For instance, the type of the actual parameter of a method call does not necessarily have to be the same type as that of the method's formal parameter - it can be its subtype. That is, a method which requires Object as a parameter may be applied to a String.
To express such a constraint, you may use an inequation instead of an equation. An inequation expresses the fact that a certain type should be a subtype of another type. It is written as follows: expr1 :<=: expr2.
Weak And Strong Subtyping
A relationship of subtyping is useful for several different cases. You want a type of an actual parameter to be a subtype of formal parameter type, or you want a type of an assigned value to be a subtype of variable's declared type; in method calls or field access operations you want a type of an operand to be a subtype of a method's declaring class.
Sometimes such demands are somewhat controversial: consider, for instance, two types, int and Integer, which you want to be interchangeable when you pass parameters of such types to a method: if a method is doSomething(int i), it is both legal to call doSomething(1) and doSomething(new Integer(1)). But when these types are used as types of operand of, say, method call, the situation is the different: you shouldn't be able to call a method of an expression of type int, of an integer constant for example. So, we have to conclude that in one sense int and Integer are subtypes of each other, while in other sense they are not.
For solving such a controversy, we introduce two relationships of subtyping: namely, weak and strong subtyping. Weak subtyping will follow from strong subtyping: if a node is a strong subtype of another node, then it is it's weak subtype also.
Then, we can say about our example, that int and Integer are weak subtypes of each other, but they are not strong subtypes. Assignment and parameters passing require weak subtyping only, method calls require strong subtyping.
When you create an inequation in you typesystem, you may choose it to be a strong or weak inequation. Also subtyping rules, those which state subtyping relationship (see below), can be either weak or strong. A weak inequation looks like :<=:, a strong inequation looks like :<<=:
In most cases you want to state strong subtyping, and to check weak subtyping. If you are not sure which subtyping you need, use weak one for inequations, strong one for subtyping rules.
Subtyping Rules
When the typesystem engine solves inequations, it requires information about whether a type is a subtype of another type. But how does the typesystem engine know about that? It uses subtyping rules. Subtyping rules are used to express subtyping relationship between types. In fact, a subtyping rule is a function which, given a type, returns its immediate supertypes.
A subtyping rule consists of a condition (which can be either a concept reference or a pattern) and a body, which is a list of statements that compute and return a node or a list of nodes that are immediate supertypes of the given node. When checking whether some type A is a supertype of another type B, the typesystem engine applies subtyping rules to B and computes its immediate supertypes, then applies subtyping rules to those supertypes and so on. If type A is among the computed supertypes of type B, the answer is "yes".
By default, subtyping stated by subtyping rules is a strong one. If you want to state only weak subtyping, set "is weak" property of a rule to "true".
Comparison Inequations And Comparison Rules
Consider you want to write a rule for EqualsExpression (operator == in Java, BaseLanguage and some other languages): you want left operand and right operand of EqualsExpression to be comparable, that is either type of a left operand should be a (non-strict) subtype of a right operand, or vice versa. To express this, you write a comparison inequation, in a form expr1 :~: expr2, where expr1 and expr2 are expressions which represent types. Such an inequation is fulfilled if expr1 is a subtype of expr2 (expr1 <: expr2), or expr2 <: expr1.
Then, consider that, say, any Java interfaces should also be comparable, even if such interfaces are not subtypes of one another. That is because there always can be written a class which implements both of interfaces, so variables of interface types can contain the same node, and variable of an interface type can be cast to any other interface. hence a equation, cast, instanceof expressions with both types being interface types should be legal (and in Java they are such).
To state such a comparability which does not follow from subtyping relationship, one should use comparison rules. A comparison rule consists of two conditions for the two applicable types and a body which returns true if the types are comparable or false if they are not.
Here's the comparison rule for interface types:
comparison rule interfaces_are_comparable
applicable for concept = ClassifierType as classifierType1 , concept = ClassifierType as classifierType2
rule {
if (classifierType1.classifier.isInstanceOf(Interface) && classifierType2.classifier.isInstanceOf(Interface)) {
return true;
} else {
return false;
}
}
Quotations
A quotation is a language construction that lets you easily create a node with a required structure. Of course, you can create a node using smodelLanguage and then populate it with appropriate children, properties and references, using the same smodelLanguage. But there's a simpler - and more visual - way to accomplish that.
A quotation is an expression whose value is the MPS node written inside the quotation. Think about a quotation as a "node literal", a construction similar to numeric constants and string literals. That is, you write a literal if you statically know what value do you mean. So inside a quotation you don't write an expression which evaluates to a node, you rather write the node itself. That is, an expression 2 + 3 evaluates to 5, an expression < 2 + 3 > (angled braces being quotation braces) evaluates to a node PlusExpression with leftOperand being IntegerConstant 3 and rightOperand being IntegerConstant 5.
Antiquotations
For it is a literal, a value of quotation should be known statically. So in case where you know some parts (i.e. children, referents or properties) of your node only dynamically i.e. those parts should be evaluated in runtime and are not known in design time, then you can't use just a quotation to create a node with such parts.
The good news, however, is that if you know the most part of a node statically and you want to replace only several parts of by dynamically-evaluated nodes you can use antiquotations. An antiquotation can be of 4 types: child, reference, property and list antiquotation. They both contain an expression which evaluates dynamically to replace a part of quoted node by its result. Child and referent antiquotations evaluate to a node, property antiquotation evaluates to string and list antiquotation evaluates to list of nodes.
For instance, you want to create a ClassifierType with the class ArrayList, but its type parameter is known only dynamically, for instance by calling a method, say, "computeMyTypeParameter()".
Thus, you write the following expression: < ArrayList < %( computeMyTypeParameter() )% > >. The construction %(...)% here is that very antiquotation.
One also may antiquotate reference targets and property values, with ^(...)^ and $(...)$, respectively; or a list of children of one role, using *(...)* .
a) If you want to replace a node somewhere inside a quoted node with a node evaluated by an expression, you use node antiquotation, that is %( )%. As you may guess there's no sense to replace the whole quoted node with an antiquotation with an expression inside, because you can write such an expression simply in your program instead.
So such an antiquotation is used to replace children, grandchildren, great-grandchildren and other descendants of a quoted node. Thus, an expression inside of antiquotation should return a node. To write such an antiquotation, position your caret on a cell for a child and type "%".
b) If you want to replace a target of a reference from somewhere inside a quoted node with a node evaluated by an expression, you use reference antiquotation, that is ^(...)^. To write such an antiquotation, position your caret on a cell for a referent and type "^".
c) If you want to replace a child (or some more deeply located descendant) which is of a multiple-cardinality role, and if for that reason you may want to replace it not with a single node but rather with several ones, then use child list (simply list for brevity) antiquotations, *( )*. An expression inside a list antiquotation should return a list of nodes, that is of type nlist<..> or compatible type (i.e. list<node<..>> is ok, too, as well as some others). To write such an antiquotation, position your caret on a cell for a child inside a child collection and type "*". You can not use it on an empty child collection, so before you press "*" you have to enter a single child inside it.
d) If you want to replace a property value of a quoted node by a dynamicаlly calculated value, use property antiquotation $( )$. An expression inside a quotation should return string, which will be a value for an antiquoted property of a quoted node. To write such an antiquotation, position your caret on a cell for a property and type "$".
Examples Of Inference Rules
Here are the simplest basic use cases of an inference rule:
- to assign the same type to all instances of a concept (useful mainly for literals):
applicable to concept = StringLiteral as nodeToCheck { typeof (nodeToCheck) :==: < String > } - to equate a type of a declaration and the references to it (for example, for variables and their usages):
applicable to concept = VariableReference as nodeToCheck { typeof (nodeToCheck) :==: typeof (nodeToCheck.variableDeclaration) } - to give a type to a node with a type annotation (for example, type of a variable declaration):
applicable to concept = VariableDeclaration as nodeToCheck { typeof (nodeToCheck) :==: nodeToCheck.type } - to establish a restriction for a type of a certain node: useful for actual parameters of a method, an initializer of a type variable, the right-hand part of an assignment, etc.
applicable to concept = AssignmentExpression as nodeToCheck { typeof (nodeToCheck.rValue) :<=: typeof (nodeToCheck.lValue) }
Type Variables
Inside the typesystem engine, a type may be either a concrete type (a node) or a so-called type variable. Also, it may be a node which contains some type variables as its children or further descendants. A type variable is an undefined type which may then become a concrete type, as a result of solving equations that contain this type variable.
Type variables appear at runtime mainly as a result of a "typeof" operation, but you can create them manually if you want to. There's a statement TypeVarDeclaration in typesystem lanuage to do so. You write it like "var T" or "var X" or "var V", i.e. "var" followed by the name of a type variable. Then you may use your variable, for example, in antiquotations to create a node with type variables inside.
Example: an inference rule for "for each" loop. A "for each" loop in Java consists of a loop body, an iterable to iterate over, and a variable into which the next member of an iterable is assigned before the next iteration. An iterable should be either an instance of a subclass of the Iterable interface, or an array. To simplify the example, we don't consider the case where the iterable is an array. Therefore, we need to express the following: an iterable's type should be a subtype of an Iterable of something, and the variable's type should be a supertype of that very something. For instance, you can write the following:
for (String s : new ArrayList<String>(...)) {
...
}
or the following:
for (Object o : new ArrayList<String>(...)) {
...
}
Iterables in both examples above have the type ArrayList<String> which is a subtype of Iterable<String>. Variables have types String and Object, respectively, both of which are subtypes of String.
As we see, an iterable's type should be a subtype of an Iterable of something, and the variable's type should be a supertype of that very something. But how to say "that very something" in typesystem language? The answer is, it's a type variable that we use to express the link between the type of an iterable and the type of a variable. So we write the following inference rule:
applicable for concept = ForeachStatement as nodeToCheck
{
var T ;
typeof ( nodeToCheck . iterable ) :<=: Iterable < %( T )% >;
typeof ( nodeToCheck . variable ) :>=: T ;
}
Meet and Join types
Meet and Join types are special types which are treated differently by the typesystem engine. Technically Meet and Join types are instances of MeetType and JoinType concepts, respectively. They may have an arbitrary number of argument types which could be any nodes. Semantically, a Join type is a type which is a supertype of all its arguments, and a node which has a type Join(T1|T2|..Tn) can be regarded as if it has type T1 or type T2 or... or type Tn. a Meet type is a type which is a subtype of its every argument, so one can say that a node which has a type Meet(T1&T2&..&Tn) inhabits type T1 and type T2 and.. and type Tn. The separators of arguments of Join and Meet types (i.e. "|" and "&") are chosen respectively to serve as a mnemonics.
Meet and Join types are not unuseful. Meet types appear even in MPS Base Language (which is very close to Java), for instance the type of such an expression:
true ? new Integer(1) : "hello"
is Meet(Serializable & Comparable), because both Integer (the type of new Integer(1)) and String (the type of "hello") implement both Serializable and Comparable.
Join type is useful when, say, you want some function-like concept return values of two different types (node or list of nodes, for instance). Then you should make type of its invocation be Join(node<> | list<node<>>).
You can create Meet and Join types by yourself, if you need to. Use quotations to create them, just as with other types and other nodes. The concepts of Meet and Join types are MeetType and JoinType, as it is said above.
"When Concrete" Blocks
Sometimes one may want not only to write equations and inequations for a certain types, but to perform some complex analysis with type structure. That is, inspect inner structure of a concrete type: its children, children of children, referents, etc.
It may seem that one just may write typeof(some expression), and then analyze this type. The problem is, however, that one can't just inspect a result of "typeof" expression because it may be a type variable at that moment. Although a type variable usually will become a concrete type at some moment, it can't be guaranteed that it is concrete in some given point of your typesystem code.
To solve such a problem you can use a "when concrete" block.
when concrete ( expr as var ) {
body
}
Here, "expr" is an expression which will evaluate to a mere type you want to inspect (not to a node type of which you want to inspect), and "var" is a variable to which an expression will be assigned. Then this variable may be used inside a body of "when concrete" block. A body is a list of statements which will be executed only when a type denoted by "expr" becomes concrete, thus inside the body of a when concrete block you may safely inspect its children, properties, etc. if you need to.
If you have written a when concrete block and look into its inspector you will see two options: "is shallow" and "skip error". If you set "is shallow" to "true", the body of your when concrete block will be executed when expression becomes shallowly concrete, i.e. becomes not a type variable itself but possibly has type variables as children or referents. Normally, if your expression in condition of when concrete block is never concrete, then an error is reported. If it is normal for a type denoted by your expression to never become a concrete type, you can disable such error reporting by setting "skip error" to true.
Overloaded Operators
Sometimes an operator (like +, -, etc.) has different semantics when applied to different values. For example, + in Java means addition when applied to numbers, and it means string concatenation if one of its operands is of type String. When the semantics of an operator depends on the types of its operands, it's called operator overloading. In fact, we have many different operators denoted by the same syntactic construction.
Let's try to write an inference rule for a plus expression. First, we should inspect the types of operands, because if we don't know the types of operands (whether they are numbers or Strings), we cannot choose the type for an operation (it will be either a number or a String). To be sure that types of operands are concrete we'll surround our code with two when concrete blocks, one for left operand's type and another one for right operand's type.
when concrete(typeof(plusExpression.leftExpression) as leftType) {
when concrete(typeof(plusExpression.rightExpression) as rightType) {
...
}
}
Then, we can write some inspections, where we check whether our types are strings or numbers and choose an appropriate type of operation. But there will be a problem here: if someone writes an extension of baseLanguage, where one wants to use plus expression for addition of some other entities, say, matrices or dates, this person won't be able to use plus expression because types for plus expression are hard-coded in the inference rule for it. So, we need an extension point to allow language-developers to overload existing binary operations.
Typesystem language has such an extension point. It consists of:
- overloading operation rules and
- a construct which provides a type of operation by operation and types of its operands.
For instance, a rule for PlusExpression in baseLanguage is written as follows:
when concrete(typeof(plusExpression.leftExpression) as leftType) {
when concrete(typeof(plusExpression.rightExpression) as rightType) {
node<> opType = operation type( plusExpression , leftType , rightType );
if (opType.isNotNull) {
typeof(plusExpression) :==: opType;
} else {
error "+ can't be applied to these operands" -> plusExpression;
}
}
}
Here, "operation type" is a construct which provides a type of an operation according to types of operation's left operand's type, right operand's type and the operation itself. For such a purpose it uses overloading operation rules.
Overloaded Operation Rules
Overloaded operation rules reside within a root node of concept OverloadedOpRulesContainer. Each overloaded operation rule consists of:
- an applicable operation concept, i.e. a reference to a concept of operation to which a rule is applicable (e.g. PlusExpression);
- left and right operand type restrictions, which contain a type which restricts a type of left/right operand, respectively. A restriction can be either exact or not, which means that a type of an operand should be exactly a type in a restriction(if restriction is exact), or its subtype(if not exact), for a rule to be applicable to such operand types;
- a function itself, which returns a type of operation by operation, left and right operand types.
Here's an example of one of overloading operation rules for PlusExpression in baseLanguage:
operation concept: PlusExpression
left operand type: <Numeric>.descriptor is exact: false
right operand type: <Numeric>.descriptor is exact: false
operation type:
(operation, leftOperandType, rightOperandType)->node< > {
if (leftOperandType.isInstanceOf(NullType) || rightOperandType.isInstanceOf(NullType)) {
return null;
} else {
return Queries.getBinaryOperationType(leftOperandType, rightOperandType);
}
}
Replacement Rules
Motivation
Consider the following use case: you have types for functions in your language, e.g. (a 1, a 2,...a N ) -> r, where a 1, a 2, .., a N, and r are types: a K is a type of K-th function argument and r is a type of a result of a function. Then you want to say that your function types are covariant by their return types and countervariant by their argument types. That is, a function type F = (T 1, .., T N) -> R is a subtype of a function type G = (S 1, .., S N) -> Q (written as F <: G) if and only if R <: Q (covariant by return type) and for any K from 1 to N, T K :> S K (that is, countervariant by arguments' types).
The problem is, how to express covariance and countervariance in typesystem language? Using subtyping rules you may express covariance by writing something like this:
nlist < > result = new nlist < > ;
for ( node < > returnTypeSupertype : immediateSupertypes ( functionType . returnType ) ) {
node <FunctionType> ft = functionType . copy;
ft . returnType = returnTypeSupertype;
result . add ( ft ) ;
}
return result ;
Okay, we have collected all immediate supertypes for a function's return type and have created a list of function types with those collected types as return types and with original argument types. But, first, if we have many supertypes of return type, it's not very efficient to perform such an action each time we need to solve an inequation and second, although now we have covariance by function's return type, we still don't have countervariance by function's arguments' types. We can't collect immediate subtypes of a certain type because subtyping rules give us supertypes, not subtypes.
In fact, we just want to express the abovementioned property: F = (T 1, .., T N) -> R <: G = (S 1, .., S N) -> Q (written as F <: G) if and only if R <: Q and for any K from 1 to N, T K :> S K . For this and similar purposes typesystem language has a notion called "replacement rule."
What's a replacement rule?
A replacement rule is another way to solve inequations. While the standard way is to transitively apply subtyping rules to a should-be-subtype until a should-be-supertype is found among the results (or is never found among the results), a replacement rule, if applicable to a inequation, removes the inequation and then executes its body (which usually contains "create equation" and "create inequation" statements).
Examples
A replacement rule for above-mentioned example is written as follows:
replacement rule FunctionType_subtypeOf_FunctionType
applicable for concept = FunctionType as functionSubType <: concept = FunctionType as functionSuperType
rule {
if ( functionSubType . parameterType . count != functionSuperType . parameterType . count ) {
error " different parameter numbers " -> equationInfo . getNodeWithError ( ) ;
return ;
}
functionSubType . returnType :<=: functionSuperType . returnType ;
foreach ( node < > paramType1 : functionSubType . parameterType ; node < > paramType2 : functionSuperType . parameterType ) {
paramType2 :<=: paramType1 ;
}
}
Here we say that a rule is applicable to a should-be-subtype of concept FunctionType and a should-be-supertype of concept FunctionType. The body of a rule ensures that the number of parameter types of function types are equal, otherwise it reports an error and returns. If the numbers of parameter types of both function types are equal, a rule creates an inequation with return types and appropriate inequation for appropriate parameter types.
Another simple example of replacement rules usage is a rule which states that a Null type (a type of null literal) is a subtype of every type except primitive ones. Of course, we can't write a subtyping rule for Null type which returns a list of all types. Instead, we write the following replacement rule:
replacement rule any_type_supertypeof_nulltype
applicable for concept = NullType as nullType <: concept = BaseConcept as baseConcept
rule {
if ( baseConcept . isInstanceOf ( PrimitiveType ) ) {
error " null type is not a subtype of primitive type " -> equationInfo . getNodeWithError ( ) ;
}
}
This rule is applicable to any should-be-supertype and to those should-be-subtypes which are Null types. The only thing this rule does is checking whether should-be-supertype is an instance of PrimitiveType concept. If it is, the rule reports an error. If is not, the rule does nothing, therefore the inequation to solve is simply removed from the typesystem engine with no further effects.
Different semantics
A semantic of a replacement rule, as explained above, is to replace an inequation with some other equations and inequations or to perform some other actions when applied. This semantic really doesn't state that a certain type is a subtype of another type under some conditions. It just defines how to solve an inequation with those two types.
For example, suppose that during generation you need to inspect whether some statically unknown type is a subtype of String. What will an engine answer when a type to inspect is Null type? When we have an inequation, a replacement rule can say that it is true, but for this case its abovementioned semantics is unuseful: we have no inequations, we have a question to answer yes or no. With function types, it is worse because a rule says that we should create some inequations. So, what do we have to do with them in our use case?
To make replacement rules usable when we want to inspect whether a type is a subtype of an another type, a different semantic is given to replacement rules in such a case.
This semantic is as follows: each "add equation" statement is treated as an inspection of whether two nodes match; each "add inequation" statement is treated as an inspection of whether one node is a subtype of another; each report error statement is treated as "return false."
Consider the above replacement rule for function types:
replacement rule FunctionType_subtypeOf_FunctionType
applicable for concept = FunctionType as functionSubType <: concept = FunctionType as functionSuperType
rule {
if ( functionSubType . parameterType . count != functionSuperType . parameterType . count ) {
error " different parameter numbers " -> equationInfo . getNodeWithError ( ) ;
return ;
}
functionSubType . returnType :<=: functionSuperType . returnType ;
foreach ( node < > paramType1 : functionSubType . parameterType ; node < > paramType2 : functionSuperType . parameterType ) {
paramType2 :<=: paramType1 ;
}
}
In a different semantic, it will be treated as follows:
boolean result = true;
if ( functionSubType . parameterType . count != functionSuperType . parameterType . count ) {
result = false;
return result;
}
result = result && isSubtype( functionSubType . returnType <: functionSuperType . returnType );
foreach ( node < > paramType1 : functionSubType . parameterType ; node < > paramType2 : functionSuperType . parameterType ) {
result = result && isSubtype (paramType2 <: paramType1) ;
}
return result;
So, as we can see, another semantic is quite an intuitive mapping between creating equations/inequations and performing inspections.
Advanced features of typesystem language
Check-only inequations
Basically, inequations may affect nodes' types, for instance if one of the inequation part is a type variable, it may become a concrete type because of this inequation. But, sometimes one does not want a certain inequation to create types, only to check whether such an inequation is satisfied. We call such inequations check-only inequations. To mark an inequation as a check-only, one should go to this inequation's inspector and should set a flag "check-only" to "true". An "less or equals" sign for check-only inequation visually is gray, while for normal ones it is black, so you can see whether an inequation is check-only without looking at its inspector.
Dependencies
When writing a generator for a certain language (see generator), one may want to ask for a type of a certain node in generator queries. When generator generates a model, such a query will make typesystem engine do some typechecking to find out the type needed. Performing full typechecking of a node's containing root to obtain the node's type is expensive and almost always unnecessary. In most cases, the typechecker should check only the node given. In more difficult cases, obtaining the type of a given node may require checking its parent or maybe a further ancestor. The typechecking engine will check a given node if the computed type is not fully concrete (i.e. contains one or more type variables); then the typechecker will check the node's parent, and so on.
Sometimes there's an even more complex case: the type of a certain node being computed in isolation is fully concrete; and the type of the same node - in a certain environment - is fully concrete also but differs from the first one. In such a case, the abovementioned algorithm will break, returning the type of an node as if being isolated, which is not the correct type for the given node.
To solve this kind of problem, you can give some hints to the typechecker. Such hints are called dependencies - they express a fact that a node's type depends on some other node. Thus, when computing a type of a certain node during generation, if this node has some dependencies they will be checked also, so the node will be type-checked in an appropriate environment.
A dependency consists of a "target" concept (a concept of a node being checked, whose type depends on some other node), an optional "source" concept (a concept of another node on which a type of a node depends), and a query which returns dependencies for a node which is being checked, i.e. a query returns a node or a set of nodes.
For example, sometimes a type of a variable initializer should be checked with the enclosing variable declaration to obtain the correct type. A dependency which implements such a behavior may be written as follows:
target concept: Expression find source: (targetNode)->JOIN(node< > | Set<node< >>) {
if ( targetNode / . getRole_ ( ) . equals ( " initializer " ) ) {
return targetNode . parent ;
}
return null ;
}
source concept(optional): <auto>
That means the following: if the typechecker is asked for a type of a certain Expression during generation, it will check whether such an expression is of a role initializer, and if it is, will say that not only the given Expression but also its parent should be checked to get the correct type for the given Expression.
Using a typesystem
If you have defined a typesystem for a language, a typechecker will automatically use it in editors to highlight opened nodes with errors and warnings. You may additionally want also to use the information about types in queries, like editor actions, generator queries, etc. You may want to use the type of a node, or you may want to know whether a certain type is a subtype of another one, or you may want to find a supertype of a type which has a given form.
Type Operation
You may obtain a type of a node in your queries using type operation. Just write <expr>.type, where <expr> is an expression which is evalouated to a node.
Do not use type operation inside inference rules and inference methods! Inference rules are used to compute types, and type operation returns an already computed type.
Is Subtype expression
To inspect whether one type is a subtype of another one, use "is subtype" expression. Write isSubtype( type1 :< type2 ) or isStrongSubtype( type1 :<< type2 ), it will return "true" if type1 is a subtype of type2, or if type1 is a strong subtype of type2, respectively.
Coerce expression
A result of a coerce expression is a boolean value which says whether a certain type may be coerced to a given form, i.e. whether this type has a supertype which has a given form (satisfies a certain condition). A condition could be written either as a reference to a concept declaration, that means that a sought-for supertype should be an instance of this concept; or as a pattern, which a sought-for supertype matches with.
A coerce expression is written coerce( type :< condition ) or coerceStrong( type :<< condition ), what is a condition has just been discussed above.
Coerce Statement
A coerce statement consists of a list of statements which are executed if a certain type can be coerced to a certain form. It is written as following:
coerce ( type :< condition ) {
...
} else {
...
}
If a type can be coerced so as to satisfy a condition, first block will be executed, otherwise a block after "else" will be executed. The supertype to which a type is coerced can be used inside the first block of coerce statement. If a condition is a pattern and contains some pattern variables which match with parts of the supertype to which a type is coerced, such pattern variables can also be used inside the first block of the coerce statement.
Data Flow
A language's data flow aspect allows you to find unreachable statements, detect unused assignments, check whether a variable might not be initialized before it's read, and so on. It also allows performing some code transformations, for example the 'extract method' refactoring.
Most users of data flow analyses aren't interested in details of its inner working, but are interested in getting the results they need. They want to know which of their statements are unreachable, and what can be read before it's initialized. In order to shield a user from the complexities of these analyses, we provide assembly-like intermediate language into which you translate your program. After translation, this intermediate presentation is analyzed and a user can find which of the statements of original language are unreachable etc.
For example here is the translation of a 'for' loop from baseLanguage:
First, we translate the expression for node.iterable. Then we emit a label so we can jump after it. Then we perform a conditional jump after the current node. Then we emit code for writing to node.variable. This means that we change the value of node.variable on each iteration. We don't need to know what we write to node.variable, since this information isn't used by our analysis. Finally, we emit code for the loop's body, and jump to the previously emitted label.
Commands of intermediate language
Here are the commands of our intermediate language:
- read x - reads a variable x
- write x - writes to variable x
- jump before node - jumps before node
- jump after node - jumps after node
- jump label - jumps to label
- ifjump ((before|after)node)| label - conditional jump before/after node / to label
- code for node - insert code for node
- ret - returns from current subroutine
Can be unreachable
Some commands shouldn't be highlighted as unreachable. For example we might want to write some code like this:
If you generate data flow intermediate code for this statement, the last command: jump after condition will be unreachable. On the other hand, it's a legal base language statement, so we want to ignore this command during reachable statement analysis. To do so we mark it is as 'may be unreachable,' which is indicated by curly braces around it. You can toggle this settings with the appropriate intention.
Links:
http://www.itu.dk/people/brabrand/UFPE/Data-Flow-Analysis/static.pdf - good introduction to static analyses including data flow and type systems.
TextGen language aspect
Introduction
The TextGen language aspect defines a model to text transformation. It comes in handy each time you need to convert your models into the text form directly. The language contains constructs to print out text, transform nodes into text values and give the output some reasonable layout.
Operations
The append command performs the transformation and adds resulting text to the output. You can use found error command to report problems in the model. The with indent command demarcates blocks with increased indentation. Alternatively, the increase depth and decrease depth commands manipulate the current indentation depth without being limited to a block structure. The indent buffer command applies the current indentation (as specified by with ident or increase/decrease depth) for the current line.
| Operation | Arguments |
|---|---|
| append | any number of:
|
| found error | error text |
| decrease depth | decrease indentation level from now onwards |
| increase depth | increase indentation level from now on |
| indent buffer | apply indentation to the current line |
| with indent { <code> } | increase indentation level for the <code> |
Examples
- Here is an example of the text gen component for the ForeachStatement (jetbrain.mps.baseLanguage).
- This is an artificial example of the text gen:
producing following code block containing a number of lines with indentation:
In order to integrate with MPS IDE, you can use plugin aspect of your language. There are a number of entities that can be used in your language's plugin. This chapter describes all of them.
Actions and action groups
One can add custom actions to any menu in MPS by using action and action group entities.
The action describes one concrete action. Action groups are named lists of actions intended for structuring of actions - adding them to other groups and MPS groups (which represent menus themselves) and combining them into popup menus. You can also create groups with dynamically changing contents.
| Tutorial A quick and simple tutorial by Federico Tomassetti on how to create action and show it in a context menu is available here: http://www.federico-tomassetti.it/tutorial-how-to-add-an-action-to-the-jetbrains-metaprogramming-system/ |
How to add new actions to existing groups?
In order to add new actions to existing groups, the following should be done:
- actions should be described
- described actions should be composed into groups
- these groups should be added to existing groups (e.g. to predefined MPS groups to add new actions to MPS menus).
Predefined MPS groups are stored in jetbrains.mps.ide.actions model, which is an accessory model to pluginLanguage, so you don't need to import it into your model.
Action structure
Action properties
Name - The name of an action. You can give any name you want, the only obvious constraint is that the names must be unique in scope of the model.
Mnemonic - if mnemonic is specified, the action will be available via alt+mnemonic shortcut when any group that contains this action is displayed. Note that the mnemonic (if specified) must be one of the chars in action's caption. Mnemonic is displayed as an underlined symbol in action's caption.
Execute outside command - all operations with MPS models are executed within commands. Command is an item in the undo list (you don't control it manually, MPS does it for you), so the user can undo changes brought into the model by action's execution. Also, all the code executed in a command, has read-write access to the model but if you show dialogs inside of command, it can cause deadlock, so, if you are not using UI in your action, set this to false, otherwise it should be set to true and control read/write access manually with read action and command statements.
Caption - the string representing an action in menus
Description - this string (if specified) will be displayed in the status bar when this action is active (selected in any menu)
Keystroke - if specified, action will be available via this keystroke even if the containing group is not open (butthis action must be accessible through a chain of menus in current place). The first field of the keystroke editor specifies, which control keys should be pressed, while the second one goes for keycode.
Icon - this icon will be displayed near the action in all menus. You can select the icon file by pressing "..." button. Note that the icon must be placed near your language (because it's stored not as an image, but as a path relative to the language's root)
Construction parameters
Each action can be parameterized at construction time using construction parameters. This can be any data determining action's behavior. Thus, a single action that uses construction parameters can represent multiple different behaviors. To manage actions and handle keymaps MPS needs a unique identifier for each concrete behavior represented by an action. So, toString function was introduced for each construction parameter (can be seen in inspector). For primitive types there is no need to specify this function explicitly - MPS can do it automatically. For more complex parameters, you need to write this function explicitly so that for each concrete behavior of an action there is a different set of values returned from toString() functions.
Enable/disable action control
Is always visible flag - if you want your action to be visible even in disabled state, set this to true, otherwise to false.
Context parameters - specifies which items must be present in current context for the action to be able to execute. They are extracted from the context before any method is executed. There are two types of context parameters - optional and non-optional, this can be set in inspector. If some non-optional parameters were not extracted, the action state is set to disabled and isApplicable/update/execute methods are not executed. If all action parameters were extracted, you can use their values in all action methods. There are 2 types of action parameters - simple and complex action parameters.
- Simple action parameters allow to simply extract all available data from current data context. The data is provided "by key", so you should specify the name and the key in declaration. The type of the parameter will be set automatically.
- Complex action parameters were introduced to perform some frequently used checks and typecasts. Now there are 3 types available for context parameter of this type: node<concept>, nlist<concept>, model.
- node<concept> - currently selected node which is an instance of specified concept. Action won't be enabled if selected node isn't an instance of this concept.
- nlist<concept> - currently selected nodes. It is checked that all nodes are instances of concept (if specified). As with node<concept>, the action won't be enabled if the check fails.
- model - current model
Is Applicable / update - In cooperation with context parameters, this controls enabled/disabled state of the action. The isApplicable method returns the new state of an action, update method is designed to update the state manually. You can also update any of your action's properties (caption, icon etc.) by accessing action's presentation via event.getPresentation(). This method is executed only if all context parameters were extracted from the context.
| Note Use isApplicable only if you don't modify presentation manually. This will work in isApplicable, but update method is more suitable place for complex presentation manipulations. |
Execute - this method is executed when the action is performed. It is guaranteed that it is executed only if the action's update method for the same event left the action in active state (or isApplicable returned true) and all required context parameters are present in current context and were filled in.
Methods - in this section you can declare utility methods.
Group structure
Group describes a set of actions and provides the information about how to modify other groups with current group.
Presentation
Name - The name of the group. You can give any name you want, the only obvious constraint is that the names must be unique in scope of the model.
isPopup - if this is true, the group represents a popup menu, otherwise it represents a list of actions.
Caption (popup=true) - string that will be displayed as a name of a popup menu
Mnemonic (popup=true) - if mnemonic is specified, popup menu will be available via alt+mnemonic shortcut when any group that contains it is displayed. Note that the mnemonic (if specified) must be one of the chars in caption. Mnemonic is displayed as an underlined symbol in popum menu caption.
Is invisible when disabled - if set to true, group will not be shown in case it has no enabled actions or is disabled manually in update().
Contents
There are 3 possibilities to describe group contents:
Element list - this is just a static list of actions, groups and labels (see modifications). The available elements are:
- ->name - an anchors. Anchors are used for modifying one group with another. See Add statement section for details.
- <---> - separator
- ActionName[parameters] - an actions.
Build - this alternative should be used in groups, which contents are static, but depend on some initial conditions - the group is builded once and is not updated. Use add statement to add elements inside build block.
Update - this goes for dynamically changing groups. Group is updated every time before it is rendered.
| Note In update/build blocks use add statement to add group members. |
Modifications and labels
Add to <group> at position <position> - this statement adds current group to <group> at the given position. Every group has a <default> position, which tells to add the current group to the end of the target group. Some groups can provide additional positions by adding so-called anchors into themselves. Adding anchors is described in contents section. The anchor itself is invisible and represents a position, in which a group can be added.
Note
|
actionGroup <...> expression
There is a specific expression available in pluginLanguage to access any registered group - actionGroup<group> expression.
Shortcuts
All the actions added by plugins are visible in Settings->Keymap and Settings->Menus and Toolbars. This means that any user can customize shortcuts for all MPS actions. The shortcut specified in action is the one that will be added to default shortcuts scheme.
Note
|
Editor Tabs
Look at any concept declaration. Have you noticed the tabs at the bottom of the editor? You are able to add the same functionality to concepts of your language.
What is the meaning of these tabs? The answer is pretty simple - they contain editors for some aspects of "base" node. You can also create corresponding aspect nodes from some of them. Each tab can be either single-tabbed (which means that only one node is displayed in it, e.g. editor tab) or multi-tabbed (if multiple nodes can be created for this aspect of the base node, see Typesystem tab for example).
How the editor for node is created? When you open some node, call it N, MPS tries to find the "base" node for N. If there isn't any base node, MPS just opens the editor for selected node. If the node is found (call it B), MPS opens some tabs for it, containing editors for some subordinate nodes. Then it selects the tab for N and sets the top icon and caption corresponding to B.
When you create tabbed editors, you actually provide rules for: finding base node, finding subordinate nodes and (optionally) algorithms of subordinate nodes creation.
Editor Tab Structure
Name - The name of the rule. You can give any name you want, the only obvious constraint is that the names must be unique in scope of the model.
Main Concept - the concept of base node.
Base Node - this is a rule for searching for "base" node. It should return null if the base node is not found or this TabbedEditor can't be applied.
Tabs
Single-tabbed tab
Caption - the caption of the tab
Empty text - this will be written in this tab's panel if there are no nodes to display in it
getNode - should return the node to edit in this tab
create - if specified, this wil be executed when user asks to create node from this tab
Multi-tabbed tab
Caption - the caption of the tab
Empty text - this will be written in this tab's panel if there are no nodes to display in it
getNodes - should return nodes to edit in this tab
getCaption- should return caption for inner tab for the given node
create - if specified, this wil be executed when user asks to create node from this tab
Tools
Tool is an instrument that has a graphical presentation and aimed to perform some specific tasks. For example, Usages View, Todo Viewer, Model and Module repository Viewers are all tools. MPS has rich UI support for tools - you can move it by drag-and-drop from one edge of the window to another, hide, show and perform many other actions.
Tools are created "per project". They are initialized/disposed on class reloading (after language generation, on "reload all" action etc.)
Tool structure
Name - The name of the tool. You can give any name you want, the only obvious constraint is that the names must be unique in scope of the model.
Caption - this string will be displayed in tool's header and on the tool's button in tools pane
Number - if specified, alt+number becomes a shortcut for showing this tool (f it s available)
Icon - the icon to be displayed on the tool's button. You can select the icon file by pressing "..." button. Note that the icon mustbe placed near your language (because it's stored not as an image, but as a path relative to the language's root)
Init - initialize tool instance here
Dispose - dispose all tool resources here
getComponent - should return a Swing component (instance of a class which extends JComponent) to display inside the tool's window. If you are planning to create tabs in your tool and you are familiar with tools framework in IDEA, it's better to use IDEA's support for tabs. Using this framework greatly improves tabs functionality and UI.
Fields and methods - regular fields and methods, you can use then in your tool and in external code.
Tool operation
We added an operation to simply access a tool in some project. You can access it as project.tool<toolName>, where project is an IDEA Project. Do not forget to import pluginLanguage to use it.
| Be careful This operation can't currently be used in dispose() method |
Preferences components
Sometimes you may want to be able to edit and save some settings (e.g. your tools' settings) between MPS startups. We have introduced preferences components for these purposes.
Each preferences component includes a number of preferences pages and a number of persistent fields.Preferences page is a dialog for editing user preferences. They are accessible through File->Settings.
Persistent fields are saved to .iws files when project is closed and restored from them on project open. The saving process uses reflection, so you don't need to care about serialization/deserialization in most cases.
| Note Only primitive types and non-abstract classes can be used as types of persistent fields. If you want to store some complex data, create a persistent field of type org.jdom.Element (do not forget to import model org.jdom), annotate it with com.intellij.util.xmlb.annotations.Tag and serialize/deserialize your data manually in after read / before write |
Preferences component structure
name - component name. You can give any name you want, the only obvious constraint is that the names must be unique in scope of the model.
fields - these are persistent fields. They are initialized before after read and pages creation, so their values will be correct in every moment they can be accessed. Default values can be specified for them.
after read / before write - these blocks are used for custom serialization purposes and for applying/collecting preferences, which have no corresponding preferences pages (e.g. tool dimensions)
pages - preferences pages
Preferences page structure
name - the string to be used as a caption in Settings page. The name must be unique within a model.
component - UI component to edit preferences.
| Hint uiLanguage components can be used here |
icon - the icon to show in Settings window. The size of the icon can be up to 32x32
reset - reset preferences values in UI component when this method is called.
commit - in this method preferences should be collected from the UI component and commited to wherever they are used.
isModified - if this method returns false, commit won't be executed. This is for preferences pages with long-running commit method.
PreferenceComponent expression
We added an expression to simply access a PreferenceComponent in some project. You can access it as project.preferenceComponent<componentName>, where project is an IDEA Project. Do not forget to import pluginLanguage to use it.
| Be careful This operation can't currently be used in dispose() method |
Custom plugin parts
Custom plugin parts are custom actions performed on plugin initializing/disposing. They behave exactly like plugins. You can create as many custom plugins for your language as you want. There are two types of custom plugins - project and application custom plugins. The project custom plugin is instantiated once per project, while the application custom plugin is instantiated once per application and therefore it doesn't have a project parameter.
File Generators
File Generators provide an ability of changing the place to generate files to. This can be used, for example, to store application database settings in application's root directory regardless of the model in which they were described.
File Generator structure
generateFile - this method is used to create files. You are provided with contents (String) and some additional information and need to create resulting files.
isDefault / overridesDefault - these two methods are used to better control file generator applicability. Each file generator can be either "default" or "non-default". Defult file generators can be overridden by non-default.
fields, methods, extended class - some additional stuff can be declared here
Generation Listeners
Using generation listeners you can perform custom activities on generation events.
These events are:
- before generation (models) - occures before generation
- on models generated (success,models) - occures just after all models were generated
- after generation(models) - occures after generation has finished
Generate Plugins concept
Note that when one creates the plugin aspect, he doesn't have to explicity write the plugin class, instead of it, he describes only the parts of the plugin itself. The plugin class is generated automatically, but only for plugin aspects of languages. If plugin language is used in a model, the plugin class won't be generated for it automatically. To generate the class, create Generate Plugin node in this model and specify which plugin classes should be generated.
Run Configurations
Introduction
Run configurations allow you to define how to execute programs, written in your language.
An existing run configuration can be executed either from run configurations box, located on the main toolbar,

by the "Run" menu item in the main menu

or through the run/debug popup (Alt+Shift+F10/Alt+Shift+F9).

Also run configurations could be executed/created for nodes, models, modules and project. For example, JUnit run configuration could run all tests in selected project, module or model. See Run configurations creators on how to implement such behavior for you run configurations.
To summarize, run configurations define the following things:
- On creation stage:
- configurations name, caption, icon;
- configurations kind;
- how to create a configuration from node(s), model(s), module(s), project.
- On configuration stage:
- persistent parameters;
- editor for persistent parameters;
- checker of persistent parameters validity.
- On execution stage:
- proccess which is actually executed;
- console with all its tabs, action buttons and actual console window;
- things required for debugging this configuration (if it is possible).
| This section describes new execution languages usage. To see old run configuration languages, visit MPS 1.5 Documentation. |
The following languages are introduced in MPS 2.0 to support run configurations in MPS.
- jetbrains.mps.execution.common (common language) – contains concepts utilized by the other execution* languages;
- jetbrains.mps.execution.settings (settings language) – language for defining different setting editors;
- jetbrains.mps.execution.commands (command languages) – processes invocation from java;
- jetbrains.mps.execution.configurations (configurations language) – run configurations definition;
Settings
Settings language allows to create setting editors and integrate them into one another. What we need from settings editor is the following:
- fields to edit;
- validation of fields correctness;
- editor UI component;
- apply/reset functions to apply settings from UI component and to reset settings in the UI component to the saved state;
- dispose function to destroy UI component when it is no longer needed.
As you can see, settings have UI components. Usually, one UI component is created for multiple instances of settings. In the settings language settings are usually called "configurations" and their UI components are called "editors".
The main concept of settings language is PersistentConfigurationTemplate. It has the following sections:
- persistent properties. This section describes the actual settings we are editing. Since we want also to persist this settings (i.e. to write to xml/read from xml) and to clone our configurations there is a restriction on their type: each property should be either Cloneable or String or any primitive type. There is also a special kind of property named template persistent property, but they are going to be discussed later.
- editor. This section describes the editor of the configuration. It has functions create, apply to, reset from, dispose. Section also can define fields to store some objects in the editor. create function should return a swing component – the main UI component of the editor. apply to/reset from functions apply or reset settings in the editor to/from configuration given as a parameter. dispose function disposes the editor.
- check. In this section persistent properties are checked for correctness. If some properties are not valid, a report error statement can be used. Essentially, this statement throws RuntimeConfigurationException.
- additional methods. This section is for methods, used in the configurations. Essentially, this methods are configuration instance methods.
Persistent properties
It was said above that persistent properties could be either Cloneable or String or any primitive type. But if one uses settings language inside run configurations, those properties should also support xml persistent. Strings and primitives are persisted as usual. For objects persistent is more complicated. Two types of properties are persisted for an object: public instance fields and properties with setXXX and getXXX methods. So, if one wish to use some complex type in persistent property, he should either make all important field public or provide setXXX and getXXX for whatever he wants to persist.
Integrating configurations into one another
One of the two basic features of settings language is the ability to easy integration of one configuration into another. For that template persistent properties are used.
Template parameters
The second basic feature of settings language is template parameters. These are like constructor parameters in java. for example, if one creates a configuration for choosing a node, he may want to parametrize the configuration with nodes concept. The concept is not a persistent parameter in this case: it is not chosen by the user. This is a parameter specified on configuration creation.
Commands
Commands language allows to start up processes from the code in a way it is done from command line. The main concept of the language is CommandDeclaration. In the declaration, command parameters and the way to start process with this parameters are specified. Also, commands can have debugger parameters and some utility methods.
Execute command sections
Each command can have several execute sections. Each of this sections defines several execution parameters. There are parameters of two types: required and optional. Optional parameters can have default values and could be ommited when the command is started, while required can not have default values and are mandatory. Two execute sections of the command should have different (by types) lists of required parameters. One execute section can invoke another execute section. Each execute section should return either values of process or ProcessHandler types.
ProcessBuilderExpression
To start a process from a command execute section ProcessBuilderExpression isused. It is a simple list of command parts. Each part is either ProcessBuilderPart which consists of an expression of type string or list<string>, or a ProcessBuilderKeyPart which represents a parameter with a key (like "-classpath /path/to/classes"). When the code generated from ProcessBuilderExpression is invoked, each part is tested for being null or empty and ommited if so. Then, each part is splitted into many parts by spaces. So if you would like to provide a command part with a space in it and do not wish it to be splitted (for example, you have a file path with spaces), you have to put it into double quotes ("). Working directory of created process could be specified in inspector.
Debugger integration
To integrate a command with the debugger, two things are required to be specified:
- the specific debugger to integrate with;
- the command line arguments for a process.
To specify a debugger one can use DebuggerReference – an expression of debugger type in jetbrains.mps.debug.apiLang – to reference a specific debugger. Debugger settings must be an object of type IDebuggerSettings.
Configurations
Configurations language allows to create run configurations. To create a run configuration, on should create an instance of RunConfiguration (essentially, configuration from settings language) and provide a RunConfigurationExecutor for it. One also may need a RunConfigurationKind to specify a kind of this configuration, RunConfigurationProducer to provide a way of creating this configuration from nodes, models, modules etc and a BeforeTask to specify how to prepare a configuration before execution.
Executors
Executor is a node which describes how a process is started for this run configuration. It takes settings user entered and created a process from it. So, the executor's execute methods should return an instance of type process. This is done via StartProcessHandlerStatement. Anything which has type process or ProcessHandler could be passed to it. A process could be created in three different ways:
- via command;
- via ProcessBuilderExpression (recommended to use in commands only);
- by creating new instance of ProcessHandler class; this method is recommended only if the above two do not fit you, for example when you creating a run configuration for remote debug and do not really need to start a process.
The executor itself consists of the following sections:
- "for" section where the configuration this executor is for and an alias for it is specified;
- "can" section where the ability of run/debug this configuration is specified; if the command is not used in this executor, one must provide an instance of DebuggerConfiguration here;
- "before" section with the call of tasks which could be executed before this configuration run, such as Make;
- "execute" section where the process itself is created.
Debugger integration
If a command is used to start a process, nothing should be done apart from specifying a configuration as debuggable (by selecting "debug" in the executor). However, if a custom debugger integration is required, it is done the same way as in command declaration.
Useful examples
In this section you can find some useful tips and examples of run configurations usages.
Person Editor
In this example a editor for a "Person" is created. This editor edits two properties of a person: name and e-mail address.

PersonEditor could be used from java code in the following way:
Exec command
This is an example of a simple command which starts a given executable with programParameters in a given workingDirectory. You can find this example in model jetbrains.mps.samples.nanoc.plugin in project nanocProject in MPS samples.

Compile with gcc before task
This is anexample of a BeforeTask wich does compilation of a source file with gcc command. It also demonstrates how to use commands outside of run configurations executors.

Note that this is just a toy example, in real life one should show a progress window while compiling.
Java Executor
This is an actual executor of Java run configuration from MPS.

Running a node, generated into java class
Lets suppose you have a node of a concept which is generated into a java class with a main method and you wish to run this node from MPS. Then you do not have to create a run confgiuration in this case, but you should do the following:
- The concept you wish to run should implement IMainClass concept from jetbrains.mps.execution.util language. To specify when the node can be executed, override isNodeRunnable method.
- Unit information should be generated for the concept. Unit information is required to correctly determine the class name which is to be executed. You can read more about unit information, as whell as about all trace information, in Debugger section of MPS documentation. To ensure this check that one of the following conditions are satisfied:
- a ClassConcept from jetbrains.mps.baseLanguage is generated from the node;
- a node is generated into text (the language uses textGen aspect for generation) and the concept of the node implements UnitConcept interface from jetbrains.mps.traceable;
- a node is genearted into a concept for which one of the three specified conditions is satisfied.
Intentions are a very good example of how MPS enables language authors to smoothen the user experience of people using their language. Intentions provide fast access to the most used operations with syntactical constructions of a language, such as "negate boolean", "invert if condition," etc. If you've ever used IntelliJ IDEA's intentions or similar features of any modern IDEs, you will find MPS intentions very familiar.
Using intentions
Like in IDEA, if there are avaliable intentions applicable to the code at the current position, a light bulb is shown. To view the list of avaliable intentions, press Alt+Enter or click the light bulb. To apply an intention, either click it or select it and press Enter. This will trigger the intention and alter the code accordingly.
Example: list of applicable intentions
Intention types
All intentions are "shortcuts" of a sort, bringing some operations on node structure closer to the user. Three kinds of intentions can be distinguished: regular intentions, "surround with" intentions and "generate" intentions.
Generally speaking, there is no technical difference between these types of intentions. They only differ in how they are typically used by the user.
regular intentions are listed on the intentions list (the light bulb) and they directly perform transformations on a node without asking the user for parameters customizing the operations.
"surround with" intentions are used to implement a special kind of transformation - surrounding some node(s) with another construct (e.g. "surround with parenthesis"). These intentions are not offered to the users unless they press ctrl-alt-T (the surround with command) on a node. Neither they are shown in general intentions pop-up menu.
"generate" intentions are used to produce some new nodes using user-specified parameters. In contrast with regular intentions, intentions of this type can show some interactive UI. This type of intentions has its own shortcut - alt-insert (ctrl-n on Macs)
Common Intention Structure
Regular Intentions
is error intention - This flag is responsible for an intention's presentation. It distinguishes two types of intentions - "error" intentions which correct some errors in the code (e.g. a missing 'cast') and "regular" intentions, which are intended to help the user perform some genuine code transformations. To visually distinguish the two types, error intentions are shown with a red bulb, instead of an orange one, and are placed above regular intentions in the applicable intentions list.
Parameterized regular intentions
Intentions can sometimes be very close to one another. They may all need to perform the same transformation with a node, just slightly differently. E.g. all "Add ... macro" intentions in the generator ultimately add a macro, but the added macro itself is different for different intentions. This is the case when parameterized intention is needed. Instead of creating separate intentions, you create a single intention and allow for its parametrization. The intention has a parameter function, which returns a list of parameter values. Based on the list, a number of intentions are created , each with a diferent parameter value. The parameter values can then be accessed in almost every intention's method.
| Note You don't have an access to the parameter in the isApplicable function. This is because of performance reasons. As isApplicable is executed very often and delays would quickly become noticeable by the user, you should perform only base checks in isApplicable. All parameter-dependent checks should be performed in the parameter function, and if a check was not passed, this parameter should not be returned |
Surround With - Intentions
This type of intentions is very similar to regular intentions and all the mentioned details apply to these intentions as well.
Generate - Intentions
The main difference from the general intentions is the executeUI function, which is executed before regular execute and is supposed to present some UI to the user. If the UI returns false, no further processing is done - the execute function is not performed
| Note It is not a good idea to show UI in the execute function - such a code will mostly likely throw an exception at runtime |
Where to store my intentions?
You can create intentions in any model by importing the intentions language. However, MPS collects intentions only from the Intentions language aspects. If you want your intentions to be used by the MPS intentions subsystem, they must be stored in the Intentions aspect of your language.
Examples
You can find some intentions examples in jetbrains.mps.baseLanguage.intentions.
You can also see all intentions by going to the IntentionDeclaration concept (press Ctrl+N, type "IntentionDeclaration", press ENTER) and finding all instances of this concept (pres Alt+F7, check instances, check Global Scope).
[Previous] Next
In MPS, any model consists of nodes. Nodes can have many types of relations. These relations may be expressed in a node structure (e.g. "class descendants" relation on classes) or not (e.g. "overriding method" relation on methods). Find Usages is a tool to display some specifically related nodes for a given node.
In MPS, the Find Usages system is fully customizable - you can write your own entities, so-called finders, which represent algorithms for finding related nodes. For every type of relation there is a corresponding finder.
This is how "find usages" result looks like:

Using Find Usages Subsystem
You can press Alt+F7 on a node (no matter where - in the editor or in the project tree) to see what kind of usages MPS can search for.
You can also right-click a node and select "Find Usages" to open the "Find usages" window.

Finders - select the categories of usages you want to search for
Scope - this lets you select where you want to search for usages - in concrete model, module, current project or everywhere.
View Options - additional view options
After adjusting your search, click OK to run it. Results will be shown in the Find Usages Tool as shown above.
Finders
To implement your own mechanism for finding related nodes, you should become familiar with Finders. For every relation there is a specific Finder that provides all the information about the search process.
Where to store my finders?
Finders can be created in any model by importing findUsages language. However, MPS collects finders only from findUsages language aspects. So, if you want your finder to be used by the MPS Find Usages subsystem, it must be stored in the findUsages aspect of your language.
Finder structure
What does the MPS Find Usages subsystem do automatically?
- Stores search options between multiple invocations and between MPS runs
- Stores search results between MPS runs
- Automatically handles deleted nodes
- All the visualization and operations with found nodes is done by the subsystem, not by finders
Specific Statements
execute
Finders can be reused thanks to the execute statement. The execution of this statement consists of 2 steps: validating the search query (checking for concept and isApplicable), and executing the find method. That's where you can see the difference between isApplicable and isShown. If you use isApplicable for cases when the finder should be applicable, but not shown, you can get an error when using this finder in the execute statement.
Examples
You can see some finder examples in jetbrains.mps.baseLanguage.findUsages
You can also find all finders by going to the FinderDeclaration concept (Ctrl+N, type "FinderDeclaration", then press ENTER) and finding all instances of this concept (Alt+F7, check instances, then check Global Scope).
Suppose we want to describe one of the existing languages inside of MPS (for example, we can roughly consider baseLanguage as a description of Java in MPS). In this case, the libraries written for this language should be accessible from MPS. The stubs aspect of a language helps to create stub models for such libraries. These models are used to reference the code outside of MPS.
Stubs Language
There are two things that can be described using stubs aspect
1. The StubCreator describes how to parse existing code and convert it into models
2. The StubSolutions describe standard libraries for the language and how MPS can find them on the user's machine (e.g. for baseLanguage, there are 2 stub solutions - JDK for JDK classes and MPS.Classpath for MPS classes)
Stub Creator Declaration
Library Stub Descriptor
Note that you can't re-create a stub solution after you have published it (even with the same name!). This happens because these solutions are referenced from user modules by automatically generated moduleId, which can't be preserved between deletion and creation.
Note that there's no need for using stub solutions for libraries that you distribute together with your language. Such libraries can be simply added as runtime or design-time libraries in language properties. Stub solutions are only applicable when paths to load stubs from can't be statically specified (like JDK paths, which can be different on different computers)
Usage
Let's consider how stubs are used.
Author of the language does the following things:
1. Creates StubCreator that can load stub models for his language
2. Creates StubSolutions describing standard libraries for his language (the libraries that are pre-installed on the user's machine and are not included into the language's packaging)
After the things mentioned are done, any user of this language can
1. Use the described libraries directly (import stub models, view and reference them, etc.)
2. Use his own libraries that can be loaded by the created StubCreator.
Let's see how to add a new library of a new type to solution:
1. Go to Solution Properties -> External code -> Libraries
2. Add a new library (select a path where it is situated)
3. Choose an appropriate manager for this library
Performance
For now, stubs are reloaded on classes reload.
Stubs are invalidated and loaded lazily. This means that
- the contents of stub model are not loaded before they are queried
- if neither source files, nor the stub manager for some stub model were changed since the last reload, the model won't be reloaded
Despite the optimizations considered, it is better to mention that there are still some bottlenecks.
1. The list of models is queried every time the classes are reloaded, which means that modelDescriptors block of Stub Manager is better to execute fast.
2. As the aim of stub models is to reference the outside code and help to compute types, there's no need to include anything non-referenceable into stub model. E.g. nothing inside Java method can be referenced from outside the method, so there's no need to include method bodies into stub models. This will only make the loading slower and the models bigger.
Warning: Never set stubs containing same models in different modules. Most probably, this won't work.
Debugger
MPS provides an API for creating custom debuggers as well as integrating with debugger for java. See Debugger Usage page for a description of MPS debugger features.
Integration with java debugger
To integrate your java-generated language with java debugger, provided by MPS, you should specify:
- on which nodes breakpoints could be created;
- nodes which should be traced;
- how to start your application under debug;
- custom viewers for your data.
Not all of those steps are absolutely necessary; which are – depends on the language. See next parts for details.
Nodes to trace and breakpoints
Suppose you have a language, let's call it highLevelLanguage, which generates code on some lowLevelLanguage, which in turn is generated directly into text (there can be several other languages between highLevelLanguage and lowLevelLanguage, it does not really metter). Suppose that the text generated from lowLevelLanguage is essentially java, and you want to have your highLevelLanguage integrated with java debugger. See the following explanatory table:
| lowLevelLanguage is baseLanguage | lowLevelLanguage is not baseLanguage | |
|---|---|---|
| highLevelLanguage extends baseLanguage (uses concepts Statement,Expression, BaseMethodDeclaration etc) |
Do not have to do anything. | Specify traceable concepts for lowLevelLanguage. |
| highLevelLanguage does not extend baseLanguage | Specify breakpointable concepts for highLevelLanguage. | Specify traceable concepts for lowLevelLanguage. Specify breakpointable concepts in DebugInfoInitializer for highLevelLanguage. |
Startup of a run configuration under java debugger
MPS provides a special language for creating run configurations for languages generated into java – jetbrains.mps.baseLanguage.runConfigurations. Those run configurations are able to start under debugger automatically. See Run configurations for languages generated into java for details.
Custom viewers
When one views variables and fields in a variable view, one may want to define one's own way to show certain values. For instance, collections could be shown as a collection of elements rather than as an ordinary object with all its internal structure.
For creating custom viewers MPS has jetbrains.mps.debug.customViewers language.
A language jetbrains.mps.debug.customViewers enables one to write one's own viewers for data of certain form.
A main concept of customViewers language is a custom data viewer. It receives a raw java value (an objects on stack) and returns a list of so-called watchables. A watchable is a pair of a value and its label (a string which cathegorizes a value, i.e. whether a value is a method, a field, an element, a size etc.) Labels for watchables are defined in custom watchables container. Each label could be assigned an icon.
The viewer for a specific type is defined in a custom viewer root. In the following table custom viewer parts are described:
| Part | Description |
|---|---|
| for type | A type for which this viewer is intended. |
| can wrap | An additional filter for viewed objects. |
| get presentation | A string representation of an object. |
| get custom watchables | Subvalues of this object. Result of this funtion must be of type watchable list. |
Custom Viewers language introduces two new types: watchable list and watchable.
This is the custom viewer specification for java.util.Map.Entry class:

And here we see how a map entry is displayed in debugger view:

Creating a non-java debugger
You can create a non-java debugger using the API provided by MPS. You can see how it is done in jetbrains.mps.samples.nanoc language – a toy language generated into C. This language is supplied with MPS among other sample languages. The project location is %HOME_PATH%/MPSSamples.2.0/nanoc/nanocProject/nanocProject.mpr.
Traceable Nodes
| To upgrade from MPS 1.5 use migration script "Upgrade Trace Info Generation" (Tools->Scripts->By Language->jetbrains.mps.lang.plugin->Upgrade Trace Info Generation) and regenerate textGen and plugin model of your language |
This section describes how to spesify which nodes require to save some additional information in trace.info file (like information about positions text, generated from the node, visible variables, name of the file the node was generated into etc.). trace.info files contain information allowing to connect nodes in MPS with generated text. For example, if a breakpoint is hit, java debugger tells MPS the line number in source file and to get the actual node from this information MPS uses information from trace.info files.
Specifically, trace.info files contain the following information:
- position information: name of text file and position in it where the node was generated;
- scope information: for each "scope" node (such that has some variables, associated with it and visible in the scope of the node) – names and ids of variables visible in the scope;
- unit information: for each "unit node" (such that represent some unit of a language, for example a class in java) – name of the unit the node is generated into.
Concepts TraceableConcept, ScopeConcept and UnitConcept of language jetbrains.mps.lang.traceable are used for that purpose. To save some information into trace.info file, user should derive from one of those concepts and implement the specific behavior method. The concepts are described in the table below.
| Concept | Description | Behavior method to implement | Example |
|---|---|---|---|
| TraceableConcept | Concepts for which location in text is saved and for which breakpoints could be created. | getTraceableProperty – some property to be saved into trace.info file. | |
| ScopeConcept | Concepts which have some local variables, visible in the scope. | getScopeVariables – variable declarations in the scope. | |
| UnitConcept | Concepts which are generated into separate units, like classes or inner classes in java. | getUnitName – name of the generated unit. | |
trace.info files are created on the last stage of generation – while generating text. So the decsribed concepts are only to be used in languages generated into text.
Breakpoint Creators
To specify how breakpoints are created on various nodes, root breakpoint creators is used. This is a root of concept BreakpointCreator from jetbrains.mps.debug.apiLang language. The root should be located in the language plugin model. It contains a list of BreakpointableNodeItem, each of them specify a list of concept to create breakpoint for and a method actually creating a breakpoint. jetbrains.mps.debug.apiLanguage provides several concepts to operate with debuggers, and specifially to create breakpoints. They are described below.
- DebuggerReference – a reference to a specific debugger, like java debugger;
- CreateBreakpointOperation – an operation which creates a location breakpoint of specified kind on a given node for a given project;
- DebuggerType – a special type for references to debuggers.
On the following example breakpoint creators node from baseLanguage is shown.

Changes highlighting
Changes highlighting is a handy way to show changes which were made since last update from version control system.
Since MPS 1.5 changes for models are highlighted in the following places:
Project tree view
Models, nodes, properties and references are highlighted.
green means new items, blue means modified items, brown means unversioned items.

Editor tabs
Highlighting appears for all of the editor tabs: for language aspect tabs of a concept and also for custom tabbed editors declared in plugin aspect of a language (see Plugin: Editor Tabs).

Editor
Every kind of changes are highlighted in MPS editor: changing properties and references, adding, deleting and replacing nodes.

If you hover mouse cursor over the highlighter's strip on the left margin of editor, the corresponding changes become highlighted in editor pane.
If you want to have your changes highlighted in editor pane all the time (not only on hovering mouse cursor over highlighter's strip), you can select "Highlight Nodes With Changes Relative to Base Version" option in IDE Settings → Editor.

If you click on highlighter's strip on the left margin, there appears a panel with three buttons: "Go to Previous Change", "Go to Next Change" and "Rollback".

If you click "Rollback", all the corresponding changes are reverted.
This feature allows you to feel free to make any changes to MPS model in editor without any fear, because at any moment you can revert it right from the editor.
Base Language
The BaseLanguage is an MPS' counterpart to Java, since it shares with Java almost the same set of constructs. MPS is the most common target of code generation and the most extensively extended language at the same time.
In order to simplify integration with Java, it is possible to specify the classpath for all modules in MPS. Classes found on the classpath will then be automatically imported into @java_stub models and so can be used directly in programs that use the BaseLanguage.
The frequently extended concepts of MPS include:
- Expression. Constructs, which are evaluated to some results like 1, "abc", etc.
- Statement. Constructs, which can be contained on a method level like if/while/synchronized statement.
- Type. Types of variables, like int, double.
- IOperation. Constructs, which can be placed after a dot like in node.parent. The parent element is a IOperation here.
- GenericCreator. Constructs, which can be used to instantiate various elements.
Base Language is by far the most widely extended language in MPS. Since it is very likely that a typical MPS project will use a lot of different extensions from different sources or language vendors, the community might benefit from having a unified style across all languages. In this document we describe the conventions that creators should apply to all Base Language extensions.
Quick Reference
| If you use... | Set its style to... |
|---|---|
| |
Dot |
| |
LeftBracket |
| |
RightBracket |
| |
LeftBrace |
| |
RightBrace |
| |
Operator |
| |
KeyWord |
Keywords
A keyword is a widely used string, which identifies important concepts from a language. For example, all the primitive types from Base Language are keywords. Also names of statements such as ifStatement, forStatement are keywords. Use the KeyWord style from base language's stylesheet for keywords.
Curly braces
Curly braces are often used to demarcate a block of code inside of a containing construction. If you create an if-like construct, place opening curly brace on the same line as the construct header. I.e. use:
instead of
Use the LeftBrace and RightBrace styles to set correct offsets. Make sure that the space between a character which is to left to opening curly brace and the curly brace itself is equal to 1 space. You can do so with a help of padding-left/padding-right styles.
Parentheses
When you use parentheses, set the LeftParen/RightParen styles to the left/right parenthesis. If a parenthesis cell's sibling is a named node's property, disable the first/last position of a parenthesis with first/last-position-allowed style.
Identifiers
When you use named nodes: methods, variables, fields, etc, it's advisable to make their name properties have 0 left and right padding. Making identifier declaration and reference holding the same color is also a good idea. For example, in Base Language, field declarations and references have the same color.
Punctuation
If you have a semicolon somewhere, set its style to Semicolon. If you have a dot, use the Dot style. If you have a binary operator, use the Operator style for it.
Closures
Introduction
Closures are a handy extension to the base language. Not only they make code more consise, but you can use them as a vehicle to carry you through the lands of functional paradigm in programming. You can treat functions as first-class citizens in your programs - store them in variables, pass them around to methods as arguments or have methods and functions return other functions. The MPS Closures Support allows to you employ closures in your own languages. In fact, MPS itself uses closures heavily, for example, in the collections language.
This language loosely follows the "BGGA" proposal specification for closures in Java12. However, you don't need Java 7 to run code with MPS closures. The actual implementation uses anonymous inner classes, so any recent version of Java starting with 1.5 will run the generated code without problems. Only the closures runtime jar file is required to be on the classpath of the generated solutions.
Function type
{ Type1, Type2... => ReturnType }
Let's start with a trivial example of function type declaration. It declares a function that accepts no parameters and returns no value.
Subtyping rules
A function type is covariant by its return type and contravariant by parameter types.
For example, given we have defined a method that accepts {String => Number} :
we can pass an instance of {Object => Integer} (a function that accepts Object and returns int) to this method:
Simply put, you can use different actual types of parameters and the return value so long as you keep the promise made in the super-type's signature.
Notice the int type automatically converted to boxed type Integer.
Closure literal
Closure literal is created simply by entering a following construct: { <parameter decls> => <body> }. No "new" operator is necessary.
The result type is calculated following one or more of these rules:
- last statement, if it's an ExpressionStatement;
- return statement with an expression;
- yield statement.
Note: it's impossible to combine return and yield within a single closure literal.
Closure invocation
The invoke operation is the only method you can call on a closure. Instead of entering
To invoke a closure, it is recommended to use the simplified version of this operation - parentheses enclosing the parameter list.
Invoking a closure then looks like a regular method call.
Some examples of closure literal definitions.
Recursion
Functional programing without recursion would be like making coffe without water, so obviously you have a natural way to call recursively a closure from within its body:
A standalone invoke within the closure's body calls the current closure.
Closure conversion
For practical purposes a closure literal can be used in places where an instance of a single-method interface is expected, and vice versa3.
The generated code is exactly the same as when using anonymous class:
Think of all the places where Java requires instances of Runnable, Callable or various observer or listener classes:
| Updated for MPS 1.5 The following changes are applicable to the upcoming 1.5 version of MPS. |
As with interfaces, an abstract class containing exactly one abstract method can also be adapted to from a closure literal. This can help, for example, in smooth transition to a new API, when existing interfaces serving as functions can be changed to abstract classes implementing the new interfaces.
Yield statement
The yield statement allows closures populate collections. If a yield statement is encountered within the body of a closure literal, the following are the consequences:
- if the type of yield statement expression is Type, then the result type of the closure literal is sequence<Type>;
- all control statements within the body are converted into a switch statement within an infinite do-while loop at the generation;
- usage of return statement is forbidden and the the value of last ExpressionStatement is ignored.
Functions that return functions
A little bit of functional programming for the functional hearts out there:
The curry() method is defined as follows:
Runtime
In order to run the code generated by the closures language, it's necessary to add to the classpath of the solution the closures runtime library. This jar file contains the synthetic interfaces needed to support variables of function type and some utility classes. It's located in:
Differences from the BGGA proposal
- No messing up with control flow. This means no support for control flow statements that break the boundaries of a closure literal.
- No "early return" problem: since MPS allows return to be used anywhere within the body.
- The yield statement.
[1] Closures for the Java Programming Language
[3] Version 0.5 of the BGGA closures specification is partially supported
[3] This is no longer true: only closure literal to interface conversion is supported, as an optimization measure.
Collections Language
An extension to the Base Language that adds support for collections.
Introduction
Collection language provides a set of abstractions that enable the use of a few most commonly used containers, as well as a set of powerful tools to construct queries. The fundamental type provided by the collections is sequence, which is an abstraction analogous to Iterable in Java, or IEnumerable in .NET . The containers include list (both array-based and linked list), set and map. The collections language also provides the means to build expressive queries using closures, in a way similar to what LINQ does.
Null handling
Collections language has a set of relaxed rules regarding null elements and null sequences.
Null sequence is still a sequence
Null is a perfectly accepted value that can be assigned to a sequence variable. This results simply in an empty sequence.
Null is returned instead of exception throwing
Whereas the standard collections framework would have to throw an exception as a result of calling a method that cannot successfully complete, the collection language's sequence and its subtypes would return null value. For example, invoking first operation on an empty sequence will yield a null value instead of throwing an exception.
Skip and stop statements
skip
Applicable within a selectMany or forEach closure. The effect of the skip statement is that the processing of the current input element stops, and the next element (if available) is immediately selected.
stop
Applicable within a selectMany closure or a sequence initializer closure. The stop statement causes the construction of the output sequence to end immediately, ignoring all the remaining elements in the input sequence (if any).
—
Collections Runtime
Collections language uses a runtime library as its back end, which is designed to be extensible. Prior to version 1.5, the collections runtime library was written in Java and used only standard Java APIs. The release 1.5 brings a change: now the runtime library is available as an MPS model and uses constructs from jetbrains.mps.baseLanguage.closures language to facilitate passing of function-type parameters around.
| Important change! In order to make the transition from Java interfaces to abstract function types possible, several of the former Java interfaces in the collections runtime library have been changed into abstract classes. While no existing code that uses collections runtime will be broken, unfortunately this breaks the so called binary compatibility, which means that a complete recompilation of all the generated code is required to avoid incompatibility with the changed classes in the runtime. |
The classes which constitute the collections runtime library can be found in the collections.runtime solution, which is available from the jetbrains.mps.baseLanguage.collections language.
Sequence
Sequence is an abstraction of the order defined on a collection of elements of some type. The only operation that is allowed on a sequence is iterating its elements from first to last. A sequence is immutable. All operations defined in the following subsections and declared to return a sequence, always return a new instance of a sequence or the original sequence.
Although it is possible to create a sequence that produces infinite number of elements, it is not recommended. Some operations may require one or two full traversals of the sequence in order to compute, and invoking such an operation on an infinite sequence would never yield result.
Sequence type
sequence<Type>
| Subtypes | Supertypes | Comparable types |
|---|---|---|
| list<Type> set<Type> |
none | java.lang.Iterable<Type> |
Creation
new sequence
| Parameter type | Result type |
|---|---|
| { => sequence<Type> } | sequence<Type> |
Sequence can be created with initializer.
closure invocation
| Result type |
|---|
| sequence<Type> |
A sequence may be returned from a closure (see Closures).
array as a sequence
| Operand type | Parameter type | Result type |
|---|---|---|
| Type[] | none | sequence<Type> |
An array can be used as a sequence.
A list, a set and a map are sequences, too. All operations defined on a sequence are also available on an instance of any of these types.
Sequence type is assignable to a variable of type java.lang.Iterable. The opposite is also true.
Operations on sequence
Iteration and querying
foreach statement
Loop statement
is equivalent to
forEach
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | { Type => void } | void |
The code passed as a parameter (as a closure literal or by reference) is executed once for each element.
size
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | none | int |
Gives number of elements in a sequence.
isEmpty
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | none | boolean |
Test whether a sequence is empty, that is its size is 0.
isNotEmpty
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | none | boolean |
Test whether a sequence contains any elements.
indexOf
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | Type | int |
Gives the index of a first occurrence in a sequence of an element that is passed to it as a parameter.
contains
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | Type | boolean |
Produces boolean value, indicating whether or not a sequence contains the specified element.
any / all
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | { Type => boolean } | boolean |
Produces boolean value that indicates whether any (in case of any operation) or all (in case of all) of the elements in the input sequence match the condition specified by the closure.
iterator
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | none | iterator<Type> |
Produces an iterator.
enumerator
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | none | enumerator<Type> |
Produces an enumerator.
Selection and filtering
first
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | none | Type |
Yields the first element.
last
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | none | Type |
Yields the last element.
take
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | int | sequence<Type> |
Produces a sequence that is sub-sequence of the original one, starting from first element and of size count.
skip
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | int | sequence<Type> |
Produces a sequence that is sub-sequence of the original one, containing all elements starting with the element at index count.
cut
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | int | sequence<Type> |
Produces a sequence that is a sub-sequence of the original one, containing all elements starting with first and up to (but not including) the element at index size minus count. In other words, this operation returns a sequence with all elements from the original one except the last count elements.
tail
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | int | sequence<Type> |
Produces a sequence that is a sub-sequence of the original one, containing all elements starting with the element at index size minus count. In other words, this operations returns a sequence with count elements from the end of the original sequence, in the original order.
page
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | int int |
sequence<Type> |
Results in a sequence that is a sub-sequence of the original one, containing all elements starting with the element at index start and up to (but not including) the element at index end.
This is equivalent to
Where skip = start, count = end - start .
where
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | { Type => boolean } | sequence<Type> |
Produces a sequence that is a sub-sequence of the original one, with all elements for which the code passed as a parameter returns true.
findFirst
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | { Type => boolean } | Type |
Results in the first element that matches the parameter closure.
findLast
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | { Type => boolean } | Type |
Results in the last element that matches the parameter closure.
Transformation and sorting
select
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | { Type => Type2 } | sequence<Type2> |
Results in a sequence consisting of elements, each of which is the result of applying the parameter function to each element of the original sequence in turn.
selectMany
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | { Type => sequence<Type2> } | sequence<Type2> |
Produces a sequence that is a concatenation of all sequences, which are all the results of applying the parameter closure to each element of the original sequence in turn. The statements skip and stop are available within the parameter closure.
distinct
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | none | sequence<Type> |
Produces a sequence, which contains all elements from the original sequence in the original order, with all the elements having cardinality exactly 1. Of all occurrences of an element in the original sequence only the first occurrence is included in the resulting sequence.
sortBy
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | { Type => Type2 } boolean |
sequence<Type> |
Produces a sequence with all elements from the original one in the order, which corresponds to an order induced by an imaginary sequence produced by applying the selector function to each element in the original sequence in turn. The selector function can be thought of as returning a key, which is used to sort elements in a sequence. The ascending parameter controls the sort order.
alsoSortBy
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | { Type => Type2 } boolean |
sequence<Type> |
Equivalent to sortBy, unless used as a chain operation immediately following sortBy or another alsoSortBy. The result is a sequence sorted with a compound key, with the first component taken from previous sortBy or alsoSortBy (which is also a compound key), and the last component taken from this operation.
sort
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | { Type, Type => int } boolean sequence<Type> |
Produces a sequence containing all elements from the original one in the order produced by applying the comparator function (passed as a closure literal or by reference) to a list with elements from the original sequence. The ascending parameter controls the sort order (order is reversed if the value is false).
Binary operations
intersect
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | sequence<Type> | sequence<Type> |
Produces a sequence containing elements contained both by the original sequence and the parameter sequence.
except
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | sequence<Type> | sequence<Type> |
Produces a sequence containing all elements from the original sequence that are not also members of the parameter sequence.
union
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | sequence<Type> | sequence<Type> |
Produces a sequence containing elements both from the original sequence and the one passed as a parameter.
disjunction
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | sequence<Type> | sequence<Type> |
Produces exclusive disjunction (symmetric difference) of the original sequence and the one passed as a parameter.
concat
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | sequence<Type> } | sequence<Type> |
Produces a sequence, which is a concatenation of the original one with the sequence passed as a parameter
Conversion
reduceLeft / reduceRight
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | { Type, Type => Type } | Type |
Operation reduceLeft/reduceRight applies the combinator function passed as a parameter to all elements of the sequence in turn. One of the function parameters is a sequence element, and another is the result of previous application of the function. Operation reduceLeft takes first two sequence elements and applies the function to them, then takes the result of the first application and the third sequence element, etc. Operation reduceRight does the same, but moving from the sequence's tail backwards.
- reduceLeft
- reduceRight
foldLeft / foldRight
| Operand type | Parameter type | Result type | Applicable for |
|---|---|---|---|
| seed sequence<Type> |
{ Z, Type => Z } | Z | foldLeft |
| seed sequence<Type> |
{ Type, Z => Z } | Z | foldRight |
Operation foldLeft/foldRight behaves similarly to reduceLeft/reduceRight, with the difference that it also accepts a seed value. Also the combinator function is asymmetric (it takes a Type and a Z parameters and returns a Z value). The result of the operation is of type Z.
- foldLeft
- foldRight
join
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence< ? extends string > | string (optional) | string |
This operation is only available on a sequence of strings. The result is a string that is produced by concatenating all elements with the optional separator. The default separator is " " (single space).
toList
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | none | list<Type> |
Returns new list containing all the elements from the original sequence.
toArray
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | none | Type*[]* |
Returns new array containing all the elements from the original sequence.
List
A basic list container backed by either array list or linked list.
List type
list<Type>
| Subtypes | Supertypes | Comparable types |
|---|---|---|
| none | sequence<Type> | java.util.List<Type> |
List creation
new arraylist
new linkedlist
| Parameter type | Result type |
|---|---|
| Type... {*}sequence<? extends *Type> |
list<Type> |
Creates an empty list. Optionally, initial values may be specified right in the new list creation expression.
Alternatively, a sequence may be specified that is used to copy elements from.
Operations on list
iterator
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | none | modifying_iterator<Type> |
This operation is redefined for list to return a modifying_iterator.
get
| Operand type | Parameter type | Result type |
|---|---|---|
| list<Type> | int | Type |
Yields the element at index position.
- indexed access
set
Operand type Parameter type Result type list<Type> int
TypeType Sets the element at index position to the specified value. Yields the new value.
- indexed access
add
Operand type Parameter type Result type list<Type> Type Type Adds an element to the list.
addFirst
Operand type Parameter type Result type list<Type> Type Type Adds an element to the list as the first element.
addLast
Operand type Parameter type Result type list<Type> Type Type Adds an element to the list as the last element.
insert
Operand type Parameter type Result type list<Type> int
TypeType Inserts an element into the list at the position index.
remove
Operand type Parameter type Result type list<Type> Type Type Removes an element from the list.
removeFirst
Operand type Parameter type Result type list<Type> none Type Removes the first element from the list.
removeLast
Operand type Parameter type Result type list<Type> none Type Removes the last element from the list.
removeAt
Operand type Parameter type Result type list<Type> int Type Removes an element from the list located at the position index.
addAll
Operand type Parameter type Result type list<Type> sequence<Type> list<Type> Adds all elements in the parameter sequence to the list.
removeAll
Operand type Parameter type Result type list<Type> sequence<Type> list<Type> Removes all elements in the parameter sequence from the list.
clear
Operand type Parameter type Result type list<Type> none void Clears all elements from the list.
reverse
| Operand type | Parameter type | Result type |
|---|---|---|
| list<Type> | none | list<Type> |
Produces a list with all elements from the original list in the reversed order.
| Important The reverse operation does not modify the original list, but rather produces another list. |
Set
A basic set container backed by either hash set or linked hash set.
Set type
set<Type>
| Subtypes | Supertypes | Comparable types |
|---|---|---|
| sorted_set<Type> | sequence<Type> | java.util.Set<Type> |
Set creation
new hashset
new linked_hashset
| Parameter type | Result type |
|---|---|
| Type... sequence<? extends Type> |
set<Type> |
Creates an empty set. Optionally, initial values may be specified right in the new set creation expression.
Alternatively, a sequence may be specified that is used to copy elements from.
Operations on set
iterator
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | none | modifying_iterator<Type> |
This operation is redefined for set to return a modifying_iterator.
add
| Operand type | Parameter type | Result type |
|---|---|---|
| set<Type> | Type | Type |
Adds an element to the set.
addAll
| Operand type | Parameter type | Result type |
|---|---|---|
| set<Type> | sequence<Type> | set<Type> |
Adds all elements in the parameter sequence to the set.
remove
| Operand type | Parameter type | Result type |
|---|---|---|
| set<Type> | Type | Type |
Removes an element from the set.
removeAll
| Operand type | Parameter type | Result type |
|---|---|---|
| set<Type> | sequence<Type> | set<Type> |
Removes all elements in the parameter sequence from the set.
clear
| Operand type | Parameter type | Result type |
|---|---|---|
| set<Type> | none | void |
Clears all elements from the set.
Sorted Set
A subtype of set that provides iteration over its elements in the natural sorting order, backed by a tree set.
Sorted Set type
set<Type>
| Subtypes | Supertypes | Comparable types |
|---|---|---|
| none | set<Type> | java.util.SortedSet<Type> |
Sorted set creation
new treeset
| Parameter type | Result type |
|---|---|
| Type... sequence<? extends Type> |
set<Type> |
Creates an empty set. Optionally, initial values may be specified right in the new set creation expression.
Alternatively, a sequence may be specified that is used to copy elements from.
Operations on sorted set
headSet
| Operand type | Parameter type | Result type |
|---|---|---|
| sorted_set<Type> | Type | sorted_set<Type> |
Results in a sorted_set that is a subset of all elements from the original set in the original sorting order, starting with the first element and up to but not including the specified element.
tailSet
| Operand type | Parameter type | Result type |
|---|---|---|
| sorted_set<Type> | Type | sorted_set<Type> |
Results in a sorted_set that is a subset of all elements from the original set in the original sorting order, starting with the specified element.
subSet
| Operand type | Parameter type | Result type |
|---|---|---|
| sorted_set<Type> | Type, Type | sorted_set<Type> |
Results in a sorted_set that is a subset of all elements from the original set in the original sorting order, starting with the first specified element and up to but not including the second specified element.
Map
A map container backed by either a hash map or a linked hash map.
Map type
map<KeyType, ValueType>
| Subtypes | Supertypes | Comparable types |
|---|---|---|
| sorted_map<KeyType, ValueType> | sequence< mapping<KeyType, ValueType> > | java.util.Map<KeyType, ValueType> |
The map type is retrofitted to be a subtype of sequence.
Map creation
new hashmap
new linked_hashmap
| Parameter type | Result type |
|---|---|
| (KeyType => ValueType)... | map<KeyType, ValueType> |
Creates an empty map. Optionally, initial values may be specified right in the new map creation expression.
Operations on map
get value by key
| Operand type | Parameter type | Result type |
|---|---|---|
| map<KeyType, ValueType> | KeyType | ValueType |
keys
| Operand type | Parameter type | Result type |
|---|---|---|
| map<KeyType, ValueType> | none | sequence<KeyType> |
Results in a sequence containing all the keys in the map.
containsKey
| Operand type | Parameter type | Result type |
|---|---|---|
| map<KeyType, ValueType> | KeyType | boolean |
Returns true if the map contains a mapping for the specified key, false otherwise.
values
| Operand type | Parameter type | Result type |
|---|---|---|
| map<KeyType, ValueType> | none | sequence<ValueType> |
Results in a sequence containing all the values in the map.
containsValue
| Operand type | Parameter type | Result type |
|---|---|---|
| map<KeyType, ValueType> | ValueType | boolean |
Returns true if the map contains a mapping with the specified value, false otherwise.
mappings
| Operand type | Parameter type | Result type |
|---|---|---|
| map<KeyType, ValueType> | none | set< mapping<KeyType, ValueType> > |
Results in a set of mappings contained by this map. The mappings can be removed from the set, but not added.
assign value to a key
| Operand type | Parameter type | Result type |
|---|---|---|
| map<KeyType, ValueType> | KeyType | ValueType |
remove
| Operand type | Parameter type | Result type |
|---|---|---|
| map<KeyType, ValueType> | KeyType | void |
Removes the specified key and the associated value from the map.
clear
| Operand type | Parameter type | Result type |
|---|---|---|
| map<KeyType, ValueType> | none | void |
Clears all key-value pairs from the map.
putAll
| Operand type | Parameter type | Result type |
|---|---|---|
| map<KeyType, ValueType> | map<KeyType, ValueType> | void |
Puts all mappings from the map specified as a parameter into this map, replacing existing mappings.
—
Sorted Map
A subtype of map that provides iteration over keys conforming to the natural sorting order, backed by a tree map.
Sorted map type
map<KeyType, ValueType>
| Subtypes | Supertypes | Comparable types |
|---|---|---|
| none | map<KeyType, ValueType> | java.util.SortedMap<KeyType, ValueType> |
Sorted map creation
new treemap
| Parameter type | Result type |
|---|---|
| (KeyType => ValueType)... | map<KeyType, ValueType> |
Creates an empty tree map. Optionally, initial values may be specified right in the new map creation expression.
Operations on sorted map
headMap
| Operand type | Parameter type | Result type |
|---|---|---|
| sorted_map<KeyType, ValueType> | KeyType | sorted_map<KeyType, ValueType> |
Results in a sorted_map that is a submap of the original map, containing all the mappings in the original sorting order, starting with the first key and up to but not including the specified key.
tailMap
| Operand type | Parameter type | Result type |
|---|---|---|
| sorted_map<KeyType, ValueType> | KeyType | sorted_map<KeyType, ValueType> |
Results in a sorted_map that is a submap of the original map, containing all the mappings in the original sorting order, starting with the specified key.
subMap
| Operand type | Parameter type | Result type |
|---|---|---|
| sorted_map<KeyType, ValueType> | KeyType, KeyType | sorted_map<KeyType, ValueType> |
Results in a sorted_map that is a submap of the original map, containing all the mappings in the original sorting order, starting with the first specified key and up to but not including the second specified key.
—
Stack
A simple stack abstraction, backed by linked list.
Stack type
stack<Type>
| Subtypes | Supertypes | Comparable types |
|---|---|---|
| deque<Type> | sequence<Type> | java.util.Deque<Type> |
Stack creation
new linkedlist
| Parameter type | Result type |
|---|---|
| Type... sequence<? extends Type> |
stack<Type> |
Creates an empty stack. Optionally, initial values may be specified right in the new linked list creation expression.
Alternatively, a sequence may be specified that is used to copy elements from.
Operations on stack
iterator
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | none | modifying_iterator<Type> |
This operation is redefined for stack to return a modifying_iterator.
addFirst / push
| Operand type | Parameter type | Result type |
|---|---|---|
| stack<Type> | Type | Type |
Appends an element to the head of the stack.
removeFirst / pop
| Operand type | Parameter type | Result type |
|---|---|---|
| stack<Type> | Type |
Removes an element from the head of the stack.
first / peek
| Operand type | Parameter type | Result type |
|---|---|---|
| stack<Type> | Type |
Retrieves the first element at the head of the stack without removing it.
—
Queue
A simple queue abstraction, backed by linked list or priority queue.
Queue type
queue<Type>
| Subtypes | Supertypes | Comparable types |
|---|---|---|
| deque<Type> | sequence<Type> | java.util.Deque<Type> |
Queue creation
new linkedlist
new priority_queue
| Parameter type | Result type |
|---|---|
| Type... sequence<? extends Type> |
queue<Type> |
Creates an empty queue. Optionally, initial values may be specified right in the new linked list creation expression.
Alternatively, a sequence may be specified that is used to copy elements from.
Operations on queue
iterator
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | none | modifying_iterator<Type> |
This operation is redefined for queue to return a modifying_iterator.
addLast
| Operand type | Parameter type | Result type |
|---|---|---|
| queue<Type> | Type | Type |
Appends an element to the tail of the queue.
removeFirst
| Operand type | Parameter type | Result type |
|---|---|---|
| queue<Type> | Type |
Removes an element from the head of the queue.
first
| Operand type | Parameter type | Result type |
|---|---|---|
| queue<Type> | Type |
Retrieves the first element at the head of the queue without removing it.
last
| Operand type | Parameter type | Result type |
|---|---|---|
| queue<Type> | Type |
Retrieves the first element at the tail of the queue without removing it.
—
Deque
A simple double-linked queue abstraction, backed by linked list.
Deque type
queue<Type>
| Subtypes | Supertypes | Comparable types |
|---|---|---|
| sequence<Type> queue<Type> stack<Type> |
java.util.Deque<Type> |
Deque creation
new linkedlist
| Parameter type | Result type |
|---|---|
| Type... sequence<? extends Type> |
deque<Type> |
Creates an empty deque. Optionally, initial values may be specified right in the new linked list creation expression.
Alternatively, a sequence may be specified that is used to copy elements from.
Operations on deque
iterator
| Operand type | Parameter type | Result type |
|---|---|---|
| sequence<Type> | none | modifying_iterator<Type> |
This operation is redefined for deque to return a modifying_iterator.
addFirst / push
| Operand type | Parameter type | Result type |
|---|---|---|
| deque<Type> | Type | Type |
Appends an element to the head of the deque.
addLast
| Operand type | Parameter type | Result type |
|---|---|---|
| deque<Type> | Type | Type |
Appends an element to the tail of the deque.
removeFirst / pop
| Operand type | Parameter type | Result type |
|---|---|---|
| deque<Type> | Type |
Removes an element from the head of the deque.
first
| Operand type | Parameter type | Result type |
|---|---|---|
| queue<Type> | Type |
Retrieves the first element at the head of the deque without removing it.
last
| Operand type | Parameter type | Result type |
|---|---|---|
| queue<Type> | Type |
Retrieves the first element at the tail of the deque without removing it.
—
Iterator
A helper type that is analogous to java.util.Iterator. An instance of the iterator can be obtained with the iterator operation on a sequence.
Iterator type
iterator<Type>
| Subtypes | Supertypes | Comparable types |
|---|---|---|
| modifying_iterator<Type> | none | java.util.Iterator<Type> |
Operations on iterator
hasNext
| Operand type | Parameter type | Result type |
|---|---|---|
| iterator<Type> | none | boolean |
Tests if there is an element available.
next
| Operand type | Parameter type | Result type |
|---|---|---|
| iterator<Type> | none | Type |
Returns the next element.
Modifying Iterator
A subtype of iterator that supports remove operation.
Modifying Iterator type
modifying_iterator<Type>
| Subtypes | Supertypes | Comparable types |
|---|---|---|
| none | iterator<Type> | java.util.Iterator<Type> |
Operations on modifying iterator
remove
| Operand type | Parameter type | Result type |
|---|---|---|
| modifying_iterator<Type> | none | none |
Removes the element this iterator is currently positioned at.
Enumerator
An alternative to the iterator, a helper type that works similar to .NET's IEnumerator. An instance of the enumerator can be obtained with the enumerator operation on a sequence.
Enumerator type
enumerator<Type>
| Subtypes | Supertypes | Comparable types |
|---|---|---|
| none | none | none |
Operations on enumerator
moveNext
| Operand type | Parameter type | Result type |
|---|---|---|
| enumerator<Type> | none | boolean |
Moves to the next element. Returns true if there is an element available.
current
| Operand type | Parameter type | Result type |
|---|---|---|
| enumerator<Type> | none | Type |
Returns the element this enumerator is currently positioned at.
Mapping
A helper type used by map and sorted_map.
Mapping type
mapping<KeyType, ValueType>
| Subtypes | Supertypes | Comparable types |
|---|---|---|
| none | none | none |
Operations on mapping
get value
| Operand type | Parameter type | Result type |
|---|---|---|
| mapping<KeyType, ValueType> | none | ValueType |
set value
| Operand type | Parameter type | Result type |
|---|---|---|
| mapping<KeyType, ValueType> | ValueType | ValueType |
get key
| Operand type | Parameter type | Result type |
|---|---|---|
| map<KeyType, ValueType> | none | KeyType |
Custom Containers
Custom containers is a simple way to provide own implementation of standard container types, thus allowing for easy extensibility of the collections language.
Example: weakHashMap
Provided the following declaration is reachable from the model currently being edited...
... one can use the weak version of hashmap thusly:
Custom Containers Declaration
A root node of concept CustomContainers may have one or more declarations.
| declaration part | allowed contents |
|---|---|
| containerName | any valid identifier |
| container_type | one of the existing container types of the collections language |
| runtime type | Java classifier which represent implementation of the container |
| factory | (optional) container creation expression; the classifier's default constructor used if undefined |
Primitive Containers
Collections framework include a set of custom containers designed to work with primitive data types. Using primitive types helps optimize speed and/or size of the containers. These containers are available with a separate language jetbrains.mps.baseLanguage.collections.trove.
Primitive list containers
list<?,?>
| byteArrayList | list<byte> |
| doubleArrayList | list<double> |
| floatArrayList | list<float> |
| intArrayList | list<int> |
| longArrayList | list<long> |
| shortArrayList | list<short> |
Primitive set containers
set<?,?>
| byteHashSet | set<byte> |
| doubleHashSet | set<double> |
| floatHashSet | set<float> |
| intHashSet | set<int> |
| longHashSet | set<long> |
| shortHashSet | set<short> |
Primitive maps
map<byte,?>
| byteByteHashMap | map<byte, byte> |
| byteDoubleHashMap | map<byte, double> |
| byteFloatHashMap | map<byte, float> |
| byteIntHashMap | map<byte, int> |
| byteLongHashMap | map<byte, long> |
| byteShortHashMap | map<byte, short> |
map<double,?>
| doubleByteHashMap | map<double, byte> |
| doubleDoubleHashMap | map<double, double> |
| doubleFloatHashMap | map<double, float> |
| doubleIntHashMap | map<double, int> |
| doubleLongHashMap | map<double, long> |
| doubleShortHashMap | map<double, short> |
map<float,?>
| floatByteHashMap | map<float, byte> |
| floatDoubleHashMap | map<float, double> |
| floatFloatHashMap | map<float, float> |
| floatIntHashMap | map<float, int> |
| floatLongHashMap | map<float, long> |
| floatShortHashMap | map<float, short> |
map<int,?>
| intByteHashMap | map<int, byte> |
| intDoubleHashMap | map<int, double> |
| intFloatHashMap | map<int, float> |
| intIntHashMap | map<int, int> |
| intLongHashMap | map<int, long> |
| intShortHashMap | map<int, short> |
map<long,?>
| longByteHashMap | map<long, byte> |
| longDoubleHashMap | map<long, double> |
| longFloatHashMap | map<long, float> |
| longIntHashMap | map<long, int> |
| longLongHashMap | map<long, long> |
| longShortHashMap | map<long, short> |
map<short,?>
| shortByteHashMap | map<short, byte> |
| shortDoubleHashMap | map<short, double> |
| shortFloatHashMap | map<short, float> |
| shortIntHashMap | map<short, int> |
| shortLongHashMap | map<short, long> |
| shortShortHashMap | map<short, short> |
<K> map<K,?>
| ObjectByteHashMap<K> | map<K, byte> |
| ObjectDoubleHashMap<K> | map<K, double> |
| ObjectFloatHashMap<K> | map<K, float> |
| ObjectIntHashMap<K> | map<K, int> |
| ObjectLongHashMap<K> | map<K, long> |
| ObjectShortHashMap<K> | map<K, short> |
Tuples
Tuples give you a way to group related data of different types into small collection-like data structures. In MPS, tuples are available within the jetbrains.mps.baseLanguage.tuples language.
Indexed tuples
Indexed tuple is a structure, which can contain several elements of arbitrary types and elements of which can be accessed by an index. The MPS implementation represents a tuple instance by a Java object. The usual meaning of '=' and '==' operations on Java objects within MPS remains unchanged.
Named tuples
Named tuples are similar to indexed tuples, with the difference that elements are accessed by name instead of by index. To use named tuples in the model you first need to explicitly define them in your model (new ->jetbrains.mps.baseLanguage.tuples/tuple).
Declaration of Pair:
Named tuple declaration
A root node of concept NamedTupleDeclaration contains a single declaration.
| declaration part | allowed contents |
|---|---|
| tupleName | any valid identifier |
| elementType | either a primitive type or any type that reduces to Java classifier |
| elementName | any valid identifier |
Dates language
An extension to the Base Language that adds support for dates.
Introduction
The Dates Language provides dedicated facilities to operate with date and time. Dates creation, comparison or adding and subtracting periods are done in a natural way, using common conventions and standard operators. As the backend implementation vehicle the Joda Time library is used.
Types
The following types are defined:
- instant represents the number of milliseconds passed since the epoch, 1970-01-01T00:00Z. It's represented with a long. Thus an instant can be exchanged freely for a long value and vice versa.
- datetime represents a date and time with time zone.
- duration represents an interval of time measured in milliseconds.
- period is a representation of a time interval defined in terms of fields: seconds, minutes, etc. A period is constructed using + (plus) operator.
- timezone represents a time zone.

Changes in 1.1
Old datetime type is renamed to instant. New datetime type contains timezone information. Use in expression to convert instant to datetime.
Predefined values
A special reserved keyword now represents the current time.
A reserved keyword never represents an instant before the beginning of times. All instants are placed after never on the time axis. Its actual representation is null.
Period constant contains of a number and a property. They can be summed up together to form more complicated periods.
Time zone can be created in different ways. All predefined values are available in completion menu. default timezone is a shortcut to the computer local time zone.
Converting between types
Datetime can be obtained from an instant by providing a time zone. For reverse conversion simply get the datetime's instant property.
Period can be converted to duration using toDuration operation. It converts the period to a duration assuming a 7 day week, 24 hour day, 60 minute hour and 60 second minute.
For compatibility there are ways to convert datetime or instant types to java.util.Date and java.util.Calendar.
Reverse conversion:
Properties
Each individual datetime field can be queried using dot expression.
To replace a field, use with expression.
Each period can be re-evaluated in terms of any field (with rounding if needed) leveraging the in operator.
Operations
Arithmetic
A period can be added to or substracted from a datetime. The result is a datetime.
Two datetimes can be subtracted, the result is a period.
A duration can be added to or subtracted from an instant. The result is an instant.
Two instants can be subtracted, the result is a duration.
Comparison
Two values of the same type (instant, datetime, period or duration) can be compared using standard operators: <, >, ==, etc.
Another form of comparison can be used for datetime by adding a keyword by following the field specification. In this case values are compared rounded to the field (see Rounding).
Minimum and maximum operations are defined for instant and datetime types.
Rounding
Datetime values can be rounded to the nearest whole unit of the specified field (second, minute, hour, day, month, etc). There are several ways of rounding:
- round returns the datetime that is closest to the original
- round down to returns the largest datetime that does not exceed the original
- round up to returns the smallest datetime that is not less than the original
Printing and Parsing
To print or parse datetime value we use date format describing its textual representation. The following formats are available by default:
- defaultFormat, rssDate
- shortDate, shortDateTime, shortTime, fullDate, longDate, etc (defined in Joda library)
Date format consists of one or more format tokens. The following kinds of tokens are supported:
- literal (referenced with a single quote) - any text, commonly used to insert dash or space
- datetime property (referenced with the name of a property in curly braces) - is replaced with the value of the property when printed
- switch - composite token, which may vary the format depending on the date
- offset (referenced as days ago, months ago, etc.) - calculates the difference between the provided datetime and the current moment
- reference (the name in angle brackets) - a way to include existing format
Additional date formats can be introduced using the j.m.baseLanguage.dates.DateFormatsTable root concept. Each format has a name and visibility. Formats with private visibility are local for the table.
Datetime instance can be printed in the form of existing format by # operation.
Another possibility is to use {{#} operation, which allows to define format in-place.
Both printing operations accept optional locale argument in parentheses.
Parse operation accepts string, date format, timezone and two optional parameters: default value and locale.
| Changes in 1.1 New print/parse expressions operate on datetime instead of instant. Use intention to convert deprecated expressions to a new ones. |
Regular expressions language
Regular expressions is one of the earliest DSLs in wide use. Nearly all modern programing languages these days support regular expressions in one way or another. MPS is no exception. Regular expression support in MPS is implemented through a base language extension.
We also recommend checking out the Regular Expressions Cookbook, to get a more thorough introduction into the language.
Defining regular expression.
Regexp language allows you to create an instance of java.util.regex.Pattern class using a special pattern expression: /regexp/. In the generated code, MPS creates for each defined pattern expression a final static field in the outermost class, so the pattern is compiled only once during application runtime.
There are three options, you can add after the ending slash of the regexp.
| /i | Case-insensitive matching |
| /s | Treat string as single line, dot character class will include newline delimiters |
| /m | Multiline mode: ^ and $ characters matches any line within the string (instead of start or end of the string) |
The options can be turned on/off by typing or deleting the character in the editor, or through the Inspector. Generated regular expression preview is available in the Inspector.
Re-using Definitions
To reuse a regular expression for a frequently used pattern accross your project, create a separate root:
model -> New -> jetbrains.mp.baseLanguage.regexp -> Regexps
Each reusable regular expression should have a name and optionally a description.
Pattern Match Operator
The =~ operator returns true if the string matches against the specified pattern.
Capturing Text
Optional use of parentheses in the expression creates a capture group. To be able to refer to the group later, the best practice is to give it a name.
Examples
If the pattern matches against the string, the matched value is captured into identifier and can be accessed in the if-block.
Don't forget to check out the Regular Expressions Cookbook, to get a more thorough introduction into the language.
GText language
TODO
Unit Test Language
TODO
Type Extension Methods
The language jetbrains.mps.extensionMethods provides a way to extend any valid MPS type with newly defined or overriden methods, akin to Java static methods.
Whereas static methods never become an internal part of the extended class and one has to always specify the "extended" object to operate on as one of the parameters to the extended method, with an extension method the new method gets added directly to the list of operations available on the target type.
So, provided we wanted to add a reverse method to the string type, instead of the good old "static method" way:
we would create new Extension Methods through New -> j.m.baseLanguage.extensionMethods/type extension, define the new method and tie it to the string class:
The very same mechanism can be used to override existing methods. And when in need to call the original method, just call it on this:
Since MPS does a good job to visually distinguish the original and overriden methods through the extension methods mechanism, you can't make a mistake picking the right one from the drop-down list. 
Obviously this mechanism can be used to implement orthgonal concepts on your own domain objects as well:
With the declaration as above, one could write an operation on type my_type:
Root Nodes
There are two equally good ways to extend types with methods. Type Extension allows to you to add methods to a single type in one place, while Simple Extension Method Container comes in handy, when you need one place to implement an orthogonal concept for multiple different types.
Type Extension
This root contains declarations of extension methods for a single type.
Extension method declaration.
Simple Extension Method Container
Extension method declaration. The target type is specified per method.
| declaration part | allowed contents |
|---|---|
| containerName | any valid identifier |
| extendedType | any valid MPS type |
Both roots may contain one or more static fields. These are available to all methods in the container.
—
XML Language
TODO
Here we introduce some handy BaseLanguage extensions
Checked dots
Language: jetbrains.mps.baseLanguage.checkedDots
A Checked Dot Expression is an dot expression extended with null checks on the operand.
If the operand is null, the value of the whole checked dot expression becomes null, otherwise it evaluates to the value of corresponding dot expression.
Ways to create a Checked Dot Expression
- The Make dot expression checked intention
- Enter "?" after dot, e.g. customer.?address.?street
- Left transform of operation with "?"
You can transform checked dot expressions to the usual dot expressions using the Make dot expression not checked intention
Overloaded operators
Language: jetbrains.mps.baseLanguage.overloadedOperators
This language provides a way to overload binary operators.
Overloaded operator declarations are stored in an OverloadedOperatorContainer.
If there are several overloaded versions for one operator the most relevant is chosen.
Note that if an overloaded operators' usage is in the other model than its declaration, overloadedOperators language should be added to "languages engaged on generation" of usage's model.
Examples
Overloading plus operator for class Complex:
Also, you can define your own custom operators. Assume we want to create a binary boolean operator for strings, which tells if one string contains another:
Now, we can simply use this operator:
Custom constructors (since 1.5.1)
Language: jetbrains.mps.baseLanguage.constructors
Custom constructors provide a simple way to create complex objects. They are stored in a special root node - CustomConstructorsContainer.
Example
Assume we need a faster way to create rectangle.
Now, let's create a rectangle:
Language test language
Introduction
Language test language provides useful interface for making test for languages. It lets describe nodes properties using annotations.
Tests creation
In order to create tests LanguageTest aspect should be created. In popup menu of language chose New->New LanguageTest Aspect.
Then create NodesTestCase concept in model languageTest.
Language test consists of three sections:
- List of nodes to test. This nodes are not generated into code and remain as is. This nodes can be referenced by adding adding annotations.
- Methods witch performs testing.
- Utility methods, than can be used to avoid code duplicates.
Simple tests
In order to check node operations reference to node is created at first. To refer to node TestAnnotation is used. Annotation is added to node by intention, clicking or pressing (Alt + Enter).
Each annotation has name and can be referenced by this name from code.
Then test code should be written in this code is performing operations with nodes. In order to write assertions of Unit test language assertion are used. To compare nodes new assertion added:
assert <node1,node2, ...> match <node1,node2, ...>
compares two lists of nodes.
Example of test:
Type tests
In order to check type of node following instruction can be used.
assert node has type t
It checks that node has type t.
In order to check node and in descendants for errors following operation is used.
check node type errors;
Properties, witch should be checked are specified by annotations, witch can be added by intention Add node properties annotation.
Node can have following properties:
- Node has error;
- Node has warning;
- Node has specified type.
If node hasn't any annotation it checked for absence of errors and warnings.
There is example of type checking:
Instead of check ... type errors statement, operation annotation can be used. It can be added by Add test operation annotation intention.
Data flow tests
To check data flow following statement should be added.
check node dataflow
It calculates dataflow for node, and check properties specified by annotations. Following properties can be checked.
- reachable - this node reachable;
- unreachable - this node unreachable;
- variable initialized - variable initialized in this node;
- variable live - variable live in this node.
Instead if using check node dataflow check dataflow operation can be added.
Tests running
Model should be generated before running tests. To start tests shortcut (Ctrl + Shift + F10) or pop-up menu item.
Build Languages
MPS has a group of languages responsible for build process. Those languages are:
- jetbrains.mps.buildlanguage (build language) – an MPS counterpart of Apache Ant;
- jetbrains.mps.build.property (property language) – an extension to build language designed for writing property files;
- jetbrains.mps.build.packaging (packaging language) – a high-level language for building software packages, including languages made in MPS.
- jetbrains.mps.build.custommps (custom mps language) – an extension to packaging language, allowing to build your own custom MPS distribution.
Table of contents
Build Language
Introduction
Build language is an MPS counterpart of Apache Ant. Like in Ant, the each build script in build language is a project. A project can contain property and target declarations, imports of property files and other projects, task calls. A build language task library has all core Ant tasks and most of all optional Ant tasks.

On the above screenshot you can see an example project on build language – HelloWorldProject. It has a property declaration – hello.world.text property and a target declaration – default target, which is default target for this project. The default target calls echo task to output the value of the hello.world.text property to the console. Here is the Ant script, generated from HelloWorldProject.
You can see, that the generated Ant file looks very similar to input project.
Features
Though the build language seems to be very similar to Ant, it has some differences with it. Those differences are described below.
Typesystem
When calling a task in Ant, values are set to it's attributes. In build language a typesystem is declared for validating those values. Each attribute has a type. Available types are: boolean, file, integer, reference and string. Some attribute's values are restricted with enumerations of some type. For example Ant task zip has an attribute duplicate which allow string values "add", "preserve" and "fail".
Properties also have types. For example, as you can see on the screenshot of HelloWorldProject, property hello.world.text has type string.
Expressions
An expression can be assigned to an attribute. Simple expressions are string, integer and boolean literals, files, references to properties, tasks and targets. Two types of composite expressions are available: plus operation, which is a simple concatenation of two expressions and a multi-line expression which is also a concatenation but written in several lines.
External Properties
External properties are useful when a build environment can pass to the Ant script some values, like build number or vcs revision number. They provide ability to use those values in the script safely.
External property is a special kind of property. They also can be defined in the beginning of build language project or task and be used along with ordinary properties, but they can't be assigned a value. A useful feature of an external property is that they are automatically checked and if they are not set by the environment, the script or task fail.
Let's see a simple script, demonstrating several kinds of external properties.

As you can see, three external properties are defined in the beginning on the project and one in the beginning of the default task. Property some.really.important.property is a checked property. This means, that when the Ant build would fail on start if it wont be set by build environment. The other two properties are checked only in tasks where they are used.
Property some.internal.property is visible in the default task and checked on it's start.
Here is the Ant script, generated from the project.
Buildlanguage allows user to use standart ant tasks and some optional tasks like junit. Tasks are located in models jetbrains.mps.build.generictasks.generated and jetbrains.mps.build.generictasks.optional. To use this tasks one should import language jetbrains.mps.build.generictasks.
Using Build Language
This section describes stuff, which makes build language usage more comfortable.
Running Build Language Project
For executing build language projects MPS has special run configuration "Build". It could be created from "Run/Debug Configurations" dialog:


Also a project could be runned from context menu:

Build output is shown in "Run" tool window:

Packaging Language
Introduction
When we think about building a project, we often do not think about a process of building, we usually imaging the result we want to have, for example, an archive with a program, documentation and libraries. We want a tool, which could take a description of the desired artifacts and generated a build script, for example an Ant file. That is one of the reasons why packaging language was made. The second reason for creating this language was lack of ability of simple deployment procedure for languages created in MPS.
Language Structure
This section describes the packaging language structure.
Defining Build Structure
Each packaging language script consists of two sections. The first one is a place for build properties, like base directory, and some build stuff, like variables or configurations. The second part is used for defining a build structure itself.
Let's talk more about the second part.
The build structure is written exactly like it should be in the resulting program distribution. There are several language elements you can use to describe it, for example, Folder, Zip, Jar and others (see all language elements). Some element can contain others, like in your distribution an archive could contain files in it.

On the above screenshot you can see an example of packaging language script. In the build structure part a Zip element project.zip contains Folder element project, which contains Folder element copied from folder sources, and this element also contains File copied from build.number. This means that we want a build script to create a zip archive project.zip with a folder project, copy sources folder with a copy of build.number inside into project folder. As you can see, the definition is very literal.
Language elements, used in build structure description, are listed in language elements table. Next sections are considering some special features of packaging language.
Build Configurations
Sometimes build should be done in several slightly different ways. For example, we want to create two types of build: for other developers – with sources and some additional tools and for users. Packaging language allow to deal with this problem using build configurations. Let's see how it is done for already mentioned example.

On the above screenshot a build script example is shown. Configurations are defined in configurations sections. As it can be seen, the script defines two configuration: dev and external for two build types. Some build elements marked with them. Elements, which are not marked at all, are included in all types of build. Those elements are Folder "project" and Folder "languages". Other elements marked with configuration dev. They are included in build for developers and not included in build for users. Those elements are Folder "devtools" and Folder "src".
Variables
Variables allow to use properties, passed to the script through command line, in elements names. Variables are declared in variables section of the build script. To declare a variable, you must define it's name and a name of ant property, which would be passed to the script by your build environment. On the below screenshot you can see an example of how variables declarations look like.

The shown section defines three variables: build, revision and configuration.
Variables can be used in titles of all elements in script. For example, having variables, defined on a previous screenshot, one can write a script like shown on a screenshot.

In this script a build number is used in a name of a zip archive with the project and build parameters are written into a file build.info.
You can use not only variables, defined in the script, but also a number of predefined variables which are listed in the following table.
| Variable Name | Ant Name | Description |
|---|---|---|
| basedir | basedir | Base directory of the script. |
| date | DSTAMP | Current date. |
| \n | line.separator | System line separator. |
| / | file.separator | System file separator. |
| : | path.separator | System path separator. |
Macro
Some packaging language elements, such as folder or copy allow to enter a source path – a name of some file or directory. In many cases leaving those paths absolute would be a bad idea. That is why packaging language afford an opportunity to use macro in names of files and directories.
Two types of macro exists: path variables defined in "Settings" -> "IDE Settings" -> "Path Variables" and predefined macro: basedir – a base directory of build script (specified in the script beginning) and mps_home – an MPS installation directory. Build script base directory definition also allow usage of macros but with exception of basedir.
Macro used in a build script, will be generated to checked external properties of build language. This means, they will be checked in a build start and a build would fail, if their values were not specified.
Of course, packaging language does not force users using macro. To enter an absolute path you can select no macro item from macro menu.
Blocks
Blocks were introduced for structuring large builds. Essentially, a block is a part of build, a group of build elements. A block could be referenced from the main script. During generation, a contents of a block is copied to places if is referenced from. On the following screenshot there is a block defining a part of MPS build script.

Apart from a title and contents, a block can define which build script it is referenced from. This way variables from main build script could be referenced from the block.
Language Elements Table
This table gives a brief description of packaging language elements.
| Element | Notation | Description |
|---|---|---|
| Antcall | antcall [target declatation] from [project] <excludes patterns> <includes patterns> |
A call of an ant target.
|
| Block Reference | -> [block name] |
A reference to a block. One could use blocks to split big scripts in parts. |
| Copy | copy from [source] <excludes patterns> <includes patterns> |
A copy of folder contents.
|
| Echo | echo [message] > <title> |
Echoes some message to the output stream or to the file.
|
| File | file <title> from <source> |
A file.
|
| Folder | folder <title> from <source> <excludes patterns> <includes patterns>
<entries>
|
A folder.
|
| Jar | jar [title] <excludes patterns> <includes patterns>
<entries>
|
A jar archive.
|
| Module | module [name] |
A packaged language, solution or descriptor.
|
| Replace | replace <title> from [source]
<pairs token-value>
|
Make replacements in file.
|
| Plugin | plugin <title> from [source] |
Idea plugin jar.
|
| Zip | zip [title] <excludes patterns> <includes patterns>
<entries>
|
A zip archive.
|
Using Packaging Language
This section describes usage of packaging language.
Generating Build Scripts For Your Project
MPS allow to easily generate a build script on a packaging language for languages in your project. This functionality is very useful when you have a really big project and you do not want to enumerate every module name by hands.
Let's see, how it is done using complex language as an example.
Let's open a complex language project which is located in file %MPS_HOME%/samples/complexLanguage/Complex.mpr. A first step in generating build process is calling a build generation wizard, by selecting "New" -> "Build Script" in project popup menu.

At first, the wizard asks to select, whether a new solution would be created for a build script, or an old one should be picked. For this example we select to create a solution named Complex.build.

The next step is to choose a model for a build script. If we selected an existing solution on previous step, we could use one of the models in that solution. But as we create a new solution for our build script, we have only one option: to create a new model. Let's call the new model Complex.build.

The last step is to select modules to include into build. We choose only two modules: the language jetbrains.mps.samples.complex and it's runtime solution jetbrains.mps.samples.complex.runtime.

After pressing finish button, a resulting script is opened. Here is a build for complex language project.

Generating Files From Build Script
For generating files from a build script a "Generate Build Files" action can be used.

It generates the model with build and place output files to build script's base directory, so the files a ready to use. For complex language build script, created in previous section, those files would be:
| File | Purpose |
|---|---|
| Complex-default.xml | The main script for building default configuration. |
| Complex-compile.xml | Compilation script. |
| Complex-languages.xml | Script for packaging modules into jars. |
| Complex.properties | Property file. |
Running Build Scripts
To run build scripts from MPS it has a run configuration "Packaging Script". It could be created via "Run/Debug Configurations" dialog:


Build script could also be runned from context menu:

The build output is shown in "Run" tool window:

Custom MPS Language
Introduction
The purpose of custom MPS language is to allow a programmer to create MPS distribution containing his own languages, so that users would be able to use those languages straight away.
The language adds two constructions into packaging language: mps-build and library. library is a special folder inside of MPS build, where packaged modules are located. While building MPS, a special configuration file is created and added to the build where all library folders are listed, so MPS will know, where to look for modules.
Building custom MPS for your project
In this section we will build a custom MPS distribution for a sample project. Our project will have three simple languages.

Creating custom MPS build script using a wizard
To create a custom MPS build script for the project, select "New" -> "Custom MPS Build Script" from project popup menu.

This action starts a wizard. The first step is to select a location of special zip file, MPS-buildTools.zip, containing files required to build MPS. This file can be downloaded from http://jetbrains.com/mps/download.

Next, the wizard asks to select, whether a new solution would be created for a build script, or an old one should be picked. For this example we select to create a solution named SimpleProject.build.

The next step is to choose a model for a build script. If we selected an existing solution on previous step, we could use one of the models in that solution. But as we create a new solution for our build script, we have only one option: to create a new model. Let's call the new model SimpleProject.build.

The last step is to select modules, included in build. Let's select all languages in project and exclude a solution jetbrains.mps.simple1.sandbox.

After pressing "OK" button the wizard creates a build script.

As you can see, resulting script has two libraries: simplenamespace library for modules in a namespace simpleNamespace and simpleproject library for modules with empty namespace.
Running custom MPS build script from MPS
To run custom MPS build script use "Custom MPS Script" run configuration.


You can also use a context menu item:

Running custom MPS build script manually
To run custom MPS script by hands you should generate build files by selecting "Generate Build Files" action from model's popup menu.

The files are generated into a folder inside of build script base directory. In our case it's folder "build" in project base directory.
~/MPSProjects/SimpleProject/build$ ls help-build.xml installer.nsi mps.sh SimpleProject-compile.xml SimpleProject-default.xml SimpleProject.properties Info.plist mps.bat mps.vmoptions SimpleProject-default-dist.xml SimpleProject-languages.xml
Here is the table with description of generated files.
| File | Description |
|---|---|
| help-build.xml | Script with different utility targets. |
| Info.plist | Configuration file for Mac OS X. |
| installer.nsi | Script for generating windows installer. |
| mps.bat | Windows startup file. |
| mps.sh | Unix startup file. |
| mps.vmoptions | File with java VM options. |
| {YourProjectName}-compile.xml | Compilation script. |
| {YourProjectName}-default-dist.xml | Main script, packaging MPS for different platforms. |
| {YourProjectName}-default.xml | Script for creating folder with MPS. |
| {YourProjectName}-languages.xml | Script for packaging modules into jars. |
| {YourProjectName}.properties | Properties file. |
To start custom MPS build you should use {YourProjectName}-default-dist.xml file (in our case it's SimpleProject-default-dist.xml). It requires mps_home property to be set to the directory, containing MPS. To run build from console you can use the following command.
ant -f {YourProjectName}-default-dist.xml -Dmps_home={PathToMPS}
Build results
Custom MPS build script creates folder "artifacts" in script base directory (in our case it's project home directory) with MPS build for different platforms. They all are marked with number of MPS build used when running build script.
| Artifact Name | Description |
|---|---|
| MPS-{build.numer}.zip | Crossplatform build with bat and sh startup scripts. |
| MPS-{build.numer}-windows.exe | Windows installer (created only when running build script on windows). |
| MPS-{build.numer}-macos.zip | Zip for MacOsX. |
| MPS-{build.numer}-linux.tar.gz | Gzipped tar archive for Linux. |
Let's unpack MPS-{build.numer}.zip and see, what is inside it.
~/MPSProjects/SimpleProject/artifacts/MPS$ ls about.txt build.number entryPoints.xml license mps.sh plugin readme.mps.txt samples.zip SimpleProject bin core lib mps.bat platform plugins readme.txt simpleNamespace workbench
Here we see folders "simpleNamespace" and "SimpleProject".
~/MPSProjects/SimpleProject/artifacts/MPS/simpleNamespace$ ls jetbrains.mps.simple2.mpsarch.jar jetbrains.mps.simple3.mpsarch.jar
Folder "simpleNamespace" contains two packed languages jetbrains.mps.simple2 and jetbrains.mps.simple3.
~/MPSProjects/SimpleProject/artifacts/MPS/SimpleProject$ ls jetbrains.mps.simple1.mpsarch.jar
Folder "SimpleProject" contains packed language jetbrains.mps.simple1.
Let's start MPS to make sure that those languages are available in it. Select "File" -> "Settings" -> "Ide Settings" -> "MPS Library Manager". You can see, that there are new simplenamespace and simpleproject libraries pointing to "simpleNamespace" and "SimpleProject" directories respectively.

Let's create a test project and try to write something on jetbrains.mps.simple1 language. Let's create a solution "Test.sandbox" with a model "Test.sandbox.sandbox" and import
jetbrains.mps.simple1 into it. All three languages we wanted to pack are loaded by MPS and available for import.

Now we can create SimpleConcept1 instance in a model.


GWT Support
The language jetbrains.mps.gwt.client provides basic support for creating and editing of GWT Module XML files. Module manifests are represented as root nodes interlinked with source code and other manifests, including ones from .jar files. The jar files for gwt-dev.jar and gwt-user.jar are included with the language.
Supported GWT SDK versions are 2.0 onward.
GWT Module root
Supported elements. Please refer to GWT SDK documentation for details.
inherits
Specify the module to inherit settings from. In order to inherit from a GWT module contained in another model, that model must be imported first.
entry-point
Specify a class that will be used as the entry point. Must implement com.google.gwt.core.client.EntryPoint.
Filtering paths
The following three elements support following features, analogous to GWT module XML file's elements.
- excludes and includes parameters
- casesensitive parameter
- defaultexcludes parameter
- include and exclude sub-elements
If no path is explicitly specified, the current model is assumed.
public
Define a path with public resources.
source
Define a source path (package).
super-source
Define a source path stripping off the prefix.
—
(C) Copyright JetBrains, s.r.o. 2003, 2008. All Rights Reserved
JetBrains MPS includes software developed by
- The Apache Software Foundation (http://www.apache.org/)
- The W3C consortium (http://www.w3c.org)
- The SAX project (http://www.saxproject.org)
- The jMock project (http://jmock.org/)
- INRIA, France Telecom (http://asm.objectweb.org/). Copyright (c) 2000-2005 All rights reserved.
- JDOM Project (http://www.jdom.org/) Copyright (C) 2000-2002 Brett McLaughlin & Jason Hunter. All rights reserved.
- NanoContainer Organization (http://www.picocontainer.org/). Copyright (c) 2003
- The HTML Parser project (http://htmlparser.org)
- Original Reusable Objects, Inc (http://www.savarese.org/oro/index.html). Copyright (c) 1996, 1997
- Joe Walnes (http://xstream.codehaus.org/), Copyright (c) 2003-2005. All rights reserved.
- Eclipse Software Foundation (http://www.eclipse.org/)
- Kent Beck, Erich Gamma, and David Saff (http://www.junit.org/)
- Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, California 95054, U.S.A. (http://beansbinding.dev.java.net/) Copyright (c) 2005-2007 All rights reserved.
- Sun Microsystems (http://java.sun.com/javase/technologies/desktop/javahelp/)
- INCORS GmbH (http://lookandfeel.incors.com/)
- YourKit, LLC (http://yourkit.com)
- JGoodies, Karsten Lentzsch (http://www.jgoodies.com/freeware/forms/), Copyright (c) 2002-2006. All rights reserved.
- Michael Baranov, (http://microba.sourceforge.net/), Copyright (c) 2005-2006. All rights reserved.
- Marc De Scheemaecker, (http://devkix.com/nanoxml.php/), Copyright (C) 2000-2008. All Rights Reserved.
- Eric D. Friedman (http://trove4j.sourceforge.net/), Copyright (c) 2001, All Rights Reserved
- CERN - European Organization for Nuclear Research (http://trove4j.sourceforge.net/), Copyright (c) 1999
- JNA Project (https://jna.dev.java.net/)
- JFCUnit (http://jfcunit.sourceforge.net/), Copyright (C) 2001 Matt Caswell, Greg Houston, Copyright (C) 2002 Vijay Aravamudhan, Kevin Wilson
- Google Web Toolkit (http://code.google.com/webtoolkit/download.html), Copyright (C) 2006 Google Inc
You can find licenses for these software in the license directory under a MPS installation.
Previous














