MPS 2021.2 Help

Implementing generators for BaseLanguage's extensions

 

This article showcases an idiomatic way for writing generators for BaseLanguage extensions that operate with lvalue-expressions. We say that a BaseLanguage expression is lvalue if it can be used in the left-hand side of an assignment expression. Concepts that introduce such expressions should override either the instance method Expression#isLValue() or the static method Expression#lvalue() in the behaviour aspect. 

The study consists of two independent languages, both of which are small extensions to BaseLanguage. The source code can be found in the BlReference sample project in the MPS distribution.

jetbrains.mps.baseLanguage.box

In this language, we show the way to generate custom BaseLanguage extensions that introduce new lvalue-expressions. The presented technique can be used for generating expressions like smodel property access or custom collection's element access by index.

The box BaseLanguage's extension introduces a new "box" type that wraps a variable and provides read & write operations over it. The whole language consists of three concepts:

  • BoxType that represents "box" type in a model.

  • BoxCreator that can be used in the new expression to create "box" instances

  • Box_ValueOperation that can be used as an operation of dot expression when its operand is of "box" type.

Image2018 11 12 10 25 36

We treat a dot-expression with "value" operation as lvalue-expression, so we overrideIOperation#lvalue() method in Box_ValueOperation's behaviour aspect. Once we've overridden this method, the dot-expression with "value" operation can be used at the left-hand side of an assignment. Additionally, this operation can be used in compound assignments (such as +=, -=), as well as the increment and decrement operations. Our final goal is to write a generator for Box_ValueOperationconcept.

The naive approach would be to manually handle all possible cases in which role a dot-expression with "value" operation is aggregated and generate a proper Java code for each case. However, this approach is insufficient because there're too many cases to handle. Also with this approach, we cannot handle cases when our expression is aggregated with some expression from an independent extension of BaseLanguage.

An alternative approach is to transform our expression into another, already existing, lvalue-expression. There are several lvalue-expressions available in Java language into which we can transform our expression - a variable references, a field access operation or an array's element access by index operation. Unfortunately, not all custom lvalue-expressions can be transformed easily into Java lvalue-expressions.

For instance, it will be difficult to transform the "value" operation into a Java lvalue-expression if at runtime the "box" type is represented with Box interface that provides two methods - getValue and setValue. To overcome this issue we can use BaseLanguage's generation-time concept of "generic lvalue expression", into which we can transform our expression:

Genxx1

Image2018 11 12 11 36 58

The "generic lvalue expression" consists of two obligatory roles: type and reference. The former role specifies the type of our original lvalue-expression while the latter role specifies the expression of type jetbrains.mps.references.Reference<T>. This type provides two methods: #get() in order to read value from evaluated variable and #set() - to assign new value to evaluated variable.

The "generic lvalue expression" additionally contains two optional roles:get value and assign value. These expressions are used to simplify the generated code and to produce fewer memory allocations of Reference<T> objects during execution. The first is used in cases when the generated expression is used not in lvalue position while the second expression is used solely when the generated expression is in a left-hand side role of assignment. Note that there is a special "value" keyword available in the assign value expression and this keyword should be used exactly once in the expression.

jetbrains.mps.baseLanguage.date

In this sample language, we show how to generate custom BaseLanguage extensions that are introducing new expressions or customising existing ones that aggregate lvalue-expression in some role.

The date BaseLanguage's extension introduces new "date" type and several operations on the "date" instances.

Image2018 11 12 10 45 48

At runtime, we will represent our "date" type with java.time.LocalDate:

The "date" extension overloads plus operator for "date" and "int" instances so that this operation adds days (the amount of which is evaluated from right sub-expression) to a date that is evaluated from left sub-expression. At generation we transform this plus operator into a simple static method call:

Image2018 11 12 10 59 49

Image2018 11 12 11 31 48

To make our extension consistent, we also want overload plus assignment expression likewise we overloaded the plus operator. A significant difference between the plus operator and the plus assignment expression is that in plus assignment a left sub-expression should be an lvalue so this expression has to be evaluated to a variable to which we can assign computed values. However, in Java, we cannot pass variables through method invocations so we cannot transform our expression into just a method call.

To approach this problem MPS provides "@byRef" generation time-concept. This expression wraps an lvalue-expression and during generation transforms it into an expression of jetbrains.mps.references.Reference<T> type. Given an instance of this type, you can produce get & set operations to a variable that is evaluated from the wrapped lvalue-expression. 

With "@byRef" expression the plus assignment expression can be easily generated in the following way:

Image2018 11 12 11 33 15

Image2018 11 12 11 34 3

A composition of independent extensions 

Two extensions shown above do not depend on each other. Their both generators are designed with the use of generation-time concept provided with BaseLanguage. On their own, these generation-time facilities make generators to be more consistent and contain less boiler-plate code. In addition to that, extensions, the generators of which are designed in a shown way, can safely aggregate one another without having dependencies on each other:

Image2018 11 12 11 51 17

 

 

Last modified: 23 March 2021