YCP Logo Assignment 6: Interpretation

Due: Thursday, December 9th by 11:59 PM

Getting Started

Create a new package in the src folder of your YCP_Scheme project by right-clicking on the src folder and choosing New->Package. Name the new package

edu.ycp.cs340.scheme.value

Copy the following source files into this new package:

Cons.java

Function.java

Literal.java

Nil.java

PrimitiveFunction.java

SymbolValue.java

ValueFactory.java

Value.java

ValueType.java

Copy the following files into the src/edu/ycp/cs340/scheme folder of your YCP_Scheme project in your Eclipse workspace:

BuiltinFunctions.java

Environment.java

InterpreterException.java

Interpreter.java

Main.java

After you have copied these files, refresh the project by right-clicking on YCP_Scheme in the Package Explorer, and choosing Refresh.

Your Task

Your task is to implement the evaluate method of the Interpreter class.

The method takes two parameters:

  • ast, which is an ASTNode object representing the abstract syntax tree of a Scheme definition or expression
  • env, which is an Environment object containing the bindings of identifiers (variable names) to values

This will be a complex project, although you won't need to write a great deal of code. You should implement the interpreter incrementally, as described below under "Hints".

Concepts/Classes

This section describes the concepts and classes that you will use in interpreting ASTs.

All of these classes, and the methods within these classes, have extensive API documentation.

Value

The Value interface is implemented by all classes which represent Scheme values. The methods defined by this interface allow the interpreter to work with Scheme values.

The Value interface is implemented by the following classes:

  • Cons - represents one node of a nonempty list
  • Function - represents a user-defined function (lambda expression)
  • Literal - represents a literal Scheme integer, boolean, or string value
  • Nil - represents an empty list
  • PrimitiveFunction - represents a primitive (built-in) function such as +, -, *, /, cons, car, cdr, etc.
  • SymbolValue - represents a literal Scheme symbol value

ValueType

The ValueType enumeration defines the different kinds of Scheme values:

public enum ValueType {
        INTEGER,
        STRING,
        BOOLEAN,
        SYMBOL,
        CONS,
        NIL,
        FUNCTION,
        PRIMITIVE,
        ANY;

In general, when the interpreter is working with a Value object, it can call the getValueType() method in order to find out what kind of value the Value object is, in order to determine which methods may be called on it.

The INTEGER, STRING, and BOOLEAN members indicate integer, string, and boolean values, respectively. Value objects with these types are instances of the Literal class.

The SYMBOL member inidicates a literal Scheme symbol value. Objects with this value type are instances of the SymbolValue class.

The CONS member indicates a node of a nonempty Scheme list. Objects with this value type are instances of the Cons class.

The NIL member indicates an empty Scheme list. There is only a single object with this value type, which is an instance of the Nil class.

The FUNCTION member indicates a user-defined function (a lambda expression). Objects with this value type are instances of the Function class.

The PRIMITIVE member indicates a primitive (built-in) function. Objects with this value type are instances of the PrimitiveFunction class.

The ANY member is a special value used only by PrimitiveFunction objects as part of checking the types of argument values.

ValueFactory

The ValueFactory class has a number of static methods which are useful for creating instances of Scheme values.

Environment

Environment objects represent a set of bindings of Scheme variables to values. Because Scheme is a lexically-scoped language, each environment (except for the outermost "global" environment) has a parent environment in which it is nested.

When looking up the value of a variable, the search starts in the current environment. If no binding is found, the parent environment is searched recursively. No binding for the variable is found in any environment, then an InterpreterException is thrown signaling a reference to an undefined variable.

The Environment class has one constructor and two methods:

  • public Environment(Environment parent) - constructor to create a new empty environment with given parent environment
  • public Value lookup(String name) - looks up the value of a variable
  • public void bind(String name, Value value) - binds a variable name to a value

Interpreter

An instance of the Interpreter class is responsible for evaluating Scheme expressions.

It has a single public method:

public Value interpret(ASTNode ast)

This method evaluates the AST of a Scheme expression or definition in the global environment. The global environment is the outermost environment, and it contains bindings for all of the builtin Scheme functions (such as +, -, car, cons, etc.)

Your task is to implement the method

private Value evaluate(ASTNode ast, Environment env)

which evaluates the AST of a Scheme expression or definition in a specified environment. It will call itself recursively to evaluate any subexpressions of the expression or definition being evaluated.

Evaluation of LITERAL AST nodes is already implemented. You will need to add code to evaluate other kinds of AST nodes.

BuiltinFunctions

This class defines all of the PrimitiveFunction objects representing built-in functions. When a new instance of the Interpreter class is created, it uses the addAll static method of BuiltinFunctions to bind all of the built-in functions in the new interpreter's global environment.

You will not need to modify this class unless you want to add new built-in functions.

Main

The Main class has been updated to execute a read-eval-print loop, where Scheme expressions and definitions are read one at a time from the console, evaluated, and the results of the evaluation printed.

Important note - The Main class expects that your Parser class has a method called parseTopLevelItem(), which reads a single top level item (expression or definition) and returns its parse tree.

Hints

This section describes a possible approach for evaluating each kind of Scheme AST.

In general, you can (and should) tackle these one at a time.

LITERAL

Evaluating a LITERAL AST node means turning the Java literal value (Integer, Boolean, or String) into an equivalent Scheme Value object.

DEFINITION

A definition binds a value to a variable in the current environment. The subexpression of the definition should be evaluated recursively, and its value bound to the identifier (variable name) defined by the DEFINITION node.

The result of evaluating a definition should be the result of evaluating the subexpression.

VAR_REF

The result of evaluating a variable reference is found by looking up the value of the variable in the current environment.

LET, LET_PAIR

A let expression is evaluated by evaluating the subexpression in each let pair, binding the value of the subexpression to the identifier stored in the LET_PAIR node in a new environment whose parent is the current environment.

The overall result of the let expression is found by evaluating the let expressions body expression in the new environment in which the variables created by the let pairs are bound.

IF

In an if expression, the first subexpression is a condition which should evaluate to a boolean value. (If it does not evaluate to a boolean value, throw an InterpreterException.)

If the condition is true, the result of the if expression is the result of recursively evaluating the second subexpression.

If the condition is false, the result of the if expression is the result of recursively evaluating the third subexpression.

AND, OR, NOT

All of these should evaluate to a boolean value.

An and expression evaluates to a true value if and only if all of its subexpressions evaluate to true values.

An or expression evaluates to a true value if and only if at least one of its subexpressions evaluates to a true value.

A not expression computes the logical negation of the value of its subexpression.

You may implement and and or expressions using short-circuiting.

FUNCTION

A FUNCTION AST node represents a lambda expression.

Return a new Function object which has the current environment as its closure environment, and which has the entire FUNCTION AST as its AST. (Function objects store the AST of the lambda expression so that they can later access both the formal parameters and body expression of the lambda expression.)

FUNCTION_APPLICATION

The first subexpression should be evaluated recursively to produce either a primitive function or a user-defined function. (If the value is not one of these types of values, throw an InterpreterException.)

All of the remaining subexpressions should be recursively evaluated in the current environment, and the computed values stored in a List. These are the argument values.

If the called function is a primitive function, the result of the call is the result of calling the applyPrimitive method, passing the list of argument values as a parameter.

If the called function is a user-defined function, then

  1. A new environment should be created, which has the called function's closure environment as its parent environment.
  2. Each formal parameter of the called function should be bound to the corresponding argument value. (If the number of argument values does not exactly match the number of formal parameters, throw an InterpreterException.)
  3. The body expression of the called function should be recursively evaluated in the environment created in step 1.

The result of step 3 is the result of the overall function call expression.

Quoted Literals

For extra credit, you can modify your ASTBuilder and Interpreter classes to support the evaluation of quoted literals.

Expressions such as

(quote a)

where a is an identifier should evaluate to a SymbolValue. You can call the ValueFactory.getSymbol() static method to convert a Java string into a SymbolValue object.

Expressions such as

(quote 123)

where the quoted value is a literal value should simply evaluate to the literal value. (Quoting does not change the way that literal values are interpreted.)

Expressions such as

(quote (a b c 1 2 3))

where the quoted value is a list should be converted into a literal Scheme list.

In each case, a good approach will be to have your ASTBuilder class store a Value object in the AST node representing the quoted literal expression. That makes evaluation by the interpreter trivial: it simply extracts the Value and returns it.

You will need to think carefully about how to translate the ASTs representing literal lists into Scheme lists. A Scheme list is a chain of Cons cells, the car of each cell being a member of the list, and the cdr of each cell being the continuation of the list. The overall list is terminated by a Nil value.

Testing

Here is an example transcript showing how the interpreter should evaluate various expressions and definitions.

Grading

Grading will be done as follows:

  • Defintions: 10 points
  • Variable references: 10 points
  • Let expressions: 10 points
  • If expressions: 10 points
  • And, or, and not expressions: 15 points
  • Applications of primitive functions: 15 points
  • Lambda expressions, calls to user-defined functions: 30 points

The following extra credit options are available:

  • Support quoted literals: up to 15 points

  • Support a built-in load function which takes a string (filename) as an argument and evaluates each top level item in the file: up to 10 points

  • Modify the interpreter so that tail-recursive Scheme functions do not cause the interpreter to throw a StackOverflowError, regardless of how deep the recursion becomes (there is a really simple way to do this): up to 25 points

  • Support the shorthand syntax that a single quote character quotes the atom or list that follows. I.e.,

    'a
    

    means the same as

    (quote a)
    

    and

    '(a b c)
    

    means the same as

    (quote (a b c))
    

    This feature is worth up to 10 points.

Submitting

Export your completed Eclipse project to a zip file by right-clicking on the name of the project (YCP_Scheme) and choosing Export->General->Archive File.

Upload the zip file to the submission server as assign6. The URL of the server is

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

IMPORTANT: after uploading, you should download a copy of your submission and double-check it to make sure that it contains the correct files. You are responsible for making sure your submission is correct. You may receive a grade of 0 for an incorrectly submitted assignment.