Assignment Interpretation

Pt

Important: Again, update your installations, since new features of Dryad will be necessary for this assignment.

Introduction

In this assignment, you will create an interpreter for a subset of Java. The main goal of this assignment is to learn more about binding, scopes, and dynamic rules. Please note that the goal of this assignment is not to create a super duper Java interpreter that supports every single language feature. Elegance is what matters!

No separate exercises. The assignment is separated in a few parts to help you a bit with incrementally developing your interpreter. However, later in the assignments you will need to update the strategies you developed earlier, so these parts are not separate exercises.

Again, we provide templates:

Testing

For this exercise, testing is very important. If you extend your interpreter with a new feature, then it helps a lot if you can automatically check if all your previous work still behaves as expected.

To help you a bit, we have developed a template testsuite. This template includes some sunit abstraction that makes it easier to do massive testing of your interpreter. As an example, we have included some basic tests. Of course, you should extend the testsuite with more complex and basic test programs.

If you don't know the sunit testing library yet, then it is a good idea to read that chapter in the Stratego/XT manual.

1. Evaluation of Constant Expressions

First, assume that the body of the main method is a single expression. Implement the strategy eval-exp in the module eval/Expressions that evaluates an expression. Separate the evaluation strategy from the evaluation rules, which you can define in the module eval/Rules. See the TIL interpreter in the Stratego/XT manual and the slides of the first dynamic rules lecture to get a basic idea about how to write such an evaluator.

Support a useful set of relational and binary operators applied to literals. For now, do not support assignments, method invocations, field access, constructor invocations etc. Don't go wild by implementing every single operator that is available in Java. Just extend your set of evaluation rules later if you find operators that are useful for testing.

Note that these Java programs are actually not syntactically valid, since Java does not allow arbitrary expressions as statements. We ignore this issue.

2. Simple Statements, Assignments and Local Variables

Next, replace the stub eval-stat in the module eval/Statements with a strategy for evaluating simple statements. For now, do not support control-flow statements, but restrict yourself to blocks, variable declarations, and expression statements. Handle primitive types only, so don't support the array declarators in variable declarations.

Also, extend the strategy eval-exp to evaluate assignments to local variables. Use a dynamic rule to propagate values of local variables. Use dynamic rule scope to restrict the propagation rule to parts of the program where the variable is indeed in scope. Use scope labels to make sure that the propagation rules are defined in the right scope. This is tested in the template testsuite, so you will soon find out these issues.

3. Artificial Primitives

For testing, it is useful if your Java program can print something. Unfortunately, System.out.println is quite complex to interpret, since you will need to handle bytecode and native code for that. This is way beyond this exercise, so we treat this as a primitive of the Java language. Add an evaluation rule that evaluates this primitive.

4. Control Flow Statements

Next, extend the strategy eval-stat to support control-flow statements. Support at least if/then, if/then/else and while. For now, do not support break and continue. Again, use separate evaluation rules for the if statements. Use a dynamic rule for the while statement (again, see the TIL interpreter, but make sure that you really get it).

5. Static Fields

Extend the interpreter to handle assignments and access of static fields. You can assume that the qualifier of a static field, if present, is always a TypeName. Similar to local variables, store the value of the fields in dynamic rules.

6. Static Method Invocations

Extend the interpreter to handle invocations of static methods. Ignore instance method invocations for now. The basic idea is to lookup the method declaration, define the method parameters, and evaluate the body of the method.

Qualifier. Similar to static fields, assume that the qualifier of the invocation is a TypeName, if present. So, you can ignore static method invocations that are actually applied to objects (which is bad style anyway).

Parameters. Use dynamic rules for the binding of formal to actual method parameters. Make sure that you restrict the scope of these bindings to the evaluation of the method body.

Return. Return statements are a bit tricky to handle, since they can occur in arbitrary places in the program. For now, assume that the return statement is always the last statement in a method body. With this assumption, it is safe to use a dynamic rule to pass the return value from an evaluation rule for return statements, to the rule that evaluates a method invocation. Note that a violation of this assumption can produce the wrong result for a method invocation.

7. Creating Objects

Next, extend the interpreter to support the creation of new objects by constructor invocations. The idea is to create a new Ref for every object. The Ref constructor has one argument, which should be a unique identifier for the object. In Stratego, you can construct unique identifiers using the new or newname strategies.

Of course, you need some information for invoking the constructor. Constructor invocations (NewInstance) contain the type of the type of the class that is instantiated (second argument) and are annotated with a Declaration attribute, similar to the CompileTimeDeclaration attribute of methods. You can use this attribute to get the constructor declaration that is to be invoked. Invoke the body of the constructor in a way similar to the invocation of static methods.

Ignore super and this constructor calls, since Dryad does currently not provide sufficient semantic information about these calls. Hence, they are difficult to implement in the interpreter.

8. Instance Fields

Now we can create objects, extend the support for fields to instance fields. Make sure that you save the value of an instance variable for a specific object reference. Also, add support for this in constructors and check field access and assignment of instance fields can be qualified with this. This in a constructor should evaluate to the reference that is being constructed.

9. Instance Method Invocations

Finally, add basic support for instance method invocations. We assume that the method that is invoked at runtime is equal to the compile-time declaration. In other words, we ignore method overriding.

For methods, you can handle this in two different ways. First, you can define a special dynamic rule at the call-site for evaluating This() to the reference to which the method was applied. Second, you could extend the list of parameters and make this just another variable.

Try to avoid duplicating larger fragments of complex code between the evaluation of constructors, static methods, and instance methods.