Nontas' Blog

Creating a very simple DSL using JetBrains MPS (Part 1 of 3)

domain specific language creation using jetbrains mps - part 1

Introduction

Domain Specific Languages (DSLs) are not an entirely new concept in the world of software design and development. In contrast to General Purpose Languages, DSLs can be more specialized and effective in solving problems in a particular domain. As a result, this can lead in more clear code, as well as in a more maintainable codebase. You can find a lot of DSLs today; from widely used languages (such as HTML), to languages that are specifically designed to address a problem in a specific scientific domain.

During my two-year traineeship at the Eindhoven University of Technology, we had a very interesting workshop from Angelo Hulshout (Delphino Consultancy) during which we had to create our own DSL based on a specific model. In this post, we are going to explore this assignment: we will have a look at the description, we will try to understand the domain and the problem we will be working on, and we will build a DSL based on the context of the assignment. For the last part (building the DSL), we will need JetBrains’ Meta Programming System (MPS), which you can get for free from this link: JetBrains MPS. The steps we will follow work with version 3.3.5 of MPS.

After building this specific DSL, we will be able to write high-level code, and through MPS we will be able to generate Java code, which can be run from any computer (more or less). In addition to that, we’re also going to generate XML files (we will see how and why later on).

In this blog post, we’re going to start with the description of the problem, and then we’ll give an implementation using Java. After we’ve done this and things are clear, we will proceed in a next blog post with the creation of the DSL. I’m choosing to follow this approach in order to not create a very long blog post where one can easily get lost.

The problem

Let’s consider the following example: TU/e wants to make pens with its branding. The brand of TU/e can be seen below. We can see that the logo of TU/e consists of three parts: the TU/e sign (part 1), the text “Techische Universiteit Eindhoven University of Technology” (part 2), and the motto “Where innovation starts” (part 3). TUE_logo

However, we might want to use parts of the logo on the pens. That being said, we can make pens that have only one of the three parts of the logo, two parts, or all three parts. In order to accomplish this task, we found a printing company which agreed to give us all the pens we will order for free, if we propose a design for a printing machine that can work without re-uploading the sketch we want to print, every time we want to print something different. For instance, let’s assume that we have 1000 pens and we want 300 with parts 1 and 2 of the logo, 300 pens with parts 2 and 3, 300 with parts 1 and 3, and 100 with part 1 only. With their current printer we need to re-upload the updated version of the schematic every time we need to change something. This is not very efficient and as a result, we are going to propose a different design for their printer. Moreover, this company consists of non-technical people, so the configuration of the proposed printer should be easy for their employees.

Our proposal

In this different design, we’ll consider a printer that has three printing units and each unit can print a specific sketch. Furthermore, we can configure the machine with a printing recipe that indicates which printing units are going to be active. That being said, every product (a pen in our case) that goes through the printer, will get sketches according to the currently active printing recipe. In order to program this printer we will create a simple DSL, through which the employees will be able to configure the printing procedure. A brief model of this design can be seen below.
model

We can control and program this printer using Java. According to our design, we need three classes to represent our system: Printer, PrintingUnit, and Product, as well as one more class (PrintingSystem), which will handle the printing. Let’s have a quick look over these classes.

Product

We will use a StringBuffer for each product, where we are going to append the sketches each PrintingUnit will put on this product.

package me.eparon.product;

public class Product {
	protected StringBuffer parts;

	public Product() {
		parts = new StringBuffer();
	}
	public void addItem(char part) {
		parts.append(part);
	}

	public String toString() {
		return parts.toString();
	}
}

PrintingUnit

Each PrintingUnit will have a sketch (represented by a character), which will be printed on a Product. The sketch of this unit will be set via the constructor of this class.

package me.eparon.machine;
import me.eparon.product.*;

public class PrinterUnit {

	protected char part;
	protected Product product;

	public void initialize(char p) {
		part = p;
		product = null;
	}

	public void setProduct(Product p) {
		product = p;
	}

	public void printProduct() {
		if( product != null ) {
			product.addItem(part);
		}
	}
}

Printer

The Printer class needs to be aware of the PrintingUnits and the Products that need to be processed. The printing of the objects is done in the following way: From the stack of products, we get a product and we pass it to the first PrintingUnit. Next, this product is forwarded to the next PrintingUnit, and a new Product comes into the first unit. A product is considered to be ready when it has passed from all the printing units.

package me.eparon.machine;

import java.util.List;
import java.util.ArrayList;

import me.eparon.product.*;

public class Printer {
	protected List<PrintingUnit> units;
	protected List<Product> products;

	public Printer() {
		units = new ArrayList<PrinterUnit>();
		products = new ArrayList<Product>();
	}
	public void addPrintingUnit(PrintingUnit pu) {
		units.add(pu);
	}

	public void initializeProduction(List<Product> pl) {
		products = pl;
	}

	public void performPrinting() {
        int prodIndex = 0;
        int move = 0;
		while (prodIndex < products.size() || units.stream().filter(unit -> unit.product == null).count()!=3) {

            if (prodIndex < products.size()) {
                units.get(0).setProduct(products.get(prodIndex++));
            }

            units.forEach(unit -> unit.produce());

            for (int j = units.size()-1; j>0 ;j--)
            {
                units.get(j).setProduct(units.get(j-1).product);
                units.get(j-1).setProduct(null);
            }
		}
		printStatus();
	}

	public void printStatus() {
		// For each product in products print the contents of the product
        products.forEach(prod -> System.out.println(prod));
	}
}

Main

In this class we’re going to initialize our system and start to process our products. Therefore, three PrintingUnits are initialized and added to a Printer. Moreover, we create a list of 25 Products that will pass through the PrintingUnits and each PrintingUnit is going to print something on this product.

package me.eparon;

import java.util.List;
import java.util.ArrayList;
import me.eparon.machine.*;
import me.eparon.product.*;


public class Main {

    public static void main(String[] args) {
        List<Product> products = new ArrayList<Product>();
        for (int i = 0; i < 24; i++) {
            products.add(new Product());

        }
        PrintingUnit unit1 = new PrintingUnit();
        PrintingUnit unit2 = new PrintingUnit();
        PrintingUnit unit3 = new PrintingUnit();
        unit1.initialize('a');
        unit2.initialize('b');
        unit3.initialize('c');
        Printer printer = new Printer();
        printer.addPrintingUnit(unit1);
        printer.addPrintingUnit(unit2);
        printer.addPrintingUnit(unit3);
        printer.initializeProduction(products);
        System.out.println("results");
        printer.performPrinting();
    }
}

Execution Results

The execution of Main, yields the following results. We can see that every product went through the PrintingUnits and has the three parts of the TU/e logo printed on it.

results
abc
abc
abc
abc
abc
abc
abc
abc
abc
abc
abc
abc
abc
abc
abc
abc
abc
abc
abc
abc
abc
abc
abc
abc

Process finished with exit code 0

We just saw how to deal with this problem in Java. In the next blog post (see here) we are going to see how to create the DSL that will use the Java classes we made and will generate for us the Main class.