In Gof's design model: The basics of reusable object-oriented software, the interpreter pattern is said: Given a language, define a representation of its grammar, and define an interpreter that uses that representation to interpret the sentences in the language. If a particular type of problem occurs at a sufficiently high frequency, it might be worthwhile to describe each instance of the problem as a sentence in a simple language. This allows you to build an interpreter that solves the problem by interpreting these sentences.
Like the game mentioned above, I enter up walk 5, I must follow: Move direction + move way + move distance This format input my instructions, and this format of instruction is a grammar, only according to my definition of this grammar to input, to control the screen of the puppy to move. Of course, I input up walk 5, the screen of the puppy must not understand, it does not know what I entered, this time what to do? I need a tool to translate what I have entered into something that the puppy can understand, and this tool is the interpreter mentioned in the definition, the interpreter interprets the instructions I have entered, and then sends the interpreted instructions to the puppy on the screen, and the puppy understands the actual movement.
The regular expressions that we often use in development are also representative of this type of problem. We sometimes need to match the phone number, the ID number; we don't have to write a specific algorithm for each match, we can define a grammar for each match, and then explain the sentence that defines the grammar as OK.
Abstract Expression Role (abstractexpression): declares an abstract interpretation operation that is implemented for all specific expression roles.
Terminator Expression role (terminalexpression): implements an interpreted operation associated with an element in the grammar, usually with only one terminator expression in an interpreter pattern, but with multiple instances corresponding to different terminator,
Terminator is the basic element used in the language, generally can not be decomposed, such as: X, XA, where A is terminator, because there is no other rule can change a to another symbol, but x can become another symbol, so X is a non-terminator.
non-terminator expression role (nonterminalexpression): Each rule in the grammar corresponds to a non-terminating expression, and non-finalization expressions are incremented according to the complexity of the logic, in principle each grammar rule corresponds to a non-terminator expression.
Environmental Roles (Context): contains some global information outside the interpreter.
Abstractexpression: Declares an abstract interpretation operation, which is shared by all nodes in the abstract syntax tree;
Ternimalexpression: Each terminator in a sentence requires an instance of the class that implements the interpreted operation associated with the Terminator in the grammar;
Nonternimalexpression:
- A nonternimalexpression class is required for each rule in the grammar;
- Maintains an instance variable of type abstractexpression for each symbol in the grammar;
- In order to realize the interpretation of non-terminator in grammar, it is generally necessary to recursively invoke the interpretation operations of those objects representing grammatical symbols.
Context: Contains some global information outside the interpreter;
Client: Construct a grammar sentence that needs to be interpreted, and then invoke the explain operation to interpret it.
The actual interpretation is performed according to the following timing:
- The client constructs a sentence, which is an abstract syntax tree for instances of nonterminalexpression and terminalexpression, and then initializes the context and invokes the interpretation operation;
- Each non-terminator expression node defines an interpretation operation for the corresponding subexpression. The interpretation of each terminator expression forms the basis of recursion.
- The interpretation of each node uses the context of the action to store and access the state of the interpreter.
Example:
#include <iostream>#include<map>#include<string>using namespacestd; classcontext{Private: Map<string,int>ValueMap; Public: voidAddValue (stringKeyintvalue) {Valuemap.insert (std::p air<string,int>(Key,value)); } intGetValue (stringkey) { returnValuemap[key]; }};classabstractexpression{ Public : Virtual intInterpreter (Context context) =0;};classAddnonterminalexpression: Publicabstractexpression{Private: Abstractexpression*Left ; Abstractexpression*Right ; Public: addnonterminalexpression (abstractexpression*left, Abstractexpression *Right ) { This->left =Left ; This->right =Right ; } intInterpreter (Context context) {return This->left->interpreter (context) + This->right->interpreter (context); }};classSubtractnonterminalexpression: Publicabstractexpression{Private: Abstractexpression*Left ; Abstractexpression*Right ; Public: subtractnonterminalexpression (abstractexpression*left, Abstractexpression *Right ) { This->left =Left ; This->right =Right ; } intInterpreter (Context context) {return This->left->interpreter (context)- This->right->interpreter (context); }};classTerminalexpression: Publicabstractexpression{Private : inti; Public: Terminalexpression (inti) { This->i =i; } intInterpreter (Context context) {return This-i; }};intMain () {//A-b+ccontext Context; Context.addvalue ("a",7); Context.addvalue ("b",8); Context.addvalue ("C",2); Subtractnonterminalexpression*subtractvalue =NewSubtractnonterminalexpression (Newterminalexpression (Context.getvalue ("a")),NewTerminalexpression (Context.getvalue ("b"))); Addnonterminalexpression*addvalue =NewAddnonterminalexpression (Subtractvalue,Newterminalexpression (Context.getvalue ("C"))); cout<< addvalue->interpreter (context); return 0; }
Applicability:
You might consider using the interpreter mode in the following situations:
(1) A sentence in a language that needs to be interpreted for execution can be represented as an abstract syntax tree.
(2) Some recurring problems can be expressed in a simple language.
(3) The grammar of a language is simpler.
(4) Implementation efficiency is not a critical issue. (Note: Efficient interpreters are not usually implemented by directly interpreting abstract syntax trees, but rather they need to be converted to other forms, and the use of interpreter patterns is not efficient.) )
Pros and Cons: Advantages:
(1) Easy to change and expand grammar. Because the class is used in the interpreter pattern to represent the grammar rules of the language, it is possible to change or extend the grammar through mechanisms such as inheritance.
(2) Each grammar rule can be represented as a class, so it is easy to implement a simple language.
(3) It is easier to implement grammar. Each expression node class in the abstract syntax tree is implemented in a similar way, and the code for these classes is not particularly complex, and the node class code can be generated automatically by some tools.
(4) It is more convenient to add new explanatory expressions. If the user needs to add a new explanatory expression that only needs to add a new terminator expression or non-Terminator expression class, the original expression class code does not need to be modified to conform to the "open and closed principle".
Disadvantages:
(1) difficult to maintain for complex grammars. In interpreter mode, each rule needs to define at least one class, so if a language contains too many grammatical rules, the number of classes will increase dramatically, making the system difficult to manage and maintain, and consider using a parser to replace the interpreter pattern.
(2) less efficient execution. Because of the large number of loops and recursive calls in the interpreter pattern, it is slow to interpret more complex sentences, and the code debugging process is cumbersome.
[design mode] Interpreter mode interpreter