//+------------------------------------------------------------------+ //| Interpreter.mqh | //| 2019-2020, dimitri pecheritsa | //| 792112@gmail.com | //+------------------------------------------------------------------+ //| interpreter — behavioral design pattern | //+------------------------------------------------------------------+ // design patterns: elements of reusable object-oriented software // gof > erich gamma, richard helm, ralph johnson, john vlissides // published — 1994 //+------------------------------------------------------------------+ //| intent | //+------------------------------------------------------------------+ // given a language, define a represention for its grammar // along with an interpreter that uses the representation // to interpret sentences in the language //+------------------------------------------------------------------+ //| benefits | //+------------------------------------------------------------------+ // aspect that may vary > grammar and interpretation of a language //+------------------------------------------------------------------+ //| applicability | //+------------------------------------------------------------------+ // there is a language to interpret // statements can be abstract syntax trees // simple grammar // parser generators // for complex grammar // don't build abstract syntax trees // efficiency is not a critical concern // interpreters translate parse trees first (state machine) //+------------------------------------------------------------------+ //| structure | //+------------------------------------------------------------------+ // // +----->|Context| // | // |Client|-------+ // | // +----->|AbstractExpression|*<----------------+ // |------------------| | // |Interpret(Context)| | // ^ | // | | // +-----------+-------------+ | // | | | // |TerminalExpression| |NonterminalExpression|o--+ // |------------------| |---------------------| // |Interpret(Context)| |Interpret(Context) | // #include <SRCPatternsPatternOrganizer.mqh> namespace Interpreter { //+------------------------------------------------------------------+ //| participants > abstract expression | //+------------------------------------------------------------------+ // declare abstract interpret method for all nodes class Context; class AbstractExpression { public: virtual void Interpret(Context&)=0; }; //+------------------------------------------------------------------+ //| participants > terminal expression | //+------------------------------------------------------------------+ // implement interpret method // associated with terminal symbols in the grammar // an instance is required for every terminal symbol in a sentence class TerminalExpression:public AbstractExpression { public: void Interpret(Context&); }; //+------------------------------------------------------------------+ //| participants > terminal expression > interpret | //+------------------------------------------------------------------+ void TerminalExpression::Interpret(Context& context) { context.m_result= StringSubstr(context.m_source,context.m_position,1)== CharToString(context.m_vocabulary); } //+------------------------------------------------------------------+ //| participants > nonterminal expression | //+------------------------------------------------------------------+ // required for every rule r = r1, r2, ..., rn in the grammar // maintains instances of abstract expression // for each of the symbols r1 through rn // implements interpret method for nonterminal symbols in the grammar // interpret method calls itself recursively on r1 through rn class NonterminalExpression:public AbstractExpression { protected: AbstractExpression* m_nonterminal_expression; AbstractExpression* m_terminal_expression; public: void Interpret(Context&); ~NonterminalExpression(void); }; //+------------------------------------------------------------------+ //| participants > nonterminal expression > destructor | //+------------------------------------------------------------------+ NonterminalExpression::~NonterminalExpression(void) { delete m_nonterminal_expression; delete m_terminal_expression; } //+------------------------------------------------------------------+ //| participants > nonterminal expression > interpret | //+------------------------------------------------------------------+ void NonterminalExpression::Interpret(Context& context) { if(context.m_position<StringLen(context.m_source)) { m_terminal_expression=new TerminalExpression; m_terminal_expression.Interpret(context); context.m_position++; if(context.m_result) { m_nonterminal_expression=new NonterminalExpression; m_nonterminal_expression.Interpret(context); } } } //+------------------------------------------------------------------+ //| participants > context | //+------------------------------------------------------------------+ // contains information that's global to the interpreter class Context { public: string m_source; char m_vocabulary; int m_position; bool m_result; //--- Context(char,string); void Result(void); }; //+------------------------------------------------------------------+ //| participants > context > constructor | //+------------------------------------------------------------------+ Context::Context(char vocabulary,string source): m_source(source), m_vocabulary(vocabulary), m_position(0), m_result(false) { } //+------------------------------------------------------------------+ //| participants > context > result | //+------------------------------------------------------------------+ void Context::Result(void) { printf("result is %s for m_source:'%s' with m_vocabulary:'%s'", (string)m_result,m_source,CharToString(m_vocabulary)); } //+------------------------------------------------------------------+ //| participants > client | //+------------------------------------------------------------------+ // builds (or is given) an abstract syntax tree // tree represents a sentence in the language // grammar defines language // tree is assembled from instances of the nonterminal expression and // terminal expression classes // call interpret method class Client:public ClientExample { public: string Output(void); void Run(void); }; string Client::Output(void) {return __FUNCTION__;} //+------------------------------------------------------------------+ //| collaborations | //+------------------------------------------------------------------+ // client // builds (or is given) the sentence // as an abstract syntax tree of nonterminal expression and // terminal expression // initializes the context and invokes the interpret operation // nonterminal expression // defines interpret method on each subexpression // terminal expression // interpret method defines the base case in the recursion // node // interpret method uses the context to store and access // the state of the interpreter void Client::Run(void) { //---language recognition grammar context (example) // V = {a}; L = V*; where V-m_vocabulary, L-language // correct chains: a, aa, aaa, ... // wrong chains: b, ab, aba, ... Context context_1('a',"aaa"); //chain ok Context context_2('a',"aba"); //wrong chain AbstractExpression* expression; //--- expression=new NonterminalExpression; expression.Interpret(context_1); context_1.Result(); //result ok delete expression; //--- expression=new NonterminalExpression; expression.Interpret(context_2); context_2.Result(); //bad result delete expression; } } //+------------------------------------------------------------------+ //| output | //+------------------------------------------------------------------+ // Interpreter::Client::Output // result is true for m_source:'aaa' with m_vocabulary:'a' // result is false for m_source:'aba' with m_vocabulary:'a' //+------------------------------------------------------------------+ //| consequences | //+------------------------------------------------------------------+ // easy to change and extend the grammar // inherit to change or extend the grammar // modify expressions incrementally // change old expressions to define new ones // easy to implement grammar // nodes // have similar implementations // easy to write // can be generated with a compiler/parser generator // complex grammars are hard to maintain // at least one class for every rule // hard to manage and maintain // parser or compiler generators are more appropriate // adding new ways to interpret expressions // easier to evaluate an expression // define a new operation on the expression // support pretty printing/type-checking // use visitor to avoid changing the grammar classes //+------------------------------------------------------------------+ //| implementation | //+------------------------------------------------------------------+ // creating the abstract syntax tree // doesn't address parsing // the tree can be created by // table-driven parser // hand-crafted (recursive) parser // client // defining the interpret operation // not in the expression classes // put interpret in a separate visitor object // grammar for a programming language operations // type-checking // optimization // code generation // terminal symbols with the flyweight pattern // grammars // whose sentences contain many occurrences // of a terminal symbol // might benefit from sharing a single copy of that symbol // for computer programs // each program variable will appear in many places // throughout the code // terminal nodes // generally don't store their tree m_position // parent nodes // pass them the context for interpretation // flyweight pattern applies // shared (intrinsic) state and passed-in (extrinsic) state // are distinct //+------------------------------------------------------------------+ //| related patterns | //+------------------------------------------------------------------+ // composite > the abstract syntax tree // flyweight > share terminal symbols within the abstract syntax tree // iterator > traverse the structure // visitor > maintain the behavior in each node // in the abstract syntax tree in one class //+------------------------------------------------------------------+