Potje rodekool

DSL support

Introduction

A Domain specific language (DSL) allows you to write code more easily for a specific domain, for example xml, json, jpa, etc.
The DSL is a compiler feature not a language feature so it can work independently of the language we it is used in and can define it own rules.

Implement

To implement a DSL on or more interfaces can be implemented.
InterfaceDescription
io.github.potjerodekool.nabu.compiler.resolve.spi.ElementResolverResolves elements in a DSL
io.github.potjerodekool.nabu.compiler.transform.CodeTransformerTransforms a DSL into Java code

Register implementations

Implementations of the interfaces can be registered in the plugin.xml file.
    <?xml version="1.0" encoding="UTF-8" ?>
    <plugin>
        <id>io.github.potjerodekool.nabu.plugin.simple</id>
    <version>0.0.1</version>
    <description>Simple plugin for Nabu compiler</description>
    <extensions>
        <code-transformer implementationClass="io.github.potjerodekool.nabu.plugin.simple.transform.SimpleTransformer"/>
        <element-resolver implementationClass="io.github.potjerodekool.nabu.plugin.simple.transform.SimpleElementResolver"/>
    </extensions>
    </plugin>

DSL example

Take for example JPA. Writing a Predicate quickly results in hard to read code like this:
    fun findCompanyByEmployeeFirstName(employeeFirstName: String): Specification<Company> {
        return (c : Root<Company>, q: CriteriaQuery<?>, cb: CriteriaBuilder) -> {
            var e = (InnerJoin<Company, Employee>) c.employees;
            var e = c.join(Company_.employees, JoinType.INNER);
            return cb.equal(e.get(Employee_.FIRST_NAME), employeeFirstName);
        };
     }
With a DSL you could write more readable code like this:
    fun findCompanyByEmployeeFirstName(employeeFirstName: String): Specification<Company> {
        return (c : Root<Company>, q: CriteriaQuery<?>, cb: CriteriaBuilder) -> {
            var e = (InnerJoin<Company, Employee>) c.employees;
            return e.firstName == employeeFirstName;
        };
     }
Were a join is defined with a cast using a DSL class (InnerJoin in this example) and access the fields of the entity directly. The code is transformed to the code below. As you can see the code doesn't use the static metamodel classes since the plugin has access to the code and can check if fields exists or not.
    fun findCompanyByEmployeeFirstName(employeeFirstName: String): Specification<Company> {
        return (c : Root<Company>, q: CriteriaQuery<?>, cb: CriteriaBuilder) -> {
            var e = c.join("employees", JoinType.INNER);
            return cb.equal(e.get("firstName"), employeeFirstName);
        };
     }