1. Overview
Designing a software for addition and subtraction calculations, as illustrated above. Our first idea is to use a tool class and provide the corresponding tool methods for addition and subtraction.
//Used for the addition of two integers public static int add(int a,int b){ return a + b; } //Used for the addition of two integers public static int add(int a,int b,int c){ return a + b + c; } //For n integer additions public static int add(Integer ... arr) { int sum = 0; for (Integer i : arr) { sum += i; } return sum; }
The above form is relatively simple and limited, and if the form changes a lot, this does not meet the requirements because there are infinite combinations of two operators and values, such as 1+2+3+4+5, 1+2+3-4, etc.
Clearly, there is a need for a translation recognition machine that can parse the legitimate sequence of operations consisting of numbers and + - symbols. If both operators and numbers are considered nodes, the ability to read and parse operations on a node-by-node basis is interpreter mode thinking.
Definition: Given a language, define its grammatical representation and define an interpreter that uses this identifier to interpret sentences in the language.
In Interpreter mode, we need to abstract the problem to be solved into a "language". For example, in addition and subtraction operations, the rule is: a legal sequence of numeric values and ++, and "1+3-2" is the sentence in that language.
Interpreters are meant to interpret the meaning of statements. But how do you describe rules?
Grammar rules: Grammar is a formal rule used to describe the grammatical structure of a language.
expression ::= value | plus | minus plus ::= expression '+' expression minus ::= expression '-' expression value ::= integer
Note: The symbol':='here denotes what is defined as, the vertical line | denotes or, one of the left and right, the character itself in quotation marks, and the grammar outside quotation marks.
The above rule describes that an expression can be either a value or a plus or minus operation, and that plus and minus are composed of an expression combination operator and the type of value is an integer.
Abstract Grammar Tree: In computer science, an abstract grammar tree (AST), or Syntax tree for short, is an abstract representation of the grammar structure of source code. It represents the grammar structure of a programming language in a tree form, where each node represents a structure in the source code.
Trees are used to represent grammatical sentences.
2. Structure
The interpreter mode contains the following main roles.
The Abstract Expression role: Defines the interface of the interpreter, conventions the interpretive operation of the interpreter, mainly including the method interpret().
Terminal Expression role: A subclass of abstract expressions used to implement operations related to terminators in grammar where each terminator has a specific termination expression corresponding to it.
The Nonterminal Expression role: is also a subclass of abstract expressions used to implement operations related to non-terminators in grammar where each rule corresponds to a non-terminator expression.
Context role: Usually contains the data needed by each interpreter or a common function used to transfer data shared by all interpreters from which subsequent interpreters can obtain these values.
Client: The main task is to convert the sentences or expressions that need to be analyzed into an abstract grammar tree that uses the description of the interpreter object, then invoke the interpreter's interpretation method, or, of course, indirectly access the interpreter's interpretation method through the environment role.
3. Case Realization
Design software that implements addition and subtraction.
The code is as follows:
//Abstract Role AbstractExpression public abstract class AbstractExpression { public abstract int interpret(Context context); } //Terminator Expression Role public class Value extends AbstractExpression { private int value; public Value(int value) { this.value = value; } @Override public int interpret(Context context) { return value; } @Override public String toString() { return new Integer(value).toString(); } } //Non-terminator expression Role addition expression public class Plus extends AbstractExpression { private AbstractExpression left; private AbstractExpression right; public Plus(AbstractExpression left, AbstractExpression right) { this.left = left; this.right = right; } @Override public int interpret(Context context) { return left.interpret(context) + right.interpret(context); } @Override public String toString() { return "(" + left.toString() + " + " + right.toString() + ")"; } } ///Non-terminator expression Role subtraction expression public class Minus extends AbstractExpression { private AbstractExpression left; private AbstractExpression right; public Minus(AbstractExpression left, AbstractExpression right) { this.left = left; this.right = right; } @Override public int interpret(Context context) { return left.interpret(context) - right.interpret(context); } @Override public String toString() { return "(" + left.toString() + " - " + right.toString() + ")"; } } //Terminator expression Role variable expression public class Variable extends AbstractExpression { private String name; public Variable(String name) { this.name = name; } @Override public int interpret(Context ctx) { return ctx.getValue(this); } @Override public String toString() { return name; } } //Environment Class public class Context { private Map<Variable, Integer> map = new HashMap<Variable, Integer>(); public void assign(Variable var, Integer value) { map.put(var, value); } public int getValue(Variable var) { Integer value = map.get(var); return value; } } //Test Class public class Client { public static void main(String[] args) { Context context = new Context(); Variable a = new Variable("a"); Variable b = new Variable("b"); Variable c = new Variable("c"); Variable d = new Variable("d"); Variable e = new Variable("e"); //Value v = new Value(1); context.assign(a, 1); context.assign(b, 2); context.assign(c, 3); context.assign(d, 4); context.assign(e, 5); AbstractExpression expression = new Minus(new Plus(new Plus(new Plus(a, b), c), d), e); System.out.println(expression + "= " + expression.interpret(context)); } }
4. Advantages and disadvantages
Strengths:
Grammar is easy to change and extend. Since classes are used to represent grammar rules of a language in interpreter mode, you can change or extend grammar through mechanisms such as inheritance. Each rule can be represented as a class, which makes it easy to implement a simple language.
Implementing grammar is easier. Each expression node class in the abstract grammar tree is implemented in a similar way, and the code for these classes is not particularly complex.
Adding new explanatory expressions is more convenient. If users need to add new explanatory expressions, they only need to add a new terminator expression or a non-terminator expression class. The original expression class code does not need to be modified and conforms to the Open and Close Principle.
Disadvantages:
Complex grammars are difficult to maintain. In interpreter mode, each rule requires at least one class to be defined, so if a language contains too many grammar rules, the number of classes will increase dramatically, making the system difficult to manage and maintain.
Low execution efficiency. Because of the large number of loops and recursive calls used in interpreter mode, complex sentences are interpreted slowly and the debugging process of the code is cumbersome.
5. Use scenarios
When the grammar of a language is relatively simple and the efficiency of its execution is not a key issue.
When problems recur and can be expressed in a simple language.
When a language needs to be interpreted and executed and sentences in the language can be represented as an abstract grammar tree.