Constraints
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.
Default concrete concept
For abstract concepts the Constraints aspect can be used to indicate, which concept should be used instead when a node of the abstract concept needs to be created.
For example, if a child collection is supposed to hold AbstractCommands and the user hits enter to insert a new child into the collection, a node of the EmptyLine concept is created and inserted.
Similarly. the smodel commands that create new nodes and accept the <default> parameter will correctly create nodes of the concrete concept instead of the abstract one.
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-returning closures, which MPS invokes each time when evaluating allowed possition for a node in the AST.
Languages to import
You will most likely need at least two languages imported in the constraints aspect in order to be able to define constraints - the j.m.baselanguage and j.m.lang.smodel languages.
can be child
Return false if an instance of the concept is not allowed to be a child of specific nodes.
parameter | description |
---|---|
node | the child node we are checking (instance of this concept) |
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 |
---|---|
childNode | the child node we are checking |
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 |
---|---|
childNode | the child node we are checking |
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 |
---|---|
model | model of the root |
instance icon
Return an icon to represent nodes of the concept in the given context.
parameter | description |
---|---|
node | The node to draw an icon for. |
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 property 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 |
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 |
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 |
Example - customizing the description in the completion menu
The completion menu lists available nodes together with some additional descriptive information:
In order to customize the additional information and provide more details on the individual options listed in the completion menu, you can override the getter of the shortDescription property of the target concept:
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 - defines the set of nodes to which this link can point. The method returns a Scope instance. Please refer to the Scopes documentation for more information on scoping. There are two types of scope referent constraint:
inherited
reference
While inherited scope simply declares the target concept, the reference scope provides a function that calculates the scope on the fly from the parameters.
parameter | description |
---|---|
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. |
contextNode | node with the reference or the closest not-null context node |
containmentLink | SContainmentLink describing parent-child relationship between the contextNode and its non-existent child that is being created (if referenceNode exists then this parameter has no meaning) |
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. |
position | the target index in contextRole |
If scope is not set for the reference then default scope from the referenced concept is used. If the default scope is also not set then "global" scope is used: all instances of referent concept from all imported models.
presentation (deprecated - the editor aspect now specifies presentation of references, see Editor) - 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 | model of node containing reference |
parameterNode | the node to be presented (referenceNode has a reference to parameterNode of type linkTarget) |
position | target index in contextRole |
exists | false when reference is being created |
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 |
contextNode | node with the reference, or closest not-null context node |
contextRole | target role in contextNode |
referenceNode enclosingNode linkTarget containingLink | deprecated |
Default scope
Suppose we have a link pointing to an instance of concept C and we have no 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 scope and presentation methods. All the parameters are the same.
Please refer to the Scopes documentation for more information on scoping.
Additional methods
The additional methods section at the bottom allows the user to define utility methods that can be called from within the constraints definition to avoid repetition. The additional methods typically perform calculations or perform tasks that are needed in multiple places of the constraints definition. The additional methods must be private to only be visible from within the containing node.