Interpreter (Interpreter mode)

The Interpreter pattern is a behavioral pattern.

Intent: Given a language, define a representation of its grammar and define an interpreter. The interpreter uses this representation to interpret sentences in the language.

Any language, be it everyday language or programming language, has a clear grammar, which can be described by grammar, and the language structure of the string through the grammar interpreter.

For example,

If you don’t understand the above description, it doesn’t matter. Design patterns need to be used in daily work. Examples will help you understand better.

SQL interpreter

SQL is a description language, so it also works with the interpreter pattern. Different SQL dialects have different syntax. We can customize a set of suitable grammar expressions according to a particular SQL dialect, and then use ANTLR to parse into a grammar book. In this case, ANTLR is the interpreter.

Code compiler

Programming languages are also strings by nature and, like SQL and everyday languages, require a schema to parse to work. Different languages have different grammatical representations, so we just need a general-purpose interpreter like ANTLR that returns different object structures by passing in different grammatical representations.

Natural language processing

Natural language processing also is a kind of interpreter, firstly, natural language processing can only handle everyday language subset, so to define the scope of the good support to define a word segmentation system expression and grammar, and word segmentation result into the pumping expression interpreter, this method so that the interpreter can return structured data, based on structured data analysis and processing.

Intention to explain

Intent: Given a language, define a representation of its grammar and define an interpreter. The interpreter uses this representation to interpret sentences in the language.

For a given language, which can be SQL, code, or natural language, “one representation of the grammar that defines it” means that a grammar can have multiple representations, and only one is defined. It is important to note that the execution efficiency of different grammars varies.

“And define an interpreter,” which is something like ANTLR, and pass it a grammar expression to parse the sentence. That is: interpreter (language, grammar) = abstract syntax tree.

We can couple grammar definitions directly to the interpreter, but doing so makes the interpreter difficult to maintain when the syntax is complex. A better approach is to define a set of grammatical expressions decoupled from the interpreter, and ultimately generate the interpreter through the preprocessor.

chart

Context is another Context variable, AbstractExpression is an abstract syntactic expression.

It can be seen that both TerminalExpression and NonterminalExpression are derived from AbstractExpression. TerminalExpression refers to a symbol that has no subsequent expansion, while non-terminalexpression is the opposite. So non-terminals point to AbstractExpression again, recursively.

The code example

The following example is written in typescript.

Suppose we want to implement the following grammar:

sum    ::= number + number
number ::= 1 | 2
Copy the code

Expresses the simplest addition grammar in which the addition expressions sum and number are non-terminals, and +, 1, and 2 are terminals. This example can only add 1 and 2. With this simple example, we can understand the essence of the interpreter pattern:

// Abstract expressions
class AbstractExpression {
  interpret (text: string) {}}// Terminator expression
class TerminalExpression extends AbstractExpression {
  constructor(values: string[]) {
    this.values = values
  }

  interpret(value: string) {
    // The value must be one of them
    return this.values.includes(value)
  }
}

// Non-terminal expressions
class NonterminalExpression extends AbstractExpression {
  constructor(left: TerminalExpression, right: TerminalExpression) {
    this.left = left
    this.right = right
  }

  interpret(value: string) {
    if (value.indexOf("+") = = = -1) {
      // Must contain a + sign
      return false
    }

    const splitValue = value.split('+')

    return this.left.interpret(splitValue[0&&])this.right.interpret(splitValue[1])}}/ / call
const context = new Context()
const terminal = new TerminalExpression(["1"."2"])
const add = new AddExpression(terminal, terminal)

add.interpreter("1 + 1") // true
add.interpreter("1 + 2") // true
add.interpreter("1 + 3") // false
add.interpreter("2-1") // false
Copy the code

If a non-terminal is encountered, the call continues. Only the terminal can be determined directly. The principle is very simple.

disadvantages

The above example is a relatively inefficient scenario, because when the syntax is complex, the number of classes increases significantly and it is difficult to maintain. In this case, you need to use a generic syntax parser. Read more about this in my previous articles: Intensive reading of the Handwritten SQL Compiler – Parsing series.

conclusion

An interpreter is a way of thinking that abstracts complex grammatical parsing into separate terminators and non-terminators. Once each grammar has made its own judgments, all that remains is to assemble the grammar.

This decoupling of a single logical judgment from the assembly of grammar can make the logical judgment and the assembly of grammar transform independently, so that complex grammar parsing can be transformed into a concrete simple problem.

The discussion address is: Close reading Design Patterns – Interpreter Patterns · Issue #296 · dT-fe /weekly

If you’d like to participate in the discussion, pleaseClick here to, with a new theme every week, released on weekends or Mondays. Front end Intensive Reading – Helps you filter the right content.

Pay attention to the front end of intensive reading wechat public account

Copyright Notice: Freely reproduced – Non-commercial – Non-derivative – Remain signed (Creative Commons 3.0 License)