Case Study: A Recursive Descent Interpreter Implementation in C++ (Source Code Courtesy of Dr. Adam Drozdek) Payap University ICS220 - Data Structures and Algorithm Analysis Instructor: Dr. Ken Cosh Analysis and Presentation by Rob Agle First, let’s clear up some terminology… Interpreter • In general, a compiler is a program that converts an entire program from high level source code into some lower level representation (assembly or machine code for example). • An interpreter on the other hand, traditionally translates high level instructions and executes them on the fly (at runtime). • The lines between these two concepts are blurring however… Examples of “Interpreted” Languages Python Pearl JavaScript Ruby Smalltalk Java Interpreter For our purposes – we can simply say that our interpreter will be used to translate and execute one instruction statement at a time. Interpreter For our purposes – we can simply say that our interpreter will be used to translate and execute one instruction statement at a time. Interpreter For our purposes – we can simply say that our interpreter will be used to translate and execute one instruction statement at a time. Interpreter Things our interpreter understands: Variable Names: Any alphanumeric string Operators: + - / * = Commands: Print, Status, End Recursive Descent A process that allows us to descend – or “go down” to lower and lower levels of complexity via recursion, and then work “backwards” towards a solution once all the pieces are in place. Recursive Descent A process that allows us to descend – or “go down” to lower and lower levels of complexity via recursion, and then work “backwards” towards a solution once all the pieces are in place. Recursive Descent For Example: var = 2*(3+5); Recursive Descent For Example: var = 2*(3+5); Recursive Descent For Example: The statement: var = 2*(3+5); can be parsed and broken down into its individual pieces using recursion. Recursive Descent For Example: The statement: var = 2*(3+5); can be parsed and broken down into its individual pieces using recursion. Recursive Descent var = 2*(3+5); Don’t worry about how, just imagine we magically use some combination of direct and indirect recursion to break down the above statement into the following… Recursive Descent var = 2*(3+5); Don’t worry about how, just imagine we magically use some combination of direct and indirect recursion to break down the above statement into the following… var = 2 * ( 3 + 5 ) ; ID var = 2 * ( 3 + 5 ) ; ID var = 2 * ( 3 + 5 ) ; Operator Operator Operator ID Factor Factor Factor var = 2 * ( 3 + 5 ) ; Operator Operator Operator Term Factor ID Term Factor Factor var = 2 * ( 3 + 5 ) ; Operator Operator Operator Expression Expression Term Factor ID Term Factor Factor var = 2 * ( 3 + 5 ) ; Operator Operator Operator Expression Expression Term Factor ID Term Factor Factor var = 2 * ( 3 + 5 ) ; Operator Operator Operator Data: • a list of all ID’s (variables) •An array of characters to store an input statement What do we need (object wise) to accomplish this? Functionality: •A way to get the input statement from the user •A way to parse the input, get values for expressions (if any) and its composite parts (terms, factors – if any) and perform indicated operations. •A few “black boxes”… Data What do we need (object wise) to accomplish this? Data: • a list of all ID’s (variables) •An array of characters to store an input statement Functionality: •A way to get the input statement from the user •A way to parse the input, get values for expressions (if any) and its composite parts (terms, factors – if any) and perform indicated operations. •A few “black boxes”… Functionality So – let’s go back to our concrete example and trace the program… Trace: Input -> var = 2*(3+5); Trace: Input -> var = 2*(3+5); Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = Trace: Input -> var = 2*(3+5); statement.idList = Runtime Stack Statement.ch = getStatement() e= id = command = Trace: Input -> var = 2*(3+5); statement.idList = Runtime Stack Statement.ch = var getStatement() e= id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Runtime Stack Statement.ch = =var R expression() getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Runtime Stack Statement.ch = = R term() R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = = R Runtime Stack R factor() term() f= ? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = = R factor() var = 1.0 minus = 1.0 id = Runtime Stack R term() f= ? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = 2= R factor() var = 1.0 minus = 1.0 id = Runtime Stack R term() f= ? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = 2 R factor() var = 1.0 minus = 1.0 id = Runtime Stack R term() f= ? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = 2 R factor() var = 1.0 minus = 1.0 id = Runtime Stack R term() f= ? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = 2* R factor() var = 1.0 2.0 minus = 1.0 id = Runtime Stack R term() f= ? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = * R factor() var = 2.0 minus = 1.0 id = Runtime Stack R term() f = ?2 R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = * R Runtime Stack R factor() term() f= 2 *? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = * R factor() var = 1.0 minus = 1.0 id = Runtime Stack R term() f= 2 *? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = *( R factor() var = 1.0 minus = 1.0 id = Runtime Stack R term() f= 2 *? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = ( R factor() var = 1.0 minus = 1.0 id = Runtime Stack R term() f= 2 *? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = ( R expression() R factor() var = 1.0 ? minus = 1.0 id = Runtime Stack R term() f= 2 *? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = ( R R factor() var = ? minus = 1.0 id = R Runtime Stack Here, we have our first recursive function call… expression() term() f= 2 *? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = ( R R factor() var = ? minus = 1.0 id = R Runtime Stack This conveniently allows us to naturally follow mathematical precedence … expression() term() f= 2 *? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = ( term() expression() R factor() var = ? minus = 1.0 id = R Runtime Stack A series of additional indirect recursive calls will determine the value of (3+5)… R R term() f= 2 *? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = ( factor() R R term() expression() R factor() var = ? minus = 1.0 id = R Runtime Stack A series of additional indirect recursive calls will determine the value of (3+5)… R term() f= 2 *? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = (3 factor() R R term() expression() R factor() var = ? minus = 1.0 id = R Runtime Stack A series of additional indirect recursive calls will determine the value of (3+5)… R term() f= 2 *? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = + Statement.ch = 3 factor() R R term() expression() R factor() var = ? minus = 1.0 id = R Runtime Stack A series of additional indirect recursive calls will determine the value of (3+5)… R term() f= 2 *? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = + R expression() R factor() var = ? minus = 1.0 id = Runtime Stack R term() f= 2 *? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = +) R expression() R factor() var = ?8 minus = 1.0 id = We now see the same chain of recursive calls to term and factor… Which eventually sets t = 8 in expression(). This value will be returned to factor… Runtime Stack R term() f= 2 *? R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = ); R factor() var = 8 minus = 1.0 id = •factor() can now return 8 to term() Runtime Stack R term() f = 2 * ?8 R expression() t= ? getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = Statement.ch = ; R Runtime Stack •factor() can now return 8 to term() •term() can return 2*8 =16 to expression() term() f= 2 *8 R expression() t = ?16 getStatement() e= ? id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = •factor() can now return 8 to term() •term() can return 2*8 =16 to expression() •expression() also returns 16 to getStatement… Runtime Stack Statement.ch = ; R expression() t = 16 getStatement() e= ? 16 id = var command = VAR Trace: Input -> var = 2*(3+5); statement.idList = var => 16 Runtime Stack Statement.ch = ; getStatement() e= 16 id = var command = VAR Control is returned to the main function, where once again getStatement will be called… Final Thoughts For the sake of time – a lot of the non-recursive functions were overlooked and treated as black boxes. Tracing your own input through the functions carefully will leave you with a solid understanding of recursion. Recursive descent was once a popular way to build a parser. These days more complex parsers can be built by parser generators. For more information (and a solid headache), google: LR parsers.