JVM: Class files
Center for ST
%TOC% %RED%Important:%ENDCOLOR% 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: * [[http://losser.labs.cs.uu.nl/~mbravenb/pt5-base.tar.gz][pt5-base.tar.gz]] ---++ 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.