Behavioral Pattern: Interpreter C h a p t e r 5 – P a g e 149 Some programs benefit from having a language to describe operations that they can perform. The Interpreter Pattern generally describes defining a grammar for that language and using that grammar to interpret statements in that language.
The Interpreter Pattern C h a p t e r 5 – P a g e 150 The AbstractExpression defines an interface for executing an operation. The TerminalExpression implements an Interpret operation associated with terminal symbols in the grammar. One CompoundExpression class is required for every rule in the grammar, each one implementing an Interpret operation for nonterminal symbols in the grammar. The Context contains information that is global to the interpreter. The Client invokes the interpret operation and builds (or is given) an abstract syntax tree, assembled from CompoundExpressions and TerminalExpressions) representing a particular sentence in the language that the grammar defines.
C h a p t e r 5 – P a g e 151 Example: Boolean Expressions A BooleanExpression is either a terminal expression (a Constant) or a compound expression (an AND, OR, or NOT Expression). A BooleanExpression is interpreted (or “interpreted) in the context of its variable names and values. For instance, the BooleanExpression (X AND NOT Y) OR Z in the context of (X,Y,Z) = (true, false, false) evaluates to eval(X AND NOT Y) OR eval(Z), which is (eval(X) AND eval(NOT Y)) OR false, which evaluates to (true AND NOT eval(Y)), which is NOT(false), resulting in an evaluation of true.
C h a p t e r 5 – P a g e 152 Example: Roman Numerals Reading left to right, a Roman Numeral can be interpreted via four “sub-interpreters”. First, the thousands characters (the leading “M” characters) are read. This is followed by the hundreds characters (either a nine- sequence “CM”, a four-sequence “CD”, or an optional five-sequence “D” followed by zero to three one-sequences “C”). The tens and ones characters are handled similarly. Notice that all of the derived “expressions” are terminal.
C h a p t e r 5 – P a g e 153 Roman Numeral Code in C++ #include using namespace std; class Thousand; class Hundred; class Ten; class One; class RomanNumeralInterpreter { public: RomanNumeralInterpreter(); // Creator for client RomanNumeralInterpreter(int){} // Creator for subclasses, avoids infinite loop int interpret(string); // interpret() for client virtual void interpret(string &input, int &total) // interpret() for derived classes { int index = 0; if (input.substr(0, 2) == nine()) { total += 9 * multiplier(); index += 2; } else if (input.substr(0, 2) == four()) { total += 4 * multiplier(); index += 2; }
C h a p t e r 5 – P a g e 154 else { if (input[0] == five()[0]) { total += 5 * multiplier(); index = 1; } else index = 0; int count = 1; while ( (input[index] == one()[0]) && (count <= 3) ) { total += 1 * multiplier(); index++; count++; } // remove leading characters processed input = input.substr(index, input.length() - index); } protected: // cannot be pure virtual because client asks for instance virtual string one() { return ""; } virtual string four() { return ""; } virtual string five() { return ""; } virtual string nine() { return ""; } virtual int multiplier() { return 0; } private: RomanNumeralInterpreter *thousands; RomanNumeralInterpreter *hundreds; RomanNumeralInterpreter *tens; RomanNumeralInterpreter *ones; };
C h a p t e r 5 – P a g e 155 class Thousand: public RomanNumeralInterpreter { public: // provide 1-argument creator to avoid infinite loop in base class creator Thousand(int): RomanNumeralInterpreter(1) {} protected: string one() { return "M"; } string four() { return ""; } string five() { return ""; } string nine() { return ""; } int multiplier() { return 1000; } }; class Hundred: public RomanNumeralInterpreter { public: Hundred(int): RomanNumeralInterpreter(1) {} protected: string one() { return "C"; } string four() { return "CD"; } string five() { return "D"; } string nine() { return "CM"; } int multiplier() { return 100; } }; class Ten: public RomanNumeralInterpreter { public: Ten(int): RomanNumeralInterpreter(1) {} protected: string one() { return "X"; } string four() { return "XL"; } string five() { return "L"; } string nine() { return "XC"; } int multiplier() { return 10; } };
C h a p t e r 5 – P a g e 156 class One: public RomanNumeralInterpreter { public: One(int): RomanNumeralInterpreter(1) {} protected: string one() { return "I"; } string four() { return "IV"; } string five() { return "V"; } string nine() { return "IX"; } int multiplier() { return 1; } }; RomanNumeralInterpreter::RomanNumeralInterpreter() { // use 1-argument creator to avoid infinite loop thousands = new Thousand(1); hundreds = new Hundred(1); tens = new Ten(1); ones = new One(1); } int RomanNumeralInterpreter::interpret(string input) { int total; total = 0; thousands->interpret(input, total); hundreds->interpret(input, total); tens->interpret(input, total); ones->interpret(input, total); if (input != "") // Input was invalid return 0; return total; }
C h a p t e r 5 – P a g e 157 void main() { RomanNumeralInterpreter interpreter; string input; cout << "Enter Roman Numeral: "; cin >> input; while (input != "quit") { cout << " interpretation is " << interpreter.interpret(input) << endl; cout << "Enter Roman Numeral: "; cin >> input; }
Interpreter Pattern Advantages C h a p t e r 5 – P a g e 158 When a program presents a number of different, but somewhat similar, cases with which it must contend, it can be advantageous to use a simple language to describe these cases and then have the program interpret that language. Recognizing cases where the Interpreter Pattern can be helpful can be problematic, and programmers who lack training in formal languages or compilers often overlook this option. One fairly obvious place where languages are applicable is when the program must parse algebraic strings in order to carry out operations based on the computation of user- entered equations. A far more useful situation that uses the Interpreter Pattern is when the program produces varying kinds of output (e.g., generating reports based on a database without having to resort to elaborate SQL queries).