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.

To check that a node is a specific one, there is the "is" operation available.
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:
Node collection cast #
A collection of nodes can be filtered and cast by the concept of the nodes using the ofConcept construct:
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(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<>.
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 link, linkName 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:
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:
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:
For example,
myNode/.getConcept().findProperty("name")