Eli   Documents

General Information

 o Eli: Translator Construction Made Easy
 o Global Index
 o Frequently Asked Questions
 o Typical Eli Usage Errors

Tutorials

 o Quick Reference Card
 o Guide For new Eli Users
 o Release Notes of Eli
 o Tutorial on Name Analysis
 o Tutorial on Scope Graphs
 o Tutorial on Type Analysis
 o Typical Eli Usage Errors

Reference Manuals

 o User Interface
 o Eli products and parameters
 o LIDO Reference Manual
 o Typical Eli Usage Errors

Libraries

 o Eli library routines
 o Specification Module Library

Translation Tasks

 o Lexical analysis specification
 o Syntactic Analysis Manual
 o Computation in Trees

Tools

 o LIGA Control Language
 o Debugging Information for LIDO
 o Graphical ORder TOol

 o FunnelWeb User's Manual

 o Pattern-based Text Generator
 o Property Definition Language
 o Operator Identification Language
 o Tree Grammar Specification Language
 o Command Line Processing
 o COLA Options Reference Manual

 o Generating Unparsing Code

 o Monitoring a Processor's Execution

Administration

 o System Administration Guide

Mail Home

Tutorial for Name Analysis Using ScopeGraphs

Previous Chapter Next Chapter Table of Contents


Libraries

It's rare that a program is developed by one person, without reference to the work of others. Usually a programmer relies on a number of libraries for things like common computations (e.g. sqrt) and input/output operations. Name analysis must have access to information from libraries in order to establish bindings, but library code resides in separate files that are not physically part of the user's program.

There are many strategies for linking with library files, but all must support the basic name analysis concepts. For the purposes of this tutorial, we will assume that the processor input is a concatenation of source files. That concatenation will begin with the program file, followed by any number of library files. Eli provides mechanisms for collecting source files into a single input, but these are beyond the scope of this tutorial (see Insert a File into the Input Stream of Tasks related to input processing).

Without use of additional facilities, a generated processor named proc could be invoked on a sequence of files as follows:

cat pgmfile libfile libfile | ./proc
In the remainder of this tutorial, any examples illustrating libraries will be single files that are the result of a concatenation.

We will extend NameLan by defining each library file as an entity called a package. File `pkg.nl' illustrates this extension:

pkg.nl[49]==

{ float rate;
  rate = verbs.fly.distance / insects.fly.legs;
}

package insects;
class ant { }
class fly { int legs; }
class bee { }

package verbs;
class run { }
class fly { int distance; }
This macro is attached to a non-product file.

Our original grammar's root symbol was Program, which now describes only the first part of a larger construct. To describe this larger construct, we need to extend the NameLan concrete grammar above Program. In the process, we create a new root called Collection:

Phrase structure[50]==

Collection:     Pgmfile Libfile*.
Pgmfile:        Program.

Libfile:        'package' Ident ';' PackageBody.
PackageBody:    Declaration*.
This macro is defined in definitions 2, 25, 30, 32, 41, 50, 55,
   67, 76, 97, 118, and 124.
This macro is invoked in definition 3.

We need a new abstract syntax symbol to distinguish the new context for identifiers (see Representation of identifiers of Name Analysis Reference Manual). The obvious choice is PackageDefName:

Abstract syntax of identifiers[51]==

RULE: Libfile        ::= 'package' PackageDefName ';' PackageBody END;
RULE: PackageDefName ::= Ident                                    END;
This macro is defined in definitions 8, 9, 26, 31, 33, 42, 51, and 120.
This macro is invoked in definition 10.

Here are our new and modified scope rules for NameLan with packages:

  • The scope of a defining occurrence VarDefName is the smallest enclosing Block, MethodBody, ClassBody, Program, or PackageBody.

  • The scope of a defining occurrence MethodDefName is the smallest enclosing ClassBody, Program, or PackageBody.

  • The scope of a defining occurrence ClassDefName is the smallest enclosing ClassBody, Program, or PackageBody.

  • The scope of a defining occurrence PackageDefName is the enclosing Collection.

  • The members of a Package are the bindings having the PackageBody as a scope.

Since Program is no longer the grammar root, it does not automatically inherit any role and must explicitly inherit RangeScope:

Abstract syntax tree[52]==

SYMBOL Program INHERITS RangeScope END;
This macro is defined in definitions 10, 18, 22, 38, 47, 52, 53,
   65, 69, 71, 88, 89, 92, 93, 95, 96, 101, 105, 108, 110, 111,
   112, 113, 122, 133, 134, 135, 136, 137, 144, and 153.
This macro is invoked in definition 11.

The LIDO implementation of the remaining roles is left as an exercise.

The ownership relation between a package and its members is established by overriding the default computation for the PackageBody.ScopeKey attribute (see The RangeScope role of Name Analysis Reference Manual). Recall that PackageDefName.Key represents the package entity:

Abstract syntax tree[53]==

RULE: Libfile ::= 'package' PackageDefName ';' PackageBody COMPUTE
  Libfile.Key = PackageDefName.Key;
END;

SYMBOL PackageBody COMPUTE INH.ScopeKey = INCLUDING Libfile.Key; END;
This macro is defined in definitions 10, 18, 22, 38, 47, 52, 53,
   65, 69, 71, 88, 89, 92, 93, 95, 96, 101, 105, 108, 110, 111,
   112, 113, 122, 133, 134, 135, 136, 137, 144, and 153.
This macro is invoked in definition 11.

Exercises

These exercises are based on files defined in the Tutorial. To obtain copies of those files in your current directory, enter Eli and give the following command:

-> $elipkg/Name/LearnSG%Pack > .
None of these files will have write permission in your current directory.

  1. Consider the LIDO specifications necessary to implement the scope rules for NameLan with packages. Briefly explain why these scope rules do not require any changes in earlier specifications.

  2. Create a specification that uses LIDO inheritance to establish the roles implied by the scope rules for PackageDefName and PackageBody, and ensure that the file containing it is named in `Solutions.specs'.

  3. Use `Bindings.specs' to generate a processor named pk that will show bindings for applied occurrences. Apply `pk' to `gambler.nl' and `pkg.nl'. Do you get the output you expected? Explain briefly.

Single import

A single import construct makes it possible to refer to a single entity defined in some package by a simple name rather than a qualified name. For example, consider file `single.nl':

single.nl[54]==

import P.D.m;
import Q.C.a;
{ a = 42; m(); }

package Q;
import P.D;
class C extends D {
  int a;
  void m() {
    a = b;
  }
}

package P;
class D {
  int b;
  void m() {
    b = Q.C.a;
  }
}
This macro is attached to a non-product file.

Single imports are used here to allow the program to refer to variable a of class Q.C and method m of class P.D by simple names, and to allow package Q to refer to the class P.D by a simple name.

In order to implement single imports, we need to think about how a single import interacts with the entities defined in the program or package. The nature of these interactions is specified by the language designer, and there are several possibilities. We will describe the decision that we made for NameLan, and show how that decision leads to a phrase structure. Other decisions would lead to other structures.

As language designers, we consider imports to be a convenience for the programmer that should not have any effect beyond the obvious one illustrated by `single.nl'. We therefore do not want the scope of an imported binding to be the Program or a PackageBody. For example, if the scope of the imported binding for D were the PackageBody of Q then that binding would be available for another package to import from Q. That is an effect beyond the obvious one.

If the scope of an imported binding were a phrase that encompassed the program or package body, then that binding could be referred to by a simple name within the program or package body unless it was hidden by a local defining occurrence. Although the PgmFile (resp. LibFile) encompasses the program (resp. package body), those phrases also encompass the import declarations. If we chose those phrases as scopes for the imported bindings, import declarations could interfere with one another. That is another effect beyond the obvious one.

We can avoid these undesired effects if we establish a structure for a program containing import declarations that has an additional phrase encompassing the file body, but not the import declarations. The desired structure can be defined by adding six productions to the NameLan grammar:

Phrase structure[55]==

Pgmfile:        ImportDecls PgmFileBody.
PgmFileBody:    Program.

Libfile:        'package' Ident ';' ImportDecls LibFileBody.
LibFileBody:    PackageBody.

ImportDecls:    ImportDecl+.
ImportDecl:     'import' WLName ';'.
This macro is defined in definitions 2, 25, 30, 32, 41, 50, 55,
   67, 76, 97, 118, and 124.
This macro is invoked in definition 3.

The first and second productions introduce a new phrase PgmFileBody that encompasses the normal Program phrase, and ensure that PgmFileBody is derived only if the PgmFile contains import declarations. If a PgmFile contains no import declarations, that PgmFile is derived directly to Program by the production defined earlier. The third and fourth productions define the corresponding structure for a Libfile.

To see how this grammar addition has the desired effect, consider `single.nl'. Pgmfile contains import declarations, and therefore the grammar derives a PgmFileBody phrase encompassing the file's Program phrase. Similarly, the first Libfile contains an import declaration and a LibFileBody phrase encompassing its PackageBody phrase is derived. On the other hand, the second Libfile has no import declarations. This means that none of the added rules applies, and thus there is no additional phrase.

Notice that we have chosen to derive the name of the imported entity from WLName. Recall that this phrase is used when there is a potential ordering conflict in analyzing names (see Inheritance). In `single.nl', the path edge between class C and class D modeling the inheritance relation depends on the operand P.D of the import declaration in package Q. Since path edges might depend on single imports, the imported name must be processed by the worklist algorithm. We will use the rather generic "ImportName" to characterize the imported entity because any declared entity might be imported:

Make contexts of complete names explicit[56]==

RULE: ImportDecl ::= 'import' ImportName ';'    END;
RULE: ImportName ::= WLName                     END;
This macro is defined in definitions 37, 44, 56, 57, 68, 77, and 98.
This macro is invoked in definition 38.

There is also a new context for PackageDefName:

Make contexts of complete names explicit[57]==

RULE: Libfile    ::=
        'package' PackageDefName ';' ImportDecls LibFileBody
COMPUTE
  Libfile.Key = PackageDefName.Key;
END;
This macro is defined in definitions 37, 44, 56, 57, 68, 77, and 98.
This macro is invoked in definition 38.

Here are the corresponding scope rules:

  • Suppose that an ImportDecl in the Pgmfile encloses an ImportName identifying a binding `(i,k)'. The binding `(i,k)' also has the PgmFileBody as a scope.

  • Suppose that an ImportDecl in a Libfile encloses an ImportName identifying a binding `(i,k)'. The binding `(i,k)' also has the LibFileBody as a scope.
These scope rules imply the following roles:

Inherit the appropriate roles[58]==

SYMBOL PgmFileBody INHERITS RangeScope END;
SYMBOL LibFileBody INHERITS RangeScope END;
This macro is defined in definitions 58 and 59.
This macro is invoked in definition 65.

An ImportName is an applied occurrence that establishes an additional scope for the binding it identifies. The WLInsertDef role supports this behavior (see Worklist search of Name Analysis Reference Manual).

Inherit the appropriate roles[59]==

SYMBOL ImportName INHERITS WLInsertDef END;
This macro is defined in definitions 58 and 59.
This macro is invoked in definition 65.

WLInsertDef provides the standard attributes of an applied occurrence:

Sym
is an integer-valued attribute specifying the identifier. This synthesized attribute is set by a module computation to the value derived from the identifier (see Tree Structure).

UseKey
is a DefTableKey-valued attribute characterizing the applied occurrence. This synthesized attribute is set by a module computation.

Key
is a DefTableKey-valued attribute representing the key of the corresponding defining occurrence. This attribute is set by a module computation. Key is a postcondition of the search.

Key is set to the value NoKey if the computation has been unable to find a suitable defining occurrence.

WLInsertDef adds the following attributes to those for the standard applied occurrence:

DependsOn
is an FPItemPtr-valued attribute that represents the computation yielding the existing binding. This attribute must be set by a developer computation.

Scope
is a NodeTuplePtr-valued attribute representing the additional scope for the identified binding. This inherited attribute is set by a module computation to INCLUDING AnyScope.Env.

We must write computations to set the values of four of these five WLInsertDef attributes in the context of an ImportName. The first three are synthesized attributes, the fourth is inherited:

Set WLInsertDef attributes[60]==

RULE: ImportName ::= WLName COMPUTE
  ImportName.Sym = WLName.Sym;
  ImportName.UseKey = WLName.UseKey;
  ImportName.DependsOn = WLName.FPItem;
END;

SYMBOL ImportName COMPUTE
  INH.Scope =
    INCLUDING (Pgmfile.ImportEnv, Libfile.ImportEnv);
END;
This macro is defined in definitions 60, 61, 62, and 63.
This macro is invoked in definition 65.

The Sym and UseKey values must be propagated to WLName from its component identifiers; this was done earlier for WLName.FPItem (see Inheritance).

Set WLInsertDef attributes[61]==

SYMBOL WLName: UseKey: DefTableKey;

RULE: WLName ::= SimpleWLName COMPUTE
  WLName.Sym = SimpleWLName.Sym;
  WLName.UseKey = SimpleWLName.UseKey;
END;

RULE: WLName ::= WLName '.' QualifiedWLId COMPUTE
  WLName[1].Sym = QualifiedWLId.Sym;
  WLName[1].UseKey = QualifiedWLId.UseKey;
END;
This macro is defined in definitions 60, 61, 62, and 63.
This macro is invoked in definition 65.

In order to obtain a value for ImportName.Scope in every context, we need to establish values for Pgmfile.ImportEnv and Libfile.ImportEnv. The simplest case is the one in which there are no import declarations. If there are no import declarations there will be no ImportName nodes, and therefore the value of Pgmfile.ImportEnv or Libfile.ImportEnv is not used and should represent no range:

Set WLInsertDef attributes[62]==

ATTR ImportEnv: NodeTuplePtr;

RULE: Pgmfile ::= Program COMPUTE
  Pgmfile.ImportEnv = NoNodeTuple;
END;

RULE: Libfile ::= 'package' PackageDefName ';' PackageBody
COMPUTE
  Libfile.ImportEnv = NoNodeTuple;
END;
This macro is defined in definitions 60, 61, 62, and 63.
This macro is invoked in definition 65.

If import declarations are present, the additional scope for those bindings is the associated PgmFileBody or LibFileBody phrase:

Set WLInsertDef attributes[63]==

RULE: Pgmfile ::= ImportDecls PgmFileBody COMPUTE
  Pgmfile.ImportEnv = PgmFileBody.Env;
END;

RULE: Libfile ::=
        'package' PackageDefName ';' ImportDecls LibFileBody
COMPUTE
  Libfile.ImportEnv = LibFileBody.Env;
END;
This macro is defined in definitions 60, 61, 62, and 63.
This macro is invoked in definition 65.

The intent of the statement import `q.i'; is to make it possible to refer to the entity named by `q.i' by the simple name `i'. Suppose that a user added the following line at the beginning of `single.nl':

import Q.C.m;
The result is a contradiction, allowing two distinct entities to have the same simple name m, and an error must be reported (see Source Text Coordinates and Error Reporting of The Eli Library). When a single import collides with another one, the value of its ImportName.Key attribute differs from the value of the WLName.Key attribute (see Worklist search of Name Analysis Reference Manual). Again, WLName.Key is the Key attribute of the rightmost applied occurrence:

Report a collision error in a single import[64]==

RULE: ImportName ::= WLName COMPUTE
  IF(NE(ImportName.Key, WLName.Key),
    message(
      ERROR,
      CatStrInd("Ambiguous import: ", WLName.Sym),
      0,
      COORDREF));
END;
This macro is invoked in definition 65.

Attaching the LIDO specifications to the abstract syntax tree:

Abstract syntax tree[65]==

Inherit the appropriate roles[58]
Set WLInsertDef attributes[60]
Report a collision error in a single import[64]
This macro is defined in definitions 10, 18, 22, 38, 47, 52, 53,
   65, 69, 71, 88, 89, 92, 93, 95, 96, 101, 105, 108, 110, 111,
   112, 113, 122, 133, 134, 135, 136, 137, 144, and 153.
This macro is invoked in definition 11.

Exercises

These exercises are based on files defined in the Tutorial. To obtain copies of those files in your current directory, enter Eli and give the following command:

-> $elipkg/Name/LearnSG%Single > .
None of these files will have write permission in your current directory. You will need to add write permission in order to do the exercises.

  1. Draw the scope graph for `single.nl', and label the path edges. Suppose that the name analysis were to be done strictly from left to right, without using a worklist algorithm. Briefly explain why this analysis would fail.

  2. Use `Bindings.specs' to generate a processor named pk that will show bindings for applied occurrences.

    1. Apply pk to `single.nl' and verify that the result is correct.

    2. Change the body of the program in `single.nl' to:

      { float a; a = 42; m(); }
      

      Apply pk to the modified file. Explain why float a hides the import rather than colliding with it (for a hint, see Basic Scope Rules of Name analysis according to scope rules).

  3. Modify `single.nl' by changing the name of the class C in the package Q to E. Apply pk to the modified file. Is the output what you expected? Do you think that the error report could be improved? Explain briefly.

Import on demand

An import on demand is used to make it possible to refer to every entity declared in a package (or a class) by a simple name rather a qualified name. Here is an example:

demand.nl[66]==

import insects.*;
{ float rate;
  rate = verbs.fly.distance / fly.legs;
}

package insects;
class ant { }
class fly { int legs; }
class bee { }

package verbs;
class run { }
class fly { int distance; }
This macro is attached to a non-product file.

The import declaration in `demand.nl' makes all of the members of the package insects (ant, fly, and bee) accessible via simple names in the program code. The user takes advantage of this effect in the expression's denominator to use a simple name for the fly class (compare `demand.nl' with `pkg.nl').

Here is the addition to the NameLan concrete syntax that supports import on demand:

Phrase structure[67]==

ImportDecl: 'import' WLName '.' '*' ';'.
This macro is defined in definitions 2, 25, 30, 32, 41, 50, 55,
   67, 76, 97, 118, and 124.
This macro is invoked in definition 3.

We shall see that this declaration adds a path edge to the scope graph, and therefore its applied occurrence must be sought using the worklist algorithm.

A new abstract syntax symbol is needed to represent the occurrence of a WLName in the context of an ImportDecl. Since the operand of import might refer to either a package or a class, we'll use PCName:

Make contexts of complete names explicit[68]==

RULE: ImportDecl ::= 'import' PCName '.' '*' ';' END;
RULE: PCName     ::= WLName                      END;
This macro is defined in definitions 37, 44, 56, 57, 68, 77, and 98.
This macro is invoked in definition 38.

Our scope rules governing NameLan's import on demand construct are:

  • Suppose that an ImportDecl in the Pgmfile encloses a PCName `p'. Any binding that is a member of `p' also has the PgmFileBody as a scope, unless a single import of that binding's identifier has the PgmFileBody as a scope.

  • Suppose that an ImportDecl in a Libfile encloses a PCName `p'. Any binding that is a member of `p' also has the LibFileBody as a scope, unless a single import of that binding's identifier has the LibFileBody as a scope.

  • No binding having the PgmFileBody or a LibFileBody as a scope hides any other binding.

The intent of the statement import `q.*'; is to make it possible to refer to some of the members of `q' by simple names rather than qualified names. Suppose that the user added a statement import verbs.*; to `demand.nl'. In that case, both insects.fly and verbs.fly could be referred to by the simple name fly and that reference would be ambiguous. No error should be reported, unless that simple name is actually used (as it is in the third line of `demand.nl').

Each import on demand can be modeled by a path edge whose tail is the scope graph node created for the PgmFileBody or LibFileBody in which the import on demand occurs, and whose tip is the scope graph node owned by the entity being imported (see Scope graphs of Name Analysis Reference Manual). The generic lookup algorithm will then implement the import rule by following that path edge (see The generic lookup of Name Analysis Reference Manual).

The semantics of a path edge modeling an import on demand differ from those of a path edge modeling inheritance. We therefore need to use a second edge label for import edges (see Scope graphs of Name Analysis Reference Manual). Eli allows us to use the default edge label 1 without comment, and we have taken advantage of this avoid specifying edge labels. The result is that Eli has silently used the label 1 to indicate an inheritance edge. We will use the label 2 to indicate an import edge.

Adding an import path edge to the scope graph is similar to adding an inheritance path edge (see Inheritance). The only real difference is that we must specify the label attribute because it is not the default value 1:

Abstract syntax tree[69]==

SYMBOL PCName INHERITS WLCreateEdge COMPUTE
  SYNT.tailEnv = INCLUDING AnyScope.Env;
  SYNT.label = 2;
END;

RULE: PCName ::= WLName COMPUTE
  PCName.tipFPItem = WLName.FPItem;
END;
This macro is defined in definitions 10, 18, 22, 38, 47, 52, 53,
   65, 69, 71, 88, 89, 92, 93, 95, 96, 101, 105, 108, 110, 111,
   112, 113, 122, 133, 134, 135, 136, 137, 144, and 153.
This macro is invoked in definition 11.

Import edges do not address the scope rule that prohibits hiding. Consider the following program:

hide.nl[70]==

import hide.*;
{ float rate;
  rate = verbs.fly.distance / insects.fly.legs;
}

package insects;
class ant { }
class fly { int legs; }
class bee { }

package verbs;
class run { }
class fly { int distance; }

package hide;
class insects { }
class verbs { }
This macro is attached to a non-product file.

When the applied occurrences verbs and insects in the assignment statement are analyzed, bindings are first sought in the scope graph node created for the enclosing Block, then in the node created for the enclosing Program, and finally in the node created for the enclosing PgmFileBody. The scope graph node created for that range is the tail of an import edge whose tip is the scope graph node owned by the hide package. Both verbs and insects are defined in the hide package, so the entities bound to those identifiers will be returned by the generic lookup.

If there had been no import on demand, bindings for verbs and insects would have ultimately been sought (and found) in the scope graph node for Collection. Thus the import on demand has hidden the defining occurrences in Collection, violating the prohibition on hiding.

We can solve the problem by creating an edge from each scope graph node created for a Program or PackageBody to the scope graph node created for the Collection. The semantics of this bypass edge differ from those of inheritance and import edges, and we'll assign it the index 3. Because path edges are followed before parent edges, the search will bypass the scope graph node created for PgmFileBody and find the package bindings for verbs and insects. In other words, the bypass edge will implement the prohibition on hiding.

Neither the tips nor the tails of these bypass edges are defined by names. Therefore the BoundEdge role is appropriate (see Path edge creation roles of Name Analysis Reference Manual).

Abstract syntax tree[71]==

SYMBOL Toplevel INHERITS BoundEdge COMPUTE
  SYNT.tailEnv = THIS.Env;
  SYNT.tipEnv = INCLUDING Collection.Env;
  SYNT.label = 3;
END;

SYMBOL Program     INHERITS Toplevel END;
SYMBOL PackageBody INHERITS Toplevel END;
This macro is defined in definitions 10, 18, 22, 38, 47, 52, 53,
   65, 69, 71, 88, 89, 92, 93, 95, 96, 101, 105, 108, 110, 111,
   112, 113, 122, 133, 134, 135, 136, 137, 144, and 153.
This macro is invoked in definition 11.

Because we now have more than one edge label, we must define MaxKindsPathEdge in a file named `ScopeGraphs.h' (see Scope graphs of Name Analysis Reference Manual).

ScopeGraphs.h content[72]==

#define MaxKindsPathEdge 3
This macro is defined in definitions 72, 107, and 109.
This macro is invoked in definition 73.

We must protect against `ScopeGraphs.h' being included more than once in some C compilation. Conventionally, Eli uses the name of the include file, in capital letters with dots replaced by underscores, as the controlling symbol:

ScopeGraphs.h[73]==

#ifndef SCOPEGRAPHS_H
#define SCOPEGRAPHS_H
ScopeGraphs.h content[72]
#endif
This macro is attached to a product file.

We must add `ScopeGraphs.h' to the set of specification files:

Specification files[74]==

ScopeGraphs.h
This macro is defined in definitions 12, 16, 23, 27, 74, 78, 81,
   91, 117, 121, 139, 142, 146, and 152.
This macro is invoked in definition 13.

Exercises

These exercises are based on files defined in the Tutorial. To obtain copies of those files in your current directory, enter Eli and give the following command:

-> $elipkg/Name/LearnSG%Demand > .

None of these files will have write permission in your current directory. You will need to add write permission in order to do the exercises.

  1. Draw the scope graph for `hide.nl'. Identify the path edges introduced by the computations in this section.

  2. Use `Bindings.specs' to generate a processor named `pk' that will show bindings for applied occurrences. Apply pk to both `demand.nl' and `hide.nl', and verify that the analysis is correct.

  3. Add the following line as the first line of `demand.nl', and apply pk to the modified file:

    import verbs.*;
    

    1. Is the output what you expected? See Deciding among possible bindings of Name Analysis Reference Manual, for an explanation.

    2. Change the denominator of the assignment to insects.fly.legs and apply pk to the changed file. Is the output what you expected?

    3. This example shows that introducing ambiguity through import on demand does not result in an error report unless the ambiguous name is actually used. Do you agree or disagree with this design decision? Explain briefly.

  4. Add a class noun to package hide and create a reference to noun in the program. Apply pk to the modified `hide.nl'.

    1. Did you get the result you expected?

    2. Explain how the generic lookup routine found the binding for noun.


Previous Chapter Next Chapter Table of Contents