Interpreter Pattern refers to a given language that defines a representation of its grammar and defines an interpreter that uses that representation to interpret sentences in the language.It is a mode of parsing according to the prescribed grammar.
For example, a compiler can interpret source code compilation as machine code so that the CPU can recognize and run it.The interpreter mode actually works like a compiler by interpreting some fixed grammar (i.e. grammar) and building an interpreter to interpret sentences.Simple to understand, the interpreter is a simple grammar analysis tool that recognizes sentence semantics, separates end and non-end symbols, extracts the required information, and enables us to process different information accordingly.Its core idea is to recognize grammar and construct interpretation.
1. Scenarios for interpreter mode application
Among them, we live in interpreter mode every day, and the music we usually hear can be recorded by simple notation; and the Mouse code (also known as the Morse code), invented in the war years, is actually an interpreter.
In our programs, if there is a specific type of problem that involves multiple different instances but has a fixed grammatical description, we can use the interpreter mode to interpret this type of problem, separate out the required information, and process it accordingly based on the information we get.In short, for some fixed grammars, build an interpreter to interpret sentences.Interpreter mode applies to the following scenarios:
- Some recurring problems can be expressed in a simple language.
- A scenario where simple grammar needs to be interpreted.
There are four main roles in the interpreter mode:
-
Expression: Responsible for defining an interpretation method, which is assigned to a specific subclass for specific interpretation;
-
TerminalExpression: Implements the grammatical interpretations associated with terminators.Each terminator in grammar has a specific terminator expression corresponding to it, such as the formula R=R1+R2, where R1 and R2 are interpreters that parse R1 and R2.Usually there is only one terminator expression in an interpreter pattern, but there are multiple instances corresponding to different terminators (R1, R2);
-
NonterminalExpression: Implements grammar-related interpretations of nonterminators.Each rule in grammar corresponds to a non-terminator expression.Non-terminator expressions are usually grammatical operators or other keywords, such as in formula R=R1+R2,'+'is a non-terminator, and the interpreter that resolves'+' is a non-terminator expression.Non-terminator expressions increase according to logical complexity Each grammar rule corresponds to a non-terminator expression in principle;
-
Context: Contains global information outside the interpreter.Its task is generally to store specific values for each terminator in grammar, such as R=R1+R2, assign R1 a value of 100, assign R2 a value of 200, and store this information in the environment.
Parsing data expressions using interpreter mode
An interpreter mode is used to implement a mathematical expression calculator, which includes four operations: addition, subtraction, multiplication and division.
First define the abstract expression role interface IArithmeticInterpreter:
public interface IArithmeticInterpreter { int interpret(); }
Create the Terminator Expression Role Interpreter Abstract class:
public abstract class Interpreter implements IArithmeticInterpreter { protected IArithmeticInterpreter left; protected IArithmeticInterpreter right; public Interpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) { this.left = left; this.right = right; } }
Create four interpreters for addition, subtraction, multiplication and division of non-final expression roles, and add operation expression AddInterpreter class:
public class AddInterpreter extends Interpreter { public AddInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) { super(left, right); } @Override public int interpret() { return this.left.interpret() + this.right.interpret(); } }
Create the subtraction expression SubInterpreter class:
public class SubInterpreter extends Interpreter { public SubInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) { super(left, right); } @Override public int interpret() { return this.left.interpret() - this.right.interpret(); } }
Create a multiplication expression MultiInterpreter class:
public class MultiInterpreter extends Interpreter { public MultiInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) { super(left, right); } @Override public int interpret() { return this.left.interpret() * this.right.interpret(); } }
Create a division expression DivInterpreter class:
public class DivInterpreter extends Interpreter { public DivInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) { super(left, right); } @Override public int interpret() { return this.left.interpret() / this.right.interpret(); } }
Create the mathematical expression NumInterpreter class:
public class NumInterpreter implements IArithmeticInterpreter { private int value; public NumInterpreter(int value) { this.value = value; } @Override public int interpret() { return this.value; } }
Create the tool OperatorUtil class:
public class OperatorUtil { public static boolean isOperator(String symbol) { return (symbol.equals("+") || symbol.equals("-") || symbol.equals("*")); } public static Interpreter getInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right, String symbol) { if(symbol.equals("+")) { return new AddInterpreter(left, right); }else if(symbol.equals("-")) { return new SubInterpreter(left, right); }else if(symbol.equals("*")) { return new MultiInterpreter(left, right); }else if(symbol.equals("/")) { return new DivInterpreter(left, right); } return null; } }
Create calculator Calculator class:
public class Calculator { private Stack<IArithmeticInterpreter> stack = new Stack<>(); public Calculator(String experssion) { parse(experssion); } private void parse(String expression) { String[] elements = expression.split(" "); IArithmeticInterpreter left, right; for(int i = 0; i < elements.length; i++) { String operator = elements[i]; if(OperatorUtil.isOperator(operator)) { left = this.stack.pop(); right = new NumInterpreter(Integer.valueOf(elements[++i])); System.out.println("Stack out:" + left.interpret() + "and" + right.interpret()); this.stack.push(OperatorUtil.getInterpreter(left, right, operator)); System.out.println("Application Operator:" + operator); }else { NumInterpreter numInterpreter = new NumInterpreter(Integer.valueOf(elements[i])); this.stack.push(numInterpreter); System.out.println("Stack:" + numInterpreter.interpret()); } } } public int calculate() { return this.stack.pop().interpret(); } }
Test main method:
public static void main(String[] args) { System.out.println("The test results are:" + new Calculator("18 - 12").calculate()); System.out.println("The test results are:" + new Calculator("18 + 12").calculate()); System.out.println("The test results are:" + new Calculator("18 * 2 + 12 - 6").calculate()); }
2. Reflection of Interpreter Mode in Source Code
2.1 Regular Expression Compile and Continue Pattern Class
private Pattern(String p, int f) { pattern = p; flags = f; // to use UNICODE_CASE if UNICODE_CHARACTER_CLASS present if ((flags & UNICODE_CHARACTER_CLASS) != 0) flags |= UNICODE_CASE; // Reset group index count capturingGroupCount = 1; localCount = 0; if (pattern.length() > 0) { compile(); } else { root = new Start(lastAccept); matchRoot = lastAccept; } } public static Pattern compile(String regex) { return new Pattern(regex, 0); } public static Pattern compile(String regex, int flags) { return new Pattern(regex, flags); }
2.2 ExpressionParser interface in Spring
public interface ExpressionParser { Expression parseExpression(String expressionString) throws ParseException; Expression parseExpression(String expressionString, ParserContext context) throws ParseException; }
3. Advantages and disadvantages of interpreter mode
Advantage:
- Extensibility is strong in interpreter mode because the grammar is represented by many classes. When the grammar rules change, you only need to modify the corresponding non-terminator expression; when you extend the grammar, you only need to add the corresponding non-terminator class.
- New ways of interpreting expressions have been added;
- The grammar corresponding to the implementation of grammar interpreter mode should be relatively simple and easy to implement, and too complex grammar is not suitable for the use of interpreter mode.
Disadvantages:
- When the grammar rules are complex, it causes class expansion: each grammar in the interpreter mode produces a non-terminator expression. When the grammar rules are complex, a large number of interpreter classes will be generated, which makes system maintenance more difficult.
- The inefficient interpreter mode uses a recursive invocation method. Each Phenanthrene expression only cares about its own expression. Each expression needs to know the final result, so the final result of the complete expression is obtained by recursive invocation from back to front.Interpretation efficiency decreases when the full expression level is deeper and debugging is difficult when errors occur because the recursive iteration level is too deep.