The objectives of this step are the following:
- Use a DSL workbench to create a language (MPS 2017.2)
- Uses a template-based code generation approach (TextGen)
The LED example, using MPS
Creating a new project
We start by creating a new project for a language named ArduinoML, associated with a sandbox solution (to write programs in ArduinoML) and a runtime solution (for test purposes).
Creating the concepts
Each concept of the ArduinoML abstract syntax tree is modelled as a concept in MPS.
- The
App
,Actuator
andState
concepts implement theINamedConcept
interface as one can use theirname
to reference it; - The
App
concept is considered as aroot
, as it is the entry point of the language; - The
SIGNAL
concept is an Enumeration, containing theHIGH
andLOW
value. One can change the way the value is displayed using the presentation field; - Modelling concepts in MPS:
- Properties are used to model simple attributes (here the
SIGNAL
to send to a given actuator) - Children are used to modelling elements that are contained by the concept. An element can only be contained by a single container;
- References are used to link an element to another one.
- Properties are used to model simple attributes (here the
Right-click on the project name and select Make
to synchronize the language with the other solutions (and do this operation each time the tool underlines your models stating that “generation is required”).
Creating Models
Using these concepts, one can create a program in ArduinoML. Right-click on the sandbox project and create a new App
(proposed as it is defined as a root concept). In MPS, the syntax is made by projecting the AST, and a default project does exist in the tool.
It is important to notice that you are not editing text, but directly the AST, using a fill in the blanks (the red parts) approach. The LED example is modelled by filling these holes (we added an isInitial
property in the State
concept between the two screenshots).
Modifying the projections
The default project has the advantage of existing for free but is not really user-friendly. To create our own projects, we define Editors associated with the concepts.
MPS provides a DSL to model editors. The DSL relies on the definition of collections (horizontal or vertical) to assemble the attributes associated with a given concept in the proper way. This is a tabular approach (which is arguable in terms of design choices and defined syntax) where you need to think of your projection as imbricated boxes.
One must notice that automatic completion is mandatory (using CRTL-space) to find the right symbol while defining the projection.
[/
…/]
: vertical collection, all elements between these elements will be displayed in a vertical way;[>
…<]
: horizontal collection;[-
…-]
: horizontal collection supporting indentation, using--->
to specify the indentation level;{
x}
: refers to the propertyx
defined in the current concept;(/ %
x%
…/)
: a vertical collection of children nodes defined inx
.empty
: an empty line
As we are defining a set of projections, building the language automatically update the LED
model we defined previously.
Specifying Constraints
Constraints are specified as logical expressions evaluated on concept instances. For example, to specify an invariant stating that a pin associated with an actuator must be in [1,13], we associated a logical check on the Actuator
concept.
A more interesting constraint is the unique property associated with the state names. To implement it, we add a constraint that looks inside the state parent node and checks for other states with the very same name
Constraints are hard properties. One can define more soft guidelines. For example, a single initial state should be defined in the FSM. This is done thanks to a checking rule in the type system definition.
Controlling Scope
Concept instances are linked together at the global level, as it is the default scope in MPS. As a consequence, if one creates a second App named led2
in the sandbox solution, it is possible to refer to actuators or states defined in led2
inside the led
application.
MPS provides scoping mechanisms to support this task. To state that the State
concept must not use the default global scope but a homemade one, we add a constraint to the State
stating that when looking to fill the next
reference with a State
, it must inherit it from something defined in its containment hierarchy.
We use the App
concept as our scope provider (it must then implements the ScopeProvider
interface in addition to the INamedConcept
one). We create a behaviour associated with the App
concept and override (use the CTRL-O shortcut to open a list of overridable methods) the getScope
method. The implementation is straightforward: if asking for a State
, we return all the states defined in the App. We do something similar for the actuators and return null
when asked for something else.
Generating Code
MPS supports a template-based generation mechanism named TextGen. It also supports language composition mechanisms, which are more expressive but also more complex. In this lab, we will rely on the simple TextGen.
For each concept, we define a TextGenComponent
describing how the concept must be projected into plain text. For example, the actuator concept is projected by declaring an integer variable named like the actuator and containing the PIN number. We use the append
keyword to add text to the generation buffer.
A root concept will also define the generated filename and extension. When appending a model element, TextGen will recursively call the associated template. The system can automatically iterate on a collection of objects, using the $list
keyword.
Expected Work
- Adapt the language to support sensors and transitions associated with sensors;
- Identify the abstractions needed in the language to support the 7-segment display;
- Adapt the language to support it.
Feedback Questions
- What is the cost/benefits ratio of using a workbench?
- What are the limitations of such an approach?
- What about vendor lock-in?