Homework 5 Solutions


    1. 30: The value of the global k is printed, because it is the most closely enclosing block in lexical order.
    2. 70: The value of k associated with adder gets printed, because adder is the first stack frame encountered when searching the stack for the value of k.
    1. In the following symbol table, the bracketed numbers in the "other" field (e.g., [2] in the "other" field for handleEvent) refer to the numbered notes below the diagram. Please also note that I forgot to provide a hash index for "void", which is also a type. I also did not provide a hash index for "true". However, "true" is not a name but a pre-defined constant, just like a number, such as 5, is a pre-defined constant. Here is the symbol table:

      What appears in the "other" fields is not something that was graded, since you really would not know what should go there until you wrote the compiler. However, in the notes below I've provided some examples of what might appear:

      1. Classes like string, outputstream, Button, or HandleButtonSelection would minimally have a superclass pointer, the scope id that it creates, a list of pointers to instance variables, a list of pointers to methods, and a list of pointers to nested classes
      2. A method, such as handleEvent, or a constructor, such as Button, would minimally have a list of pointers to parameters and an indication of what access protection it has
      3. An instance variable like label would have an indication of what access protection it has (in this case it would be "protected")

      A couple other notes:

      1. An extern declaration in C/C++ is just like an import statement in Java or Modula. In this case the "other" field records the fact that the declaration is an extern so that edit_mode can be placed in the "import" table that is output with the .o file so that the linker can resolve the external reference.
      2. The list of scope ids that were defined and the names that created them are as follows:
        • 1: globals
        • 2: outputstream
        • 3: string
        • 4: Button class
        • 5: HandleButtonSelection class
        • 6: handleEvent method
        • 7: Button constructor
    2. In the following scope stack I have only included the scope id and the name that created that scope id. The book also includes fields for whether or not the scope is "closed" (i.e., whether or not the compiler can "peer" outside this scope or whether the scope is a black box that prevents the compiler from looking outside of it) and an "other" field for additional information that might be needed. There are no "closed" scopes in this example and you really would not know what "other" information the compiler requires until you started writing the compiler:

    3. Again my scope stack only shows the scope id and the name that created that scope id:

  1. One possible strategy that mimics the "with" statement approach is to push a scope record for worker onto the scope stack when employee is referenced, and then push a scope record for addr onto the scope stack when address is referenced. The top of the scope stack might look like:
    ScopeClose?Other
    addryesstruct
    workeryesstruct
    ... remainder of scope stack ...
    The scope field would actually contain the scope id's assigned to addr and worker. If the compiler uses this scheme, then when the "address" field is encountered, the scope record for "worker" will already be on the scope stack, and hence the "address" entry for "worker" will be selected from the symbol table. Similarly, when the "street" field is encountered, the scope record for "addr" will already be on the scope stack, and hence the "street" entry for "addr" will be selected from the symbol table.

    Note that I've indicated that the scope is closed for both "worker" and "addr". The reason is that in a struct reference, such as we have here, if the field is not declared in the immediately enclosing struct, then it is an error. For example, if the reference were employee->address->name, the compiler should raise an error, because name is not declared in the "addr" struct. By stating that the scope is "closed", the compiler will not be able to probe deeper into the stack for name, and it will be forced to raise an error.

  2. Scope records for BoxObject and GraphicalObject need to be pushed onto the scope stack when the draw method for Rectangle is being processed, so that the instance variables and methods defined by BoxObject and GraphicalObject are accessible. The order in which they are pushed is important--the superclasses higher in the chain are less closely nested, in a lexical sense, and hence should be lower in the stack. For example the scope records for our example would look as follows:
    Rectangle
    BoxObject
    GraphicalObject
    ... Remainder of Scope Stack ...
    
    In order to make it possible to create these scope records, we need to be able to follow the super class chain for Rectangle. This suggests that the "other" field for a class must minimally maintain:
    1. a pointer to the super class, if any
    2. the scope id created by the class (i.e., the scope id counter was incremented when the compiler started processing this class and we need to store this incremented id--remember that the id stored in the scope field is the id of the scope in which the class was declared, not the scope id that it creates)
    3. a list of instance variable declarations
    4. a list of method declarations
    For the purposes of this problem, the only other fields that matter are the super class field and scope defined (created) by the class. The compiler would recursively follow the chain and would push a scope record for a class only after the compiler has returned from its recursive traversal of the chain. This "postfix" recursion will ensure that the topmost superclasses end up deeper in the stack.