CS 340 - Assignment 6

Due: Tuesday, Dec 11th by 11:59 PM

Preliminary assignment description: will be updated

Updated Nov 27th: revise evaluation strategies, discuss builtin functions, closure environments

Updated Dec 10th: more information on testing

Interpretation using Abstract Syntax Trees

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.

Getting Started

The assignment is designed to be completed within the Eclipse Java IDE.

Download CS340_Assign6.zip.  Within Eclipse, choose File->Import...->Existing Projects into Workspace.  Click Select archive file, Browse..., choose CS340_Assign6.zip, and click Finish.  You should see a new project called ycpl in the Package Explorer.

You will need to modify LexerImpl.java so that it contains the changes you made as part of Assignment 2.

You will need to modiify ParserImpl.java so that it contains the changes you made as part of Assignment 4.

You will need to modify ExpressionImpl.java so that it contains the changes you made as part of Assignment 5.

Your task

Your task is to implement the evaluate method in the InterpreterImpl class by evaluating the given expression AST in the given environment.

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.

A value is an instance of the Value interface.  There are three subclasses of Value:

IntValue - represents an integer

BuiltinFunctionValue - represents a built-in function

UserDefinedFunctionValue - represents a user-defined function

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.  In the interpreter, an environment is represented by an instance of the Environment class.

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.  Identifiers bound in the global environment are shown in red.

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.  Identifiers bound in the environment to evaluate the function call are shown in blue.  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.

Expression type How to evaluate
IntegerLiteralExpression Result is an IntValue whose value is the same as the IntegerLiteralExpression's value
VariableReferenceExpression Result is the Value bound to the VariableReferenceExpression's identifier in the current environment
AssignmentExpression Evaluate the AssignmentExpression's subexpression to yield a value; bind the AssignmentExpression's identifier to that value in the current environment; the overall value is the assigned value
IfThenElseExpression The condition subexpression is evaluated; if 0, the result is computed by evaluating the IfThenElseExpression's else subexpression;otherwise, the result is computed by evaluating the then subexpression
FunctionExpression The result is a UserDefinedFunction value whose closure environment is the current environment, and whose function expression is the FunctionExpression.  (The FunctionExpression object is retained to provide access to the function's parameters, and to the Expression which is the function's body.)
FunctionCallExpression
  1. Evaluate all arguments
  2. If the called function is a UserDefinedFunctionValue:
    •  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 BuiltinFunctionValue:
    • Call getName() to get the name of the builtin function, which will be one of "+", "-", "*", "/", or "=".  Using the argument values, compute the result value (which will be an IntValue).

Builtin Functions

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

Builtin function 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.

Testing

Run the ReadEvalPrint class as a Java Application.

The following text file contains a transcript of a run of the ReadEvalPrint program:

assign6-testing.txt

Make sure you can evaluate each expression, and that your interpreter yields the same result in each case.

Submitting

When you are done:

Export your project to a zip file.  In Eclipse, right-click on the project ycpl in the Package Explorer, and choose Export...->Archive File.  Enter the name/path of the zip file you want to save your project in.  Click Finish.

Upload your saved zip file to the Marmoset server as Project 6.