Interpreter Pattern

The Interpreter Pattern provides a way to evaluate the syntax or expressions of a language. It belongs to the behavioral pattern and implements an expression interface that interprets a specific context. The Interpreter Pattern is often used in SQL parsing, symbol processing engines, and more.

Description

In software development, there are often repetitive problems with a certain similarity and regularity. If these problems can be generalized into a simple language, then the instances of these problems will be sentences of that language. This can be implemented using the interpreter pattern from compilation theory. The interpreter pattern defines a language for analyzing objects and represents the grammar of the language. It then designs a parser to interpret the sentences in the language, that is, to analyze instances in the application in the same way as in compiled languages. The concepts of grammar and sentences mentioned here are the same as those described in compilation theory. The grammar refers to the language's syntax rules, while the sentences are elements of the language.

Pattern roles

  • Abstract Expression Expression role: Declares an interface that all concrete expression roles need to implement. This interface mainly consists of an interpret() method, called the interpretation operation.
  • Terminal Expression Terminal Expression role: Implements the interface required by the abstract expression role, mainly an interpret() method. Each terminal in the grammar has a specific terminal expression corresponding to it. For example, in a simple formula like R=R1+R2, R1 and R2 are terminals, and their interpreters are terminal expressions.
  • Non-terminal Expression Nonterminal Expression role: Each rule in the grammar requires a specific non-terminal expression. Non-terminal expressions are generally operators or other keywords in the grammar. For example, in the formula R=R1+R2, + is a non-terminal, and the interpreter for + is a non-terminal expression.
  • Context Context role: The task of this role is generally to store the specific values corresponding to each terminal in the grammar. For example, in R=R1+R2, we assign 100 to R1 and 200 to R2. This information needs to be stored in the context role. In many cases, using a Map as the context role is sufficient.

Advantages

  • Good extensibility: In the interpreter pattern, language grammar rules are represented using classes, so the grammar can be changed or extended through mechanisms such as inheritance.
  • Easy implementation: Each expression node class in the syntax tree is similar, making it relatively easy to implement the grammar.

Disadvantages

  • Low execution efficiency: The interpreter pattern typically uses a large number of loops and recursive calls. When interpreting complex sentences, the execution speed is slow, and the debugging process is also cumbersome.
  • Class proliferation: Each rule in the interpreter pattern requires at least one class definition. When there are many grammar rules, the number of classes will increase sharply, making the system difficult to manage and maintain.
  • Limited application scenarios: There are very few instances in software development where language grammar needs to be defined, so this pattern is rarely used.

Implementation

function Context() {
    var sum = 0;
    var list = [];
    this.getSum = function() {
        return sum;
    }
    this.setSum = function(_sum) {
        sum = _sum;
    }
    this.add = function(eps) {
        list.push(eps);
    }
    this.getList = function() {
        return list;
    }
}

function PlusExpression() {
    this.interpret = function(context) {
        var sum = context.getSum();
        sum++;
        context.setSum(sum);
    }
}

function MinusExpression() {
    this.interpret = function(context) {
        var sum = context.getSum();
        sum--;
        context.setSum(sum);
    }
}

(function() {
    var context = new Context();
    context.setSum(20);
    // Run addition three times
    context.add(new PlusExpression());
    context.add(new PlusExpression());
    context.add(new PlusExpression());
    // Run subtraction twice
    context.add(new MinusExpression());
    context.add(new MinusExpression());
    var list = context.getList();
    for (var i = 0; i < list.length; i++) {
        let expression = list[i];
        expression.interpret(context);
    }
    console.log(context.getSum()); // 21
})();

Daily Question

https://github.com/WindrunnerMax/EveryDay

References

http://c.biancheng.net/view/1402.html https://blog.csdn.net/itpinpai/article/details/51657199 https://www.runoob.com/design-pattern/interpreter-pattern.html