9.3 KiB
| layout | title | weight |
|---|---|---|
| page | Code Rules Language | 30 |
Code Rules Language
This section should teach how to use the language
Root concepts should be contained by a language aspect model importing the language jetbrains.mps.lang.coderules. For example, language jetbrains.mps.lang.typechecking defines aspect types to contain code rules related to type checking.
Root concepts
| Concept | Description |
|---|---|
| Handler | contains rule templates and constraint declarations |
| MacroTable | defines how terms are constructed |
| Query | entry point to the constraints program |
| DataFormTable 1 | defines terms, the data structure that is used in unification |
Rule templates
Handler
Handler serves two purposes: it declares the constraints that can be used in the head of productions in this handler and its extensions, and it also declares rule templates, which are, simply put, procedures applicable to specific concept and (optionally) its subconcepts.
The aim of rule templates is again twofold: firstly, they may serve as regular «checking» rules, and also, most importantly, they contribute constraint productions. These are created with a DSL that allows mixing of productions and Java code, and can also use template fragments (thus «rule templates»).
The following example is from the experimental control flow aspect for baseLanguage. It demonstrates how a production is constructed using a template. A template is enclosed into a pair of %% … %% symbols and yields constraints wrapped into special <% … %> brackets. In this particular case write() constraint is optional and is only added to the body of the production in case the condition is satisfied (a location is written to, only if the local variable has an initialiser).

(example of code rule template)
A handler is a concept in language jetbrains.mps.lang.coderules.

(example of a handler declaration — without contents)
Keyword extends allows to specify another handler, which is extended by this one. All constraint productions generated by rules in this handler will have higher priority. This enables to override the original handler’s behaviour.
Only declared constraints are allowed to be used in the heads of productions in rule templates. Handler should declare one or more constraints, unless it only uses the constraints from handlers being extended. Constraint declaration consists of name and arity, which together constitute a constraint symbol. Constraint’s arity is fixed.
Rule templates can specify applicable concept, either exactly or including its subconcepts. One can also declare standalone templates, which are triggered automatically on every invocation of constraints program.

(rule template matching concept instances with condition block)
A condition, is specified, is checked before applying the rule.

(standalone rule template without input)
The contents of a rule template is a block of code that gets executed when the template is applied to the source model. If the input is specified, the declared parameter is available in the body. When no input is specified, the rule becomes «standalone» and is automatically executed once per every application of templates.
What other features are available aside from constraint productions?
Constraint productions can be created at any place within the body of a rule template. A production has three main parts called head, guard, and body. The head defines what constraint trigger this production, and it can only contain constraints defined by the handler or one of the handlers it extends. The body can contain any visible constraints, as well as predicates. A production must include either the body or the head, no production can omit both. The guard is optional, and it can only contain predicates.
Both the head and the body can declare logical variables. By default a logical variable ranges over terms, although any POJO may be serve as a value.

(constraint production declaring logical variables)
A production with an empty head, that is not declaring any constraints to serve as the input, is considered an automatic production and is triggered automatically on start of constraints program execution.
In production’s head constraints can be declared as either kept or replaced. Briefly put, the constraints that are kept are left alone after a production is fired, whereas the replaced ones are discarded after the head has been matched. Replaced constraints are marked with tilde ~.
A production is triggered when there are constraint occurrences matching all constraints in production’s head. When a constraint with logical variable as one of its arguments is matched, that variable becomes bound to the corresponding argument of the matching occurrence. The scope of this binding is the guard and the body of the production.
Pattern matching is also possible with terms, optionally containing logical variables, serving as patterns. For this to work, a logical variable that serves as a constraint argument should have a pattern attached.

(production with pattern matching)
Everywhere where locations in the source code (model) must be referenced, node references are used, which are introduced with the @ symbol.

(example of a constraint argument referring to a location)
Whereas the production’s head can only contain constraints, there are also other logical clauses that are used in the guard and the body.
A predicate is a built-in construct that serves two purposes: when used in the guard, it serves to query if a condition is satisfied, and when used in the body it asserts this condition. An example is unifies predicate displayed as =, which tests if its arguments can be unified. There are also predicates that can only be queries, such as isFree() or isBound() testing if a logical variable is free or has value assigned.
Production’s guard can only contain predicates, and the body can contain both constraints and predicates.

(unifies predicate used in the guard and in the body)
Arbitrary Java code can be called with eval() predicate. It accepts either an expression of boolean type, in which case it can be used in the guard, or expression of any type, such as static method call, which can be inserted in the body.

(example of using eval() predicate)
Expand/call pseudo predicates
Code templates within production body
Alternative body
Query
Query is the entry point to a constraint program. The only purpose of a query is to activate constraints that trigger computation defined by productions created by handlers.
A query is discovered by the API through its kind. Query kind specifies parameters that can be passed to it. The body of a query can contain same constraints and predicates as regular constraints production.
Macro table
Macros allow to have parameterised constructors for terms. They can either create terms using nodes coming from the source model, or use the parameters to fill in values. By contrast with rule templates, macros are always explicitly invoked.
This feature may come in handy when data types used by the constraint program are complicated and include many levels of containment. For example, type checking with code rules relies on types being terms, which means types can contain other types.
Macros are defined in a root Macro table. Its contents is made of macro definitions.

(macro definition in a macro table)
Macro expands instances of specified concept. This means the macro can be invoked on an instance of this concept. In case macro is expanded, its parameters are automatically initialised to appropriate values. Otherwise, a macro can be directly called with parameters specified explicitly.
Expand/call from production body
Macro body is a code template, with every constraint or predicate produced from this template being inserted into the production body at the invocation site. There is a special logical variable available within macro body, macroLogical, which captures the logical variable on the left side of expand or call predicate.
Substitutions
DataForm table
DataForm table contains data form declarations, a.k.a. terms. A term is a simple recursive data type that is suitable for implementing unification. Also, terms can model any other data structure, in particular they are suitable for representing types in a programming language.
Data form is defined by its symbol and its features, which can be a POJO, a child data form, or a list of data forms. A feature may have a default value provider. A data form extending another data form adds new features or provides default values for existing ones.

(example of a dataform definition for classifier type)

(examples of dataform definition for long type)
Terms
-
this concept is defined in another language,
jetbrains.mps.logic↩︎
