mps-coderules/docs/content/language.md

164 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
layout: page
title: Code Rules Language
weight: 30
---
# Code Rules Language
### Language Aspect
Code rules are defined in handlers, which are root concepts of language `jetbrains.mps.lang.coderules`. Handlers and other related roots should be created in a languages aspect model corresponding to a specific kind of analysis. For example, language `jetbrains.mps.lang.typechecking` declares the aspect `types`.
![](img/language-aspect-400.png)
_(definition of aspect `types`)_
#### Root concepts
The following table contains root concepts that belong to code rules definition.
| Concept | Description |
|:-- |:-- |
| *Handler* | contains rules and constraint declarations |
| *Query* | entry point to the constraints program |
| *MacroTable* | defines how terms are constructed |
| *DataFormTable* [^dft] | defines terms, the data structure that is used in unification |
### 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 *rules*, which are, simply put, procedures applicable to specific concept and (optionally) its subconcepts.
#### Rules
The aim of rules defined by handlers contribute constraint productions. These are created with a DSL that allows mixing of productions and Java code, and can also include constraint fragments inside a production template.
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/2` constraint is optional and is only added to the body of production in case the condition is satisfied (a location corresponding to a local variable is written to only if it has an initialiser).
![](img/language-writelocal-750.png)
_(example of rule with production template)_
A handler is a concept in language `jetbrains.mps.lang.coderules`.
![](img/language-handlerhead-300.png)
_(example of a handler declaration — without contents)_
Keyword `extends` allows to specify another handler that is to be extended. All constraint productions generated by rules in this handler will have higher priority. This enables to override the original handlers behaviour.
Only declared constraints are allowed to be used in the heads of productions in rules of this handler or its extensions. Handler should declare one or more constraints, unless it only uses the constraints from handlers which it extends. Constraint declaration consists of name and *arity*, which together constitute a *constraint symbol*. Constraints arity is fixed.
An applicability condition, if specified, is checked before applying the rule.
![](img/language-vardecl-600.png)
_(example of rule matching concept instances with condition block)_
Rules can specify applicable concept, either exactly or including its subconcepts. One can also declare *standalone* rules, which are applied automatically on every evaluation of code rules.
![](img/language-hasbound-550.png)
_(example of standalone rule without input)_
Rules contents is a block of code that gets executed when the rule is applied to the source model. If the input is specified, the declared parameter is available in rules body.
Rules may affect the scope of model locations that are processed during an evaluation session. Suppose a production responsible for type checking a particular location in source model relies on presence of productions that are only available if some other location is processed as well. To guarantee that a certain model location is processed during the evaluation session, one uses `require` statement.
![](img/language-recoverct-700.png)
_(example of using `require` statement)_
#### Production templates
Constraint productions are discussed in details in the section on Constraint Processing System, and here we briefly enumerate the main concepts and their usage.
*Productions* can be created at any place within a rules body, which amounts to creating a production *template*. This means, a production within a loop generates a runtime constraint production on every iteration of this loop.
A production template has three parts called *head*, *guard*, and *body*. Their meanings correspond precisely to those defined for the constraint productions.
There is a certain limitation as to what *constraints* can be used in head: it can only contain constraints defined by this handler, or one of the handlers it extends. This limitation has its origin in the way productions are chosen when an active constraint is being processed. Namely, productions to match an active constraint are selected from the handler declaring this constraint, and handlers that are its extensions. The order of productions within the handler matters, the ones declared on top are matched first. Productions from extending handlers are prepended to the list of productions of the handler they extend.
Productions 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. Guard is optional, and it can only contain predicates.
A production with an empty head, not declaring any constraints to serve as its input, is considered an *automatic* production and is triggered automatically on start of constraints program execution.
Constraints in a productions head can be declared as either *kept* or *replaced*. Replaced constraints are marked with a tilde `~`.
Predicates, also known otherwise as “built-in constraints”, are represented either as binary operators, such as `=` for unification or `==` for equality, or they are boolean-valued functions. Unification or equality if used in guard, only tests that its arguments can be unified, otherwise if called from body, it invokes the actual unification or assigns the value to a logical variable.
![](img/language-unify-300.png)
_(`unifies` predicate used in the guard and in the body)_
There are also predicates that can only be queries, such as `isFree/1` or `isBound/1`, testing if a logical variable is free or has value assigned. Such predicates can only be used in guard.
Arbitrary Java code can be called with `eval/1` predicate. It accepts either an expression of `boolean` type, in which case it can be used in guard, or expression of any type when called from body. In the latter case the value expression evaluates to is ignored.
![](img/language-recover-500.png)
_(example of using `eval/1` predicate)_
Both head and body can declare *logical variables*. By default a logical variable ranges over *terms*, although any POJO[^pojo] may be serve as a value.
![](img/language-compatibleWith-300.png)
_(production declaring logical variables)_
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 such binding is this productions guard and body.
There is support for pattern matching, using terms as patterns, which may optionally contain logical variables. For this to work, a logical variable that serves as a constraint argument should have a pattern attached.
![](img/language-convertsto-500.png)
_(production with pattern matching)_
Everywhere locations in the source code (model) must be referenced, node references are used, which are introduced with the `@` symbol. This is a shortcut for accessing a reference to a node, which is a regular POJO.
![](img/language-typeof-550.png)
_(example of constraint argument referring to a model location)_
In order to make use of macro definitions, which are essentially parameterised production templates extracted to a separate root, one of the two pseudo predicates can be used: `expand` and `call`. The former accepts a node, whereas the second expects the arguments that are substituted as macro parameters.
***Examples of expand/call pseudo predicates***
***Substitution — passing parameters via context***
***Template fragments within production template body***
***Alternative body***
#### Query
Query is the entry point to a constraints program. The only purpose of a query is to activate constraints that trigger computation implemented by constraint productions.
![](img/language-query-350.png)
_(example of a query)_
A query is discovered by the API through its *kind*, which is specific to the language aspect. 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 rules, macros are always explicitly invoked.
This feature may come in handy when data types used by the constraints 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*.
![](img/language-macro-500.png)
_(macro definition in a macro table)_
Macro *expands* instances of specified concept. This means a 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 template, with every constraint or predicate produced from template fragments 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 *dataform* 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.
Dataform is defined by its symbol and its features, which can be a POJO, a child dataform, or a list of dataforms. A feature may have a default value provider. A dataform extending another dataform adds new features or provides default values for existing ones.
![](img/language-classifiertype-300.png)
_(example of a dataform definition for classifier type)_
![](img/language-longtype-350.png)
_(examples of dataform definition for long type)_
[^dft]: this concept is defined in another language, `jetbrains.mps.logic`
[^pojo]: Plain Old Java Object — any Java object conforming to equals/hashCode contract