This is the ninth day of my participation in the August More text Challenge. For details, see: August More Text Challenge

0 x0, introduction

😭 The Interpreter Pattern is an example of how to build a simple “language” Interpreter.

It is more niche than command mode and is used only in Specific domains, such as compilers, rule engines, regular expressions, SQL, etc. These are also known as Domain Specific Languages (DSLS).

The work rarely asks us to write an interpreter, just to understand, but mainly to borrow ideas about how to represent complex logic with more concise rules.

Tips: Second-hand knowledge processing is hard to avoid mistakes, interested in time can be consulted by themselves, thank you.


0 x1, definition

The original definition

Defines a syntax (grammar) representation for a language and defines an interpreter to process the syntax.

A little confused, ok, let’s take a look at its four constituent roles:

  • AbstractExpression → defines the interpretative operations of an interpreter. Concrete class implementations are divided into terminal and non-terminal interpreters.
  • TerminalExpression → implement the interpreted operation associated with the elements in the grammar. Usually, there is only one TerminalExpression in an interpreter pattern, but there are multiple instances, corresponding to different terminals. Terminals are generally operation units in grammar, such as the simple formula R=R1+R2, where R1 and R2 are terminals, and the corresponding interpreter for R1 and R2 is the terminal expression;
  • NonterminalExpression → Each rule in the grammar corresponds to a NonterminalExpression, typically an operator or other key in the grammar. For example, the + in R=R1+R2 is a nonterminal, and the interpreter that interprets the + is a nonterminal expression. It increases as the complexity of the logic increases, and in principle each grammar rule corresponds to a non-terminal expression.
  • R=R1+R2, R1 is assigned 100, R2 is assigned 200, this information needs to be stored in the environment role, in many cases using Map as the environment role is sufficient ~

Draw the UML class diagram directly

0x2. Write a simple example

Define an interpreter that can explain addition and subtraction as an example

// Abstract expression
public abstract class AbstractExpression {
    public abstract int interpreter(Context context);
    @Override abstract public String toString(a);
}

// Context
public class Context {
    private Map<AbstractExpression, Integer> map = new HashMap<>();

    public void addExpression(AbstractExpression expression, int value) {
        map.put(expression, value);
    }

    public int lookup(AbstractExpression expression) { 
        returnmap.get(expression); }}// Terminal expressions (constants and variables)
public class ConstantExpression extends AbstractExpression {
    private int value;

    public ConstantExpression(int value) {
        this.value = value;
    }

    @Override
    public int interpreter(Context context) {
        return value;
    }

    @Override
    public String toString(a) {
        returnInteger.toString(value); }}public class VariableExpression extends AbstractExpression {
    private String name;

    public VariableExpression(String name) {
        this.name = name;
    }

    @Override
    public int interpreter(Context context) {
        return context.lookup(this);
    }

    @Override
    public String toString(a) {
        returnname; }}// Non-terminal expressions (addition and subtraction)
public class PlusExpression extends AbstractExpression {
    private final AbstractExpression leftExpression;
    private final AbstractExpression rightExpression;

    public PlusExpression(AbstractExpression leftExpression, AbstractExpression rightExpression) {
        this.leftExpression = leftExpression;
        this.rightExpression = rightExpression;
    }

    @Override
    public int interpreter(Context context) {
        return leftExpression.interpreter(context) + rightExpression.interpreter(context);
    }

    @Override
    public String toString(a) {
        return leftExpression.toString() + "+"+ rightExpression.toString(); }}public class MinusExpression extends AbstractExpression {
    private final AbstractExpression leftExpression;
    private final AbstractExpression rightExpression;

    public MinusExpression(AbstractExpression leftExpression, AbstractExpression rightExpression) {
        this.leftExpression = leftExpression;
        this.rightExpression = rightExpression;
    }

    @Override
    public int interpreter(Context context) {
        return leftExpression.interpreter(context) - rightExpression.interpreter(context);
    }

    @Override
    public String toString(a) {
        return leftExpression.toString() + "-"+ rightExpression.toString(); }}// Test case
public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        AbstractExpression a = new VariableExpression("a");
        AbstractExpression b = new VariableExpression("b");
        AbstractExpression c = new VariableExpression("c");

        context.addExpression(a, 6);
        context.addExpression(b, 8);
        context.addExpression(c, 16);

        AbstractExpression e1 = new PlusExpression(a, b);
        System.out.println(e1 + "=" + e1.interpreter(context));
        AbstractExpression e2 = new PlusExpression(e1, c);
        System.out.println(e2 + "=" + e2.interpreter(context));
        AbstractExpression e3 = new MinusExpression(e2, new ConstantExpression(7));
        System.out.println(e3 + "=" + e3.interpreter(context));
        AbstractExpression e4 = new MinusExpression(e3, b);
        System.out.println(e4 + "="+ e4.interpreter(context)); }}Copy the code

The output of the code is as follows:

Usage scenarios

  • When the language syntax is simple and the execution efficiency is not high, such as the regular judgment IP is legal;
  • The problem is repeated, and can be expressed by simple syntax, such as if-else unified explanation as conditional statement;
  • When a language needs to interpret execution, such as XML, <> parentheses identify different node meanings;

advantages

  • Easy to implement syntax, a syntax is interpreted and executed by an interpreter object;
  • Easy to extend the new syntax, just create the corresponding interpreter, when abstract syntax tree can be used;

disadvantages

  • Few scenarios can be used, the reusability is not high, in addition to the invention of new programming languages or some new hardware to explain, rarely used, specific data structure, scalability is low;
  • The maintenance cost is high. Each rule should define at least one interpretation class. The more grammar rules there are, the more difficult the class is to manage and maintain.
  • Execution efficiency is low, recursive invocation method, interpretation sentence syntax complex, will execute a large number of circular statements;

Add food: Create your own DSL example in Kotlin

By the way, Kotlin DSLS, for those interested, use the following dark magic:

Extension functions (properties) + Lambda expressions + Infix Expressions + Operator overloading (Invoke)

The following is a code example:

/ / hotel
data class Restaurant(var name: String, var score: Int.var price: Int.var address: String)

// Context class
data class Eating(
        var kind: String? = "".var area: String? = "".var restaurantList: List<Restaurant>? = mutableListOf()
) {
    override fun toString(a) = StringBuilder().apply {
        append(""${[email protected]}"The"${[email protected]}\n\n" \n\n")
        this@Eating.restaurantList? .forEach { append("${it.name} - ${it.score}Points -${it.price}Yuan/person${it.address}\n") }
        append("\n")
    }.toString()
}

class RestaurantBuilder {
    var name: String = ""
    var score: Int = 0
    var price: Int = 0
    var address: String = ""
    fun build(a): Restaurant = Restaurant(name, score, price, address)
}

// Intermediate class, which implements the effect of adding Restaurant in a direct DSL manner
class EatingBuilder {
    var kind: String? = ""
    var area: String? = ""
    var restaurantList = mutableListOf<Restaurant>()

    fun restaurant(block: RestaurantBuilder. () - >Unit) {
        Restaurantlist.add (RestaurantBuilder().apply(block).build());
        val restaurantBuilder = RestaurantBuilder()
        block.invoke(restaurantBuilder)
        restaurantList.add(restaurantBuilder.build())
    }

    fun build(a): Eating = Eating(kind, area, restaurantList)
}

// Create a DSL for Eating (top-level function)
fun eating(block: EatingBuilder. () - >Unit): Eating = EatingBuilder().apply(block).build()

// To demonstrate the use of infix expressions, you can actually assign values directly
infix fun Eating.area(area: String) {
    this.area = area
}

fun main(a) {
    val eating = eating {
        kind = "Western fast food"
        restaurant {
            name = "Wallace whole Chicken Burger."
            score = 5
            price = 19
            address = "Mission Hills Cow Lake Shop"
        }
        restaurant {
            name = Burger King
            score = 5
            price = 33
            address = "Donghai Store"
        }
        restaurant {
            name = "McDonald's"
            score = 4
            price = 34
            address = "The Velvet Road Store."
        }
    }
    eating.area("Pinghu District")
    println(eating.toString())
}
Copy the code

The output of the code is as follows:


That’s all for this lesson. Thank you