Lecture 12: Conditional Expressions and Local Binding Introduction Corresponds to EOPL 3.3-3.4 Please review Version-1 interpreter to make sure that you understand how it works Now we will extend the basic interpreter Version-1 to Version-2 Add new functionality to our PL incorporating: 1. Conditional expressions (if-then-else expressions) 2. Local variable bindings (let expressions) Thereafter we will add user-defined procedures Copyright Bill Havens 1
Conditional Expressions Overview Conditionals essential to any functional PL Consider the utility of a PL without conditionals ( straight-line code ) Approach: 1. Add a BNF rule to the grammar describing the syntax. 2. Annotate the BNF with the abstract syntax describing the node in the abstract syntax tree. 3. Add a new case to our interpreter for evaluating conditional expressions. BNF Rule for Conditional Expressions <expression> ::= if <expression> then <expression> else <expression> Temporary convention: Boolean false = 0; true > 0 Examples: if 1 then 2 else 3 if -(3,+(1,2)) then 2 else 3 Copyright Bill Havens 2
Abstract Datatype Representation New variant of Expression datatype with 3 data fields: if-exp (test-exp then-exp else-exp) Each data field is an Expression datatype recursively Defined datatype for Expression will be created automatically by SLLGEN Implemented by adding a new production to our grammar for SLLGEN: (expression ("if" expression "then" expression "else" expression) if-exp) The specification for SLLGEN says to generate an if-exp abstract datatype record when an if expression is parsed. Copyright Bill Havens 3
Augmented PL Version-2 grammar Now the SLLGEN grammar for Version-2 PL looks like: (define the-grammar '((program (expression) a-program) (expression (number) lit-exp) (expression (identifier) var-exp) (expression (primitive "(" (separated-list expression ",") ")") primappexp) (expression ("if" expression "then" expression "else" expression) if-exp) (primitive ("+") add-prim) (primitive ("-") subtract-prim) (primitive ("*") mult-prim) (primitive ("add1") incr-prim) (primitive ("sub1") decr-prim) )) Copyright Bill Havens 4
Automatically adds a new variant to the expression datatype corresponding to the abstract syntax as follows: (if-exp (test-exp expression?) (true-exp expression?) (false-exp expression?)) Copyright Bill Havens 5
Changes to the Interpreter for PL Version-2 Augmented Interpreter Now we have a parser which generates ASTs for our Version-2 PL But we still have to augment the intepreter to execute our new PL The behavior of a conditional expression is defined by a new case variant in the eval-expression function: (define eval-expression (lambda (exp env) (cases expression exp (lit-exp (datum) datum) (var-exp (id) (apply-env env id)) (primapp-exp (prim rands) (let ((args (eval-rands rands env))) (apply-primitive prim args))) (if-exp (test-exp true-exp false-exp) (if (true-value? (eval-expression test-exp env)) (eval-expression true-exp env) (eval-expression false-exp env))) (else (eopl:error 'eval-expression "Not here:~s" exp)) ))) Copyright Bill Havens 6
Analysis The evaluator recurses on the test-exp and subsequently either the true-exp or the false-exp The operational behavior of the if-exp is defined in terms of the Scheme if expression. The object language semantics is specified in terms of the implementation language Auxiliary Functions Note that the conditional expression is defined in terms of one auxiliary function, true-value?. Since we currently have no boolean expressed values in our PL, we will need to interpret numbers as booleans a zero representing false and any other value representing true. (define true-value? (lambda (x) (not (zero? x)))) Copyright Bill Havens 7
Examples Using the read-eval-print loop, we can test conditional expressions: --> if 1 then 2 else 3 2 --> if -(3, +(1, 2)) then 2 else 3 3 Copyright Bill Havens 8
Local Binding Introduction Finally we add a let expression to our Version-2 PL to allow local bindings. The new expressions will have the following form: let x = 5 y = 6 in +(x, y) Let uses lexical (static) scoping (like Scheme and Java) The body of the let expression is evaluated in an environment containing bindings for the variables in the let expression. Free variable references in the body refer to variables declared in the program text surrounding the let expression Copyright Bill Havens 9
BNF The BNF for let expressions is defined as follows: <expression> ::= let {<identifier> = <expression>}* in <expression> Note that there can be multiple variable declarations in the let expression but only one expression in the body Abstract Datatype Representation New variant of Expression datatype with 3 data fields: let-exp (ids rands body) Defined datatype for Expression variant will be created automatically by SLLGEN Implemented by adding a new production to our grammar for SLLGEN: (expression ("let" (arbno identifier "=" expression) "in" expression) let-exp) The specification for SLLGEN says to generate an let-exp abstract datatype record when a let expression is parsed. Copyright Bill Havens 10
Final PL Version-2 grammar Now the SLLGEN grammar for Version-2 PL looks like: (define the-grammar '((program (expression) a-program) (expression (number) lit-exp) (expression (identifier) var-exp) (expression (primitive "(" (separated-list expression ",") ")") primappexp) (expression ("if" expression "then" expression "else" expression) if-exp) (expression ("let" (arbno identifier "=" expression) "in" expression) let-exp) (primitive ("+") add-prim) (primitive ("-") subtract-prim) (primitive ("*") mult-prim) (primitive ("add1") incr-prim) (primitive ("sub1") decr-prim) )) Copyright Bill Havens 11
Automatically adds a new variant to the expression datatype corresponding to the abstract syntax as follows: (let-exp (ids (list-of symbol?)) (rands (list-of expression?)) (body expression?)) Copyright Bill Havens 12
Final changes to the Interpreter for PL Version-2 Augmented Interpreter Now we have a parser which generates ASTs for our final Version-2 PL Again we have to augment the intepreter to execute our new PL The behavior of a let expression is defined by a new case variant in the evalexpression function: Behavior The interpreter will recurse once for each expression in the variable declarations and once on the body of the let expression When we recurse on the body, we must have an environment with new bindings corresponding to the declarations. Extension of current environment surrounding the let-exp This new environment is passed to the interpreter for eval-expression Copyright Bill Havens 13
Augmented Interpreter for Version-2 PL (define eval-expression (lambda (exp env) (cases expression exp (lit-exp (datum) datum) (var-exp (id) (apply-env env id)) (primapp-exp (prim rands) (let ((args (eval-rands rands env))) (apply-primitive prim args))) (if-exp (test-exp true-exp false-exp) ;\new4 (if (true-value? (eval-expression test-exp env)) (eval-expression true-exp env) (eval-expression false-exp env))) (let-exp (ids rands body) (let ((args (eval-rands rands env))) (eval-expression body (extend-env ids args env)))) (else (eopl:error 'eval-expression "Not here:~s" exp)) ))) Auxiliary function eval-rands evaluates a list of expressions Note that the let inside of the interpreter is NOT interpreting our PL let-exp Copyright Bill Havens 14
Examples We can use the REP loop, to test let expressions: --> let x = 1 in let y = +(x,3) z = 4 in add1(-(y,z)) 1 --> let x = 1 in let x = +(x,2) in add1(x) 4 Copyright Bill Havens 15