Skip to end of metadata
Go to start of metadata

The purpose of SModel language is to query and modify MPS models. It allows you to investigate nodes, attributes, properties, links and many other essential qualities of your models. The language is needed to encode several different aspects of your languages - actions, refactorings, generator, to name the most prominent ones. You typically use the jetbrains.mps.lang.smodel language in combination with BaseLanguage.

Treatment of null values

SModel language treats null values in a very safe manner. It is pretty common, in OO-languages, such as Java or C#, to have a lot of checks for null values in the form of expr == null and expr != null statements scattered across the code. These are necessary to prevent null pointer exceptions. However, they at the same time increase code clutter and often make the code more difficult to read. In order to alleviate this problem, 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. This should make your life as a language designer easier.

Types

SModel language has the following types:

  • node<ConceptType> - corresponds to an AST node (e.g. node<IfStatement> myIf = ...)
  • nlist<ConceptType> - corresponds to a list of AST nodes (e.g. nlist<Statement> body = ...)
  • node-ptr<ConceptType> - represents a pointer to a node, can be resolved into a node<ConceptType> when provided a repository
  • model - corresponds to an instance of the MPS model
  • model-ptr - represents a pointer to a model, can be resolved into a model when provided a repository
  • search scope - corresponds to a search scope of a node's reference, i.e. the set of allowed targets for the reference
  • reference - corresponds to an AST node that represents reference instance
  • concept<Concept> - corresponds to the org.jetbrains.mps.openapi.language.SConcept concept that represents a concept (e.g. concept<IfStatement> = concept/IfStatement/)
  • conceptNode<Concept> - (deprecated) corresponds to an AST node that represents a concept (e.g. conceptNode<IfStatement> = conceptNode/IfStatement/)
  • enummember<Enum Data Type> - corresponds to an AST node that represents an enumeration member (e.g. enummember<FocusPolicy> focus = ...)

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. E.g. myNode.ancestors<concept = IfStatement, concept = ForStatement>.

The "+" symbol as a parameter to these operations means "include myself". That is, when the node, on which the operation is being invoked, matches the condition, "+" ensures that the node will also be included in the returned result - e.g. myNode.ancestors<concept = IfStatement, +> may return myNode, if it is an IfStatement.

Icon

MPS allows you to down-cast from the concepts of the smodel concepts to the underlying Java API (Open API), may you need more power when manipulating the model. Check out the Open API documentation for details.

Comparison

The :eq: and :ne: operators can be used to compare nodes for equality. The operators are null-safe and will compare the whole sub-trees represented by the two compared nodes.

Queries

Getting nodes by name

Use the node-ptr/.../ construct to obtain a reference to a node using its name.

To check that a node is a specific one, there is the "is" operation available.

equals.is(Object->equals);

To get a pointer to a node, use the pointer construct:

Getting concepts by name

Use the concept/.../ construct to obtain a concept declaration by specifying its name:

The concept switch construct can be used to branch off the logic depending on the concept at hands:

Features access

The SModel language can be used to access the following features:

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

Combine this with the null-safe dot operator in the smodel language and you get a very convenient way to navigate around the model:

Icon

Intention are available to easily migrate from one type of cast expression to the other:

Node collection cast

A collection of nodes can be filtered and cast by the concept of the nodes using the ofConcept construct:

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. E.g. classDef.children<annotation, member>

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: +

E.g. myNode.ancestors<concept = InstanceMethodDeclaration, +>

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: +

E.g. myNode.descendants<concept = InstanceMethodDeclaration>

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.

For example,

  • node<> containingRoot = myNode.containing root
  • model owningModel = myNode.model

Model queries

The model-ptr/.../ expression retrieves a resolvable reference to a model. With a repository it can be resolved into the model<> type.

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 the 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

E.g. model.roots(<all>) or model.nodes(IfStatement)

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:

The 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:

E.g. concept<IfStatement> concept = concept/IfStatement/

Concept operation

If you want to find the concept of a specified node, you can call the concept operation on the node.

E.g. concept<IfStatement> concept = myNode.concept

Migrating away from deprecated types

The conceptNode<> type as well as the conceptNode operation have been deprecated. The asConcept operation will convert a conceptNode<> to a concept<>. The asNode operation, on the other hand, will do the opposite conversion and will return a node<AbstractConceptDeclaration> for a concept<>.

Icon

The conceptNode<> type was called concept<> in MPS 3.1. The conceptNode operation was called concept in MPS 3.1.

Concept hierarchy queries

We can query super/sub-concepts of expression with the concept type. The following operations are at your disposal:

  • super-concepts/all - returns all super-concepts of the specified concept. There is an option to include/exclude the current concept - super-concepts/all<+>
  • super-concepts/direct - returns all direct super-concepts of the specified concept. Again, there is an option to include/exclude the current concept - super-concepts/direct<+>
  • sub-concepts - returns sub-concepts

For example:

concept<IfStatement> concept = myNode.concept; 
list<concept<>> superConceptsAll = concept.super-concepts/all; 
concept.super-concepts/direct<+>; 
concept.sub-concepts(model);
concept<IfStatement> concept = myNode.concept; 
list<concept<>> superConceptsAll = concept.super-concepts/all; 
concept.super-concepts/direct<+>; 
concept.sub-concepts(model, myScope);

The hasRole operation

Sometimes we may want to check whether a node has a particular role. For this we have the following syntax:

For example,

myNode.hasRole(IfStatement : elsifClauses) 

Link queries

The linklinkName and linkNode operations  give you access to the details of a link between nodes.

 

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 "linkQualifier" 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 the 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:

For example,

myNode/.getConcept().findProperty("name")


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.

For example,

  • classDef.name = "NewClassName";
  • classDef.name.set( "NewClassName");
  • myNode.condition = trueConstant;
  • node<InstanceMethodDeclaration> method = classDef.member.add new initialized(InstanceMethodDeclaration);

When setting a target to a reference link, there is no need to access a target node. Having a pointer to the target is enough. This is possible with the set ptr operation, which is applicable to reference link access expressions.

node.instanceMethodDeclaration.set ptr(Object->equals)

Similarly, to check that a node is a specific one, there is the "is" operation available.

node.parent.is(Object->equals);

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()
  • new instance operation on a concept: concept.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

    Icon

    Note that the jetbrains.mps.lang.actions language adds the possibility to initialize the newly created nodes using the rules specified in NodeFactories. Upon importing the jetbrains.mps.lang.actions language you are able to call:

    • new initialized node<Concept>()
    • model.new initialized node(Concept)
    • node.new initialized next/previous sibling(Concept)
    • add new initialized(Concept)
    • set new initialized(Concept)
    • replace with new initialized(Concept)
    • replace with initialized next/previous-sibling(Concept)

Copy

To preserve the tree shape of the model, a node can only have at most one parent. As soon as you add a node as a child to a parent node, the node is detached automatically from any previous parent node that it may have had. Creating copies of nodes, including their sub-trees, may thus come handy. To create a copy of an existing node, you can use the copy operation. E.g., node<> yourNode = myNode.copy

Replace with

To replace a node in the AST 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 delete a node from the model, you can use the detach operation. You can still add the detached node to the model later.

smodel.query language

The jetbrains.mps.lang.smodel.query language enables the same type of queries that the MPS Console uses:

The scope can be constrained to a project, module, model or a sequence of these.

Operation parameters

The behavior of smodel.query operations can be slightly changed using operation parameters, which can be specified after the operation name. 

Possible parameters include:

scope - Each command operates in the scope specified in the surrounding with-statement. The scope parameter changes the operating scope for a single command.

exactcan be used in #instances operations to find instances of the concept specified, excluding instances of descendant concepts

 

Scope specification

Scope on which queries are performed can be specified explicitly for single query using scope parameter This option overrides scope derived from with statement.

The scope parameter can consist of sequence of models or modules, or can be an arbitrary expression of type SearchScope.


When the scope parameter of a query is not specified, the query is performed on the scope constructed by the enclosing with statement. To avoid confusion, nested with statements are not supported. With statements can construct a scope from a model, a module, a sequence of those, from a project of from an existing SearchScope object.

Scope constructed by the with statement include only editable models so that modifying operations can be safely performed within that scope.


Icon

When scope is set explicitly as query parameter, query uses it as is and does not throw away read only models, so the two following queries show different behavior:

Using smodel queries in console plugin

Queries can be used in console without the wrapping with statement. Default scope there for queries that do not specify it explicitly contains all editable models in the current project. Also, some additional options are available when specifying the scope explicitly.

Queries defined in the smodel.query language

#instances - fast search for instances of a specified concept

#usages - fast search for usages of a specified node

#modules - all modules in scope

#models - all models in scope

#nodes - all nodes in scope

#references - all references in scope

 

 
Previous Next

  • No labels