MPS 2021.2 Help

SModel language Queries

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.

Ndptr1

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:

Ndptr2

Getting concepts by name

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

concept<CommandList> myConcept = concept/CommandList/;  

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

Concept switch

Features access

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

  • properties

  • children

  • references

To access them, the following syntax is used:

<node expression>.featureName.

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:

thisNode.children.grandChildren

will be automatically transformed by MPS to something like:

thisNode.children.selectMany({~it => it.grandChildren; })

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:

<node expression>.isInstanceOf(Concept)

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:

<node expression> : Concept

Another way to cast node to particular concept instance is by using as cast expression:

<node expression> as Concept

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:

node.parent as BinaryOperation.leftExpression as BinaryOperation.leftExpression.isLValue()

Node collection cast

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

node.statements.ofConcept<LocalVariableDeclaration>

seq.ofConcept<MyConcept> is equivalent to seq.ofType<node<MyConcept>>

Aspect collection cast

seq.ofAspect<structure> filters nodes of a particular aspect

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()/rootsIncludingImported() - returns all roots in a model, which are instances of the specified Concept

  • nodes()/nodesIncludingImported() - returns all nodes in a model, which are instances of the specified Concept

E.g. model.roots(<all>), model.nodes(IfStatement) or model.nodes(# someNode.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:

<node expression>.search scope(link, operationContext)

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:

concept/ConceptName/

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

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:

<node expression>.hasRole(Concept : child)

For example,

myNode.hasRole(IfStatement : elsifClauses) 

Link queries

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

 

node<LinkDeclaration> decl = linkNode/ClassCreator : constructorDeclaration/; decl.sourceCardinality; decl.metaClass; decl.role; decl.target; decl.unordered;  ... string name = linkName/ClassCreator : constructorDeclaration/; SReferenceLink link = link/ClassCreator : constructorDeclaration/; link.isOptional(); link.getDeclarationNode(); link.getOwner(); link.getName(); link.getTargetConcept(); link.getScope(decl/); ...

Containing link queries

If one node was added to another one (parent) using the following expression:

parent.childLinkRole.add(node)

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:

parent.childLinkRole.indexOf(node)

downcast

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:

<node expression>/

For example,

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

Last modified: 14 September 2021