Chapter 21: Interpreter Mode
1. Introduction to Modes
1) Compilation principle: an arithmetic expression forms lexical units through lexical parsers, which then construct a grammar analysis tree through a grammar parser, resulting in an abstract grammar analysis tree. Lexical parsers and grammar parsers can both be considered interpreters.
2) Interpreter mode: Given a language, define a representation of its grammar and define an interpreter that can be used to interpret expressions in a language.
3) Scenarios:
- Some recurring problems can be expressed in a simple language (regular expressions)
- Scenarios where a simple grammar needs to be interpreted (Operational Expression Calculations)
- A sentence in a language that needs to be interpreted for execution is represented as an abstract grammar tree (compiler, interpreter, robot)
Basic Class Diagram:
- Context: Contains global information outside the interpreter
- AbstractExpression: An abstract expression that declares the interpreter behavior of an expression
- TerminalExpression: The termination expression is the leaf node on the grammar analysis tree
- NonTerminalExpression: A non-terminal expression, a non-leaf node in a grammar analysis tree
2. Actual Cases
The user enters an arithmetic expression, such as a + b + c. We need to implement an interpreter that can interpret this arithmetic expression.
Why do we need an Interpreter pattern to implement expression operations directly in a method?
Is scalability really that good?
Abstract expression class
public abstract class Expression { public abstract int interpreter(HashMap<String, Integer> vars); }
Final expression
public class VarExpression extends Expression { private String key; public VarExpression(String key) { this.key = key; } @Override public int interpreter(HashMap<String, Integer> vars) { return vars.get(this.key); } }
Non-terminal expression
public class AddExpression extends SymbolExpression { public AddExpression(Expression left, Expression right) { super(left, right); } @Override public int interpreter(HashMap<String, Integer> vars) { return super.left.interpreter(vars) + super.right.interpreter(vars); } }
public class SubExpression extends SymbolExpression { public SubExpression(Expression left, Expression right) { super(left, right); } @Override public int interpreter(HashMap<String, Integer> vars) { return super.left.interpreter(vars) - super.right.interpreter(vars); } }
Calculator class (Generate parse tree)
public class Caculator { private Expression expression; public Caculator(String expStr) { Stack<Expression> stack = new Stack<>(); Expression left = null; Expression right = null; for (int i = 0; i < expStr.length(); i++) { switch (expStr.charAt(i)) { case '+': left = stack.pop(); right = new VarExpression(expStr.valueOf(expStr.charAt(++i))); stack.push(new AddExpression(left, right)); break; case '-': left = stack.pop(); right = new VarExpression(expStr.valueOf(expStr.charAt(++i))); stack.push(new SubExpression(left, right)); break; default: stack.push(new VarExpression(expStr.valueOf(expStr.charAt(i)))); } } this.expression = stack.pop(); } public int run(HashMap<String, Integer> vars) { return expression.interpreter(vars); } }
Test Class
public class InterpreterTest { public static void main(String[] args) { String expStr = "a + b - c"; Caculator caculator = new Caculator(expStr); HashMap<String, Integer> vars = new HashMap<>(); vars.put("a", 1); vars.put("b", 2); vars.put("c", 3); System.out.println(caculator.run(vars)); } }
3. Summary of Modes
Advantages: good scalability
Disadvantages: class expansion, complex debugging