How to Set Up and Use a Domain Specific Language

Summary

A Domain Specific Language (DSL) enables you to write rules in "your own write". This may be close to some natural language, but you could also use a formal notation. The motivation for using a DSL is to enable persons who are not programmers but experts in some application domain to read and write rules by themselves.

Here are the ingredients:

DSL file
A file containing the definitions of the "sentences" of a DSL, consisting of a regular expression defining the sentence and its translation into regular DRL text.
DSLR file
A file containing rules written in a DSL. It contains a statement of the form
    expander pathname
referring to the definition of its DSL language. This implies that any DSL rule file must be written exclusively in a single DSL.
Files of both types are passed to the Knowledge Builder.

The DSL File

File Entry Format

A DSL file may contain definitions according to the following patterns:

   [condition  ][object]pattern=text
   [consequence][object]pattern=text
   [keyword    ][object]pattern=text
The tags condition and consequence define the scope of validity for a sentence, i.e., whether it may be used on the LHS or RHS of a rule. (You may use the shorter forms when and then, respectively, instead.) With keyword you can even redefine keywords such as "rule" or "salience".

The object entry is optional; it is merely used as a sort criterion for the form-based DSL editor in Eclipse. It has no influence on the DSL itself or its expansion.

The pattern and text parts are explained in the next subsections.

DSL Patterns

Each pattern defining a sentence of a DSL may consist of

Fixed text is meant to be written literally in the rule text, except for spaces; any number of successive spaces in the fixed text matches any number of spaces in the rule text. Notice that an initial hyphen ('-') has a special meaning for dealing with the expanded text, as described in the section The DSL Translation Process.

Placeholder symbols are names enclosed in braces ('{'and '}'). They are used to collect variable text occurring in their place, to be inserted for occurrences of the same symbol in the text defining the expansion. Do not use braces for any other purpose than for enclosing symbol names.

The "magic" characters provide for some flexibility in the fixed text. They correspond to the characters used in the construction of Java's regular expressions, as used with java.util.reges.Pattern.

DSL Expansion Text

The text into which a pattern is to be expanded may contain any sequence of language elements combined with placeholder symbols. If you need to include braces in this text, escape them with a backslash ('\').

The DSL Translation Process

A rule file written in some DSL is parsed line by line, and the parser tries to match pattern parts in the order they are presented in the DSL definition to the initial part of a line. This could consume the entire line, or leave some unmatched text. In the latter case, matching starts over again. Notice, however, that a pattern ending with a placeholder symbol will always consume the entire line, possibly gathering more than one token as this placeholder's value.

After matching a pattern, placeholders are set to the matching part of the line. The corresponding text may now be expanded by inserting the placeholders' values. Normally, the resulting text is appended to the current output line. But if the pattern begins with a hyphen ('-'), the expanded text is inserted into the last preceding output text, in front of a closing parenthesis and after an additional comma (','). This feature enables you to accumulate constraints, one by one, into a preceding Pattern Conditional Element.

As of Drools version 5.1, this process is subject to some restrictions.

Using the Knowledge Builder to Compile DSL Rules

The Knowledge Builder is used as usual, with minor variations indicating the type of resource passed to it. A DSL file is added with a ResourceType value of DSL, and a rule file written in a DSL is added with a Resource Type of DSLR. (It should be obvious that the DSL must come first.)

    String dslPath = ...;
    String dslrPath = ...;

    kBase = KnowledgeBaseFactory.newKnowledgeBase();
    KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
    Resource dsl = ResourceFactory.newClassPathResource( dslPath, getClass() );
    kbuilder.add( dsl, ResourceType.DSL );
    Resource dslr = ResourceFactory.newClassPathResource( dslrPath, getClass() );
    kbuilder.add( dslr, ResourceType.DSLR );
    if( kbuilder.hasErrors() ){
        // error exit
    }
    kBase.addKnowledgePackages( kbuilder.getKnowledgePackages() );
    session = kBase.newStatefulKnowledgeSession();

Examples

Simple LHS Patterns

This is a typical DSL pattern for generating a pattern CE:

    [condition][]There is an element {eVar}={eVar} : Element()
You can see that the binding variable is represented by a placeholder, which will permit, in subsequent sentences, to refer to the fact that will be bound to this CE.

A more general form is possible by using a placeholder also in place of the fact type:

    [condition][]There is an? {aType} {aVar}={aVar} : {aType}()
Note that the question mark is one of the magic characters, permitting you to omit the preceding 'n', so that a grammatically correct sentence can be written. But the type name must be written as it is in Java, which usually means an initial in uppercase.

Constraints restricting the choice of candidates for a pattern's binding are usually defined by a field, an operator and a value. You can define a sentence for a specific field, say "name":

    [condition][]- with a name equal to "{aVal}"    =name == "{aVal}"
    [condition][]- with a name not equal to "{aVal}"=name != "{aVal}"

A more general form permits free choice of the field and the operator. In this form it is assumed that the field is an integer, which would let you use a wider choice of relational operators.

    [condition][]- with {aField} {aOp} {aVal}={aField} {aOp} {aVal}

RHS Patterns

Typically, statements on the RHS refer to facts and fields by binding variables. If you cannot establish a uniform and unambiguous mapping of facts and field, you will have to burden the DSL author with providing binding variable names, to be inserted for placeholders in the LHS and RHS sentences. In the next example, there is a generic sentences for binding a field value to a variable, and a RHS sentence for updating the "price" field of a fact bound to a variable.

    [condition  ][]There is an? {aType} {aVar}={aVar} : {aType}()
    [condition  ][]- assign {aField} to {aVar}={aVar} : {aField}
    [condition  ][]- with {aField} {aOp} {aVal}={aField} {aOp} {aVal}
    [consequence][]Set the price of {fVar} to {aVal}=modify( {fVar} )\{ setPrice( {aVal} ) \}
The following rule illustrates the combined usage of these sentences.
    rule "raise prices"
    when
        There is an Article aMin
        - with id == 100
        - assign price to minPrice
        There is an Article tooCheap
        - with price < minPrice
    then
        Set the price of tooCheap to minPrice
    end

Redefining Keywords

Here is a redefinition of "salience":

    [keyword][]priority=salience
You could also redefine keywords such as "rule", "when", "then" or "end", useful when you want to create a DSL in a language other than English.