1. The concept of interpreter mode
Given a language, define a representation of grammar and define an interpreter that uses the representation to interpret sentences in the language.
2. When to use interpreter mode
Interpreter mode is the only design mode that handles grammar parsing among the 23 design modes.
When you need to be realistic, you need to interpret a grammar to perform your business, and you can represent sentences in that language as an abstract grammar tree, you can use the interpreter mode.
For the example mentioned in the section "Illustration Design Mode" in the Interpreter Mode, use the BNF-style description grammar, for example, to move the car through a grammar:
<program> ::= program <command list> <command list> ::= <command>* end <command> ::= <repeat command> | <primitive command> <repeat command> ::= repeat <number> <command list> <primitive command> ::= go | right | left
This grammar can be made into an abstract grammar tree, for example, to control the walking of a car, you can enter the grammar: program repeat 3 go right end.
It means to repeat go and right three times.
Here's how to implement the interpreter mode using a Java program.
3. How to use interpreter mode
3.1 Implementation
// Abstract Class of Grammar Tree Node public abstract class Node { abstract void parse(Context context); } // <program> ::= program <command list> public class ProgramNode extends Node { private Node commandListNode; void parse(Context context) { // Parser command, must be program to execute down // Equal to skipping program from command context.skipToken("program"); // Deliver the following commands to <command list>for parsing commandListNode = new CommandListNode(); commandListNode.parse(context); } public String toString() { return "[program " + commandListNode + "]"; } } // <command list> ::= <command>* end public class CommandListNode extends Node { ArrayList list = new ArrayList(); void parse(Context context) { // Commands are made up of <command>* end, so loop through the * until the end while (true) { if (context.currentToken() == null) { // Missing end error throw new IllegalArgumentException("missing end"); } else if (context.currentToken().equals("end")) { // Parse end, jump out of loop context.skipToken("end"); break; } else { // Give command to <command>parse Node commandNode = new CommandNode(); commandNode.parse(context); // Easy to print results defines a collection store command list.add(commandNode); } } } public String toString() { return list.toString(); } } // <command> ::= <repeat command> | <primitive command> public class CommandNode extends Node { private Node node; void parse(Context context) { // Determine whether <repeat command>or <primitive command> and find the parsed object you want to correspond to if (context.currentToken().equals("repeat")) { node = new RepeatCommandNode(); node.parse(context); } else { node = new PrimitiveCommandNode(); node.parse(context); } } public String toString() { return node.toString(); } } // <repeat command> ::= repeat <number> <command list> public class RepeatCommandNode extends Node { int number; private Node commandListNode; void parse(Context context) { // Parse repeat, get numbers for printing, and then parse numbers as well (nextToken used) context.skipToken("repeat"); number = context.currentNumber(); context.nextToken(); // Then give the command to <command list> commandListNode = new CommandListNode(); commandListNode.parse(context); } public String toString() { return "[repeat +" + number + " " + commandListNode + "]"; } } // <primitive command> ::= go | right | left public class PrimitiveCommandNode extends Node { String name; void parse(Context context) { // Resolve this command name = context.currentToken(); context.skipToken(name); // Error if not recognized if (!name.equals("go") && !name.equals("right") && !name.equals("left")) { throw new IllegalArgumentException("token is undefined."); } } public String toString() { return name; } } public class Context { StringTokenizer tokenizer; String currentToken; public Context(String text) { tokenizer = new StringTokenizer(text); nextToken(); } public String nextToken() { if (tokenizer.hasMoreTokens()) { currentToken = tokenizer.nextToken(); } else { currentToken = null; } return currentToken; } public String currentToken() { return currentToken; } public void skipToken(String token) { if (!token.equals(currentToken)) { throw new IllegalArgumentException("token is expected, but currentToken is found."); } nextToken(); } public int currentNumber() { int number = 0; try { number = Integer.parseInt(currentToken); } catch (NumberFormatException e) { throw new IllegalArgumentException("number is bad param."); } return number; } }
Use it as follows:
public class Client { public static void main(String[] args) { String command5 = "program repeat 4 repeat 3 go right go right end right end end"; Node node = new ProgramNode(); node.parse(new Context(command5)); System.out.println(node); } } // output // [program [[repeat +4 [[repeat +3 [go, right, go, right]], right]]]]
Benefits of 3.2 Interpreter Mode
- The method can be easily changed and extended because the pattern uses classes to represent method rules, and you can use inheritance to change or extend the method.
- It is also easier to implement because classes that define the total nodes of an abstract grammar tree are generally implemented similarly, and they are easy to write directly.
- The interpreter mode is simply to translate a sentence into the actual command program execution.It can also be analyzed without the Interpreter pattern itself, but by inheriting Abstract expressions, grammar expansion and maintenance are facilitated by relying on the principle of transpose.
3.3 Notes on Interpreter Mode
Interpreter mode defines at least one class for each rule in a method, so methods containing many rules may be difficult to manage and maintain.So when the method is very complex, use other techniques such as the parser or compiler generator to handle it.
4. Summary
The key to the Interpreter pattern is how to abstract the requirements into a grammar tree.
Its business implementation is simple, using classes to parse each command.
Interpreter mode is rarely used in actual system development because it can cause problems such as efficiency, performance and maintenance. Generally, it can be found in large and medium-sized framework projects, such as some data analysis tools, report design tools, scientific computing tools, and so on.
These are just some of my understandings of the interpreter model. There are some deficiencies. Please point out that thank you.