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()/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:
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.
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")