Note source: Shang Silicon Valley Java design pattern (illustration + framework source code analysis)
Interpreter mode
1. Four arithmetic problems
Four operations are realized through the interpreter mode, such as calculating the value of a + b - c. specific requirements
- 1) First enter the form of the expression, such as a + b + c - d + e. the letters of the expression must not be repeated
- 2) Enter the values of a, B, C, D and e respectively
- 3) Finally, the result is obtained as shown in the figure
Analysis of four arithmetic problems solved by traditional solutions
- 1) Write a method to receive the form of an expression, and then analyze it according to the value entered by the user to get the result
- 2) Problem analysis: if new operators, such as * /, are added, it is not conducive to expansion. In addition, if a method is used to parse, the program structure will be confused and unclear
- 3) Solution: you can consider using the interpreter mode, that is: expression = > interpreter (there can be multiple) = > result
2. Basic introduction to interpreter mode
- 1) In the compilation principle, an arithmetic expression forms lexical units through the lexical analyzer, and then these lexical units build a parsing tree through the parser, and finally form an abstract parsing tree. The lexical analyzer and parser here can be regarded as interpreters
- 2) Interpreter Pattern: given a language (expression), define a representation of its grammar, and define an interpreter to interpret sentences (expressions) in the language
- 3) Application scenario
- An application can represent a sentence in a language that needs to be interpreted and executed as an abstract syntax tree
- Some recurring problems can be expressed in a simple language
- A simple syntax needs to be explained
- 4) Examples include compilers, arithmetic expression calculations, regular expressions, robots, etc
Schematic class diagram
Roles and responsibilities of interpreter mode
- Context environment role: contains global information outside the interpreter
- AbstractExpression abstract expression: declare an abstract interpretation operation, which is shared by all nodes in the abstract syntax tree
- TerminalExpression terminator expression: implements interpretation operations related to terminators in grammar
- NonTerminalExpression non terminator expression: implements interpretation operations related to non terminators in grammar
3. Interpreter mode solves four arithmetic problems
UML class diagram
Core code
Abstract expression
/** * Abstract expression class */ public abstract class Expression { /** * a + b - c * Explain formulas and values. key is the formula (expression) parameters [a, b, c], and value is the specific value * HashMap{a=10, b=20} * * @param var * @return */ public abstract int interpret(Map<String, Integer> var); }
Abstract operation symbol interpreter
/** * Abstract operation symbol interpreter * Each operation symbol here is only related to its left and right numbers, * However, the left and right numbers may also be the result of parsing. No matter what type, they are the implementation classes of Expression class */ public class SymbolExpression extends Expression { protected Expression left; protected Expression right; public SymbolExpression(Expression left, Expression right) { this.left = left; this.right = right; } /** * Because SymbolExpression is implemented by its subclasses, interpreter is a default implementation * * @param var * @return */ @Override public int interpret(Map<String, Integer> var) { return 0; } }
Addition and subtraction interpreter
/** * Addition interpreter */ public class AddExpression extends SymbolExpression { public AddExpression(Expression left, Expression right) { super(left, right); } /** * Process addition * * @param var * @return */ @Override public int interpret(Map<String, Integer> var) { return super.left.interpret(var) + super.right.interpret(var); } } /** * Subtraction interpreter */ public class SubExpression extends SymbolExpression { public SubExpression(Expression left, Expression right) { super(left, right); } /** * Processing subtraction * * @param var * @return */ @Override public int interpret(Map<String, Integer> var) { return super.left.interpret(var) - super.right.interpret(var); } }
Arithmetic unit class
/** * Arithmetic unit class */ public class Calculator { /** * Define expression */ private Expression expression; /** * Constructor passes the expression and parses it * * @param expStr */ public Calculator(String expStr) { // expStr: a+b //The stack object stores variable expressions and expressions obtained by operation Stack<Expression> stack = new Stack<>(); //Split the expression into character arrays [a,+,b] char[] charArr = expStr.toCharArray(); Expression left; Expression right; // Traversal character array [a,+,b] for (int i = 0; i < charArr.length; i++) { switch (charArr[i]) { case '+': // Take out a left = stack.pop(); // Get the next variable and create ` VarExpression` right = new VarExpression(String.valueOf(charArr[++i])); // Take the left and right values as parameters and push them into 'Stack' stack.push(new AddExpression(left, right)); break; case '-': left = stack.pop(); right = new VarExpression(String.valueOf(charArr[++i])); stack.push(new SubExpression(left, right)); break; default: // If it is not an addition and subtraction operator, create a 'VarExpression' and push it into the 'Stack' stack.push(new VarExpression(String.valueOf(charArr[i]))); break; } } // After traversing the entire 'charArr' array, ` stack 'gets the final' Expression '` this.expression = stack.pop(); } public int run(Map<String, Integer> var) { return this.expression.interpret(var); } }
Test code
public static void main(String[] args) throws IOException { System.out.print("Please enter an expression:"); String expStr = getExpStr(); Map<String, Integer> var = getValue(expStr); Calculator calculator = new Calculator(expStr); System.out.println("Operation result:" + expStr + "=" + calculator.run(var)); } public static String getExpStr() throws IOException { return new BufferedReader(new InputStreamReader(System.in)).readLine(); } public static Map<String, Integer> getValue(String expStr) throws IOException { Map<String, Integer> map = new HashMap<>(); String s; for (char ch : expStr.toCharArray()) { s = String.valueOf(ch); if (ch == '+' || ch == '-' || map.containsKey(s)) { continue; } System.out.print("Please enter" + s + "Value of:"); map.put(s, Integer.valueOf(getExpStr())); } return map; }
test result
//Please enter the expression: a+b //Please enter the value of a: 10 //Please enter the value of b: 20 //Operation result: a+b=30
4. Source code analysis of Interpreter pattern in Spring framework
The SpelExpressionParser in the Spring framework uses the Interpreter pattern
Sample code
SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); Expression expression = spelExpressionParser.parseExpression("10*(2+1)*1+66"); int result = (Integer) expression.getValue(); System.out.println(result);
UML class diagram
Roles and responsibilities
-
Expression expression interface
-
There are different expression implementation classes, such as SpelExpression, LiteralExpression and CompositeStringExpression
-
When using, different Expression objects are returned according to the different interpreter objects created
public Expression parseExpression(String expressionString, ParserContext context) throws ParseException { if (context == null) { context = NON_TEMPLATE_PARSER_CONTEXT; } if (context.isTemplate()) { return parseTemplate(expressionString, context); } else { return doParseExpression(expressionString, context); } }
-
After calling the parseExpression method to get the Expression object, call getValue to explain the execution expression and get the final result.
5. Notes and details of interpreter mode
- 1) When a language needs interpretation and execution, the sentences in the language can be expressed as an abstract syntax tree, and the interpreter mode can be considered to make the program have good scalability
- 2) Application scenarios: compiler, operation expression calculation, regular expression, robot, etc
- 3) Possible problems caused by using the interpreter: the interpreter mode will cause class expansion, and the recursive call method used in the interpreter mode will lead to very complex debugging and reduced efficiency