CS 334
Programming Languages
Spring 2000

Assignment 7
Due Thursday, 4/18/2002


  1. Please do problem 10.1 on page 10-43. (If object-oriented languages do not solve all the problems, just say so for those problems.)

  2. Suppose class B extends class A (i.e., B is a subclass of A), and moreover B contains more instance variables than A. In Java, and most object-oriented languages, B is treated as a subtype of A. Thus an object from class B can be used in any context expecting a value of class A. In particular an object of class B can be assigned to a variable of class A.

    Because B has extra instance variables, it requires more space than A. This does not cause problems in Java because objects are held as implicit references. Thus each variable of object type contains a pointer to the actual instance variables (and indirectly) methods of the object. Thus variables of object types can always be allocated enough space to hold a pointer.

    In C++ objects are not implicit references. That is, a value of type B is not a pointer to space in the heap holding the data of the object. Instead that data is directly stored (on the stack, of course). The program in Figure 10.13 and the discussion on pages 10-27 and 10-28 of the text discuss what happens in this case versus when pointers to objects are used. (Though I thought the discussion was not as clear as it might have been.)

    Let's make the example a bit more compelling by supposing that A has a protected instance variable, x, and that B also has a protected instance variable y of type int (of course, it also inherits x). Suppose also that method q of class A prints out the value of x, while q in class B prints out the value of y.

    #include <iostream.h>
    
    class A{
    public:
      int x;
    
      A() {x = 1;}
    
      void p()
      { cout << "A::p\n" ; }
    
      virtual void q()
      { cout << "A::q\n" << x << endl; }
    
      void f() 
      { p(); q(); }
    };
    
    class B : public A
    {
    public:
      int y;
    
      B(){y = 2;}
      void p()
      { cout << "B::p\n"; }
    
      void q()
      { cout << "B::q\n" << y << endl; }
    };
    
    int main()
    { A a;
     B b;
     a.f();
     b.f();
     a = b;
     a.f();
    }
        

    Please explain what would now happen in line (30) of Figure 10.13 when a = b is executed. (In particular, discuss what happens to the instance variables in the object held in b.) What would be printed out as the output from the program. Please explain why these results obtain.

    You are welcome to run the program under a C++ compiler (g++ on our UNIX boxes) to see what happens. To run the program after it has been compiled, just type "a.out" (presuming you didn't assign the compiler output a different name).

  3. Please do problem 10.11 on page 10-44.

  4. Please do problem 10.19 on page 10-44.

  5. I know you would feel cheated if we went a week without writing interpreters for PCF, so we'll start writing an interpreter for PCF in Java this week. Next week you'll actually finish it up. To make life simple I'll have you emulate what we did in ML as closely as possible. The main difference is that the datatype definition (e.g., of term) will turn into an interface, while each of the cases of the datatype definition will turn into a class which implements that interface.

    I would like you to write a series of classes to represent the terms of PCF. These will be represented very much like the type term in your environment interpreter for PCF written in ML. Thus each term will represent an abstract syntax tree in Java. Each tag in the term datatype definition in ML will be represented by a distinct class in Java. If the tag comes with components (e.g. AST_IF has three term components), then it will have that number of instance variables with the corresponding types (e.g., class IfExp will have 3 instance variables, and its constructor will take three parameters, each of which represents a PCF expression).

    The datatype term will be represented by the following interface:

       public interface PCFExpression{
          String getStringRep();
          /** Return a printable representation of the term in the object */
       }
       

    Each of the cases of the definition of the datatype term will be represented by a class which implements PCFExpression. Thus there will be classes named IDExp, NumExp,..., FunExp, and AppExp. When a getStringRep() message is sent to one of these expressions, it should return a string representing the original expression.

    For example, suppose we wish to build an object corresponding to the PCF term: if (isZero 3) then 1 else 2. A program to build this might look like:

       PCFExpression three = new NumExp(3);
       PCFExpression isZ = new IsZeroExp();
       PCFExpression cond = new AppExp(isZ,three);
       PCFExpression trueExp = new NumExp(1);
       PCFExpression falseExp = new NumExp(2);
       PCFExpression theTerm = new IFExp(cond,trueExp,falseExp);
       System.out.println(theTerm.getStringRep());
    

    This program would build a representation of the "if" expression. The string printed at the end would look pretty much like the original expression (though it might have extra parentheses).

    The key to all of this is dynamic method invocation. Thus class AppExp will have two instance variables, each of type PCFExpression. At run-time, you have no idea what classes generated the objects held there. However, if you send a getStringRep message to each they should return the appropriate strings representing the expressions.

    Be sure to notice the differences in the organization of your Java-based interpreter (actually just a pretty-printer, but next week ...) and the ML interpreter. The organizational differences between functional and object-oriented approaches are quite important, even though the individual code snippets are not very different.

    In next week's assignment, you will complete this by adding a method called evaluate to the interface and to each of the classes. (To do this you will also need to write classes corresponding to each of the cases of the datatype value of the ML interpreter.) For this week just think about representing the expressions and printing them out.

    As you can see from the program fragment above, entering a PCF expression is very painful. For extra credit (and the eternal gratitude of your classmates!), write a parser for PCF expressions that builds your abstract syntax trees. To see how to do this, look at the parser.sml file that was used with your ML-based interpreters.