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.