YCP Logo Assignment 4: Interpretation

Due: Friday, Dec 5th by 11:59 PM

Updated Nov 11: Added suggested starting classes, clarified a few details

Updated Nov 21st: Extended the due date.

Your Task

In this assignment, you will implementation evaluation of abstract syntax trees. This will turn the lexical analyzer, parser, and AST builder you have already implemented into a full interpreter for the YCPL language.

Look at the following transcript to get a sense of what your interpreter should do:

assign4-testing.txt

Note that in the above example, >> is a prompt for the user for enter a statement, and the text following the statement is the result of evaluating the expression.

Note: you will need to modify your lexical analyzer so that it reads tokens of input on demand, rather than reading them all up-front (before parsing starts).

Interpretation

An interpreter is a program that executes other programs.

A common implementation strategy for interpreters is to build abstract syntax trees (ASTs) representing expressions to evaluate, and use the structure of the ASTs to determine the meaning of the corresponding expression.

Expressions, Values, and Evaluation

Evaluating an expression means computing the value of the expression.

The YCPL language has two kinds of values: integers and functions.

Function values are further divided into built-in functions and user-defined functions.

So, when evaluated, any expression will yield a value that is either an integer or a function.

Environnments

An environment is a collection of bindings; each binding associates an identifier (variable) with a value.

To implement lexical scoping, each environment contains a reference to its parent (outer) environment. When the interpreter needs to look up the value to which a referenced identifier is bound, it starts by looking in innermost environment. If the binding is not found in the innermost environment, it continues the search in the next outer environment. The search continues until a binding is found, or the search fails (meaning that the identifier is not bound in any environment.)

The global environment is the outermost environment; bindings in the global environment are visible everywhere, unless shadowed by a binding in an inner environment.

For example, consider the following YCPL program:

fact ::= func(n) {
        if (=(n, 1))
        then 1
        else *(n, fact(-(n, 1)));
};

n ::= 6;

fact(n);

In this program, the identifiers n and fact are both bound in the global environment: fact is bound to a function (which computes the factorial of its parameter), and n is bound to the integer value 6.

Note that the built-in functions =, *, and - are automatically part of the global environment.

When the fact function is called, passing the value of n (6) as the argument, a new environment is created to bind the parameter n of the fact function to its argument value, 6. So, when the body of the function is evaluated, the identifier n refers to the parameter n, not the global variable n. The new environment created for the function call has the global environment as its parent (because the global environment is the called function's closure environment; more on this in a bit), so the reference to fact in the body of the function refers to the binding of fact in the global environment. (The identifier fact is free in the body of the function.)

Note that the body of the fact function contains a recursive call: the recursive call is evaluated in a new environment that binds the value computed by the argument expression -(n, 1) to the parameter n in the recursive call.

Evaluating expressions

Each of the 6 kinds of YCPL expressions requires a slightly different evaluation strategy.

Integer Literal Expression
Result is an integer value whose value is the same as the integer literal expression's value
Variable Reference Expression
Result is the value bound to the variable reference expression's identifier in the current environment.
Assignment Expression
Evaluate the assignment expression's subexpression to yield a value; bind the assignment expression's identifier to that value in the current environment; the overall value is the assigned value.
If/Then/Else Expression
The condition subexpression is evaluated; if 0, the result is computed by evaluating the if/then/else expression's else subexpression; otherwise, the result is computed by evaluating the then subexpression
Function Expression
The result is a UserDefinedFunction value whose closure environment is the current environment, and whose function expression is the function expression. (The resulting user-defined function value should retain the entire AST for the function, in order to provide access to the function's parameters and to the Expression (AST) forming the function's body.)

Function Call Expression

  1. Evaluate all arguments

  2. If the called function is a user-defined function value:
    • create a new environment whose parent is the called function's closure environment
    • in the new environment, bind each parameter to the corresponding argument
    • the result value is the result of recursively evaluating the body of the called function in the new environment
  3. If the called function is a builtin function value:
    • Compute the result based on the name of the built-in function, which will be one of "+", "-", "*", "/", or "=". Using the argument values, compute the result value (which will be an integer value).

Builtin Functions

There are 5 builtin functions. Each builtin function takes two integer arguments.

Builtin Operation
+ Add two integer values; result is their sum
- Subtract right hand integer value from left hand value; result is difference
* Multiply two integer values; result is their product
/ Divide left hand integer value by right hand value; result is quotient
= Compare two integer values for equality: result is integer value 1 if they are the same, 0 if different

Closure Environments

Because YCPL is a functional language, functions are values, and can be passed to and returned from functions. A function expression computes a function value.

When a function expression is evaluated, the environment in which it is evaluated becomes its closure environment. The closure environment allows the function value to "remember" the values of any variables defined when the function expression was evaluated.

The following sequence of expressions (and their values upon evaluation) demonstrates closures:

Expression Result of evaluation
add-n ::= func(n) { func(x) { +(n, x); }; } user-defined function
add-threee ::= add-n(3) user-defined function
add-three(8) integer value 11

Explanation: the function assigned to the variable add-n takes a parameter n and returns a function which adds n to its own parameter x. The second expression calls add-n, passing the argument value 3, and assigns the resulting function to the variable add-three. Because the returned function was evaluated in an environment in which the variable n was bound to the value 3, the function has the effect of adding 3 to its parameter x. So, when the add-three function is called with the argument value 8, it returns the sum of 3 and 8, which is 11.

This should remind you of the Lambda calculus.

Submitting

Submit a zip file containing your complete project (all source files, along with whatever other files are needed to compile them) to the submission server as assign4. The URL of the server is

https://camel.ycp.edu:8443/