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


Classes

A class is an entity that can encapsulate both storage and behavior. File `random.nl' contains an example:

random.nl[29]==

class Random {
  int state = 100001;

  float ran() {
    state = state * 125;
    state = state - (state / 2796203) * 2796203;
    return state / 2796203.0;
  }
}

{ float p, q;
  p = Random.ran();
  q = Random.ran();
}
This macro is attached to a non-product file.

The behavior encapsulated by the class Random is the pseudo-random number generator ran, and the storage state holds the internal state of that generator.

The general form of a NameLan class declaration is:

Phrase structure[30]==

Declaration:    ClassDecl.

ClassDecl:      'class' Ident Inheritance ClassBody.
Inheritance:    Default.
Default:                .
ClassBody:      '{' 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 an abstract syntax symbol to distinguish the new identifier context found in the ClassDecl (see Representation of identifiers of Name Analysis Reference Manual). That identifier is the class name, so ClassDefName is a reasonable symbol for the context:

Abstract syntax of identifiers[31]==

RULE: ClassDecl    ::= 'class' ClassDefName Inheritance ClassBody END;
RULE: ClassDefName ::= 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 classes:

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

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

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

The LIDO computations associated with these scope rules are similar to the ones associated with methods, and we leave them to the reader as an exercise.

Classes introduce two new facilities, both of which affect name analysis:

  • Qualified names may be used to access the class members.

  • Classes may inherit members from other classes.
We will discuss these aspects in the next two sections.

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%Class > .

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. Consider the LIDO specifications necessary to implement the scope rules for classes.

    1. Briefly explain why these scope rules do not require any changes in earlier specifications.

    2. Create a specification that uses LIDO inheritance to implement name analysis for classes, and ensure that the file containing it is named in `Solutions.specs'.

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

  2. Apply cl to `random.nl'. A syntax error is reported because cl does not understand qualified names, but the name analysis continues with the dot removed from the input text. The two applied occurrences Random and ran remain, and are considered individually.

    1. Is the treatment of each applied occurrence correct? Explain briefly.

    2. Can you conclude that your specification is correct from the tests that you have run? Explain briefly.

Qualified names

The defining occurrences state and ran in `random.nl' have the ClassBody of Random as their scope. We introduce qualified names in order to be able to access such entities from outside of the scope of their defining occurrences. Here is a specification of the phrase structure of a qualified name:

Phrase structure[32]==

Name: Name '.' Ident.
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 Name preceding the dot in a qualified name is called the qualifier, and the Ident is an applied occurrence. We need an abstract syntax symbol to distinguish this new identifier context (see Representation of identifiers of Name Analysis Reference Manual). QualifiedId is a reasonable choice:

Abstract syntax of identifiers[33]==

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

In order to formalize the intuitive meaning of a qualified name, we need one concept in addition to the four discussed earlier (see Basic Scope Rules of Name analysis according to scope rules).

  • An entity may own a set of bindings called its members.

    • Language rules specify the set of members of an entity.

As language designers, we provide the following rules to define the members of a class and the meaning of a qualified name:

  • The members of a Class are the bindings having the ClassBody as a scope.

  • An applied occurrence that is a QualifiedId `i' in a Name `q.i' identifies the binding `(i,k)' that is a member of the qualifier `q'.

Here is an analysis of the qualified name Random.ran of `random.nl':

  1. The applied occurrence Random names a class entity `e'.

  2. The members of `e' are the bindings having the ClassBody on lines 2-8 as a scope.

  3. The binding `(ran,k)' is a member of `e'.

  4. The applied occurrence ran in Random.ran identifies `(ran,k)', and hence the name Random.ran names the pseudo-random number generator.

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

Establish the ownership relation[34]==

RULE: ClassDecl ::= 'class' ClassDefName Inheritance ClassBody COMPUTE
  ClassBody.ScopeKey = ClassDefName.Key;
END;
This macro is invoked in definition 38.

A qualified identifier plays the GCQualName role (see Graph-complete search of Name Analysis Reference Manual). That role provides three attributes used in name analysis:

ScopeKey
A DefTableKey-valued attribute that should be set by user computation to the key of the entity named by the qualifier.

Scope
A NodeTuplePtr-valued attribute that will be set by a module computation to the set of bindings owned by the entity given by the ScopeKey attribute (see Obtain a range from a qualifier of Name Analysis Reference Manual). If that entity does not own bindings, the computation will set the Scope attribute to the value NoNodeTuple (see Establishing the Structure of a Scope Graph of Name Analysis Reference Manual).

Key
A DefTableKey-valued attribute that will be set by the following module computation: Let `i' be the qualified identifier. If the binding `(i,k)' is an element of the Scope set, then set Key to `k'. Otherwise set Key to NoKey.

In some contexts, the set of bindings owned by a qualifier may not be known until some other computation has taken place (for an example, see Type-qualified entity names). This means that we need to establish a precondition, ContextIsReady, for setting the ScopeKey attribute. That precondition is true by default:

Qualified names lookup in complete graphs[35]==

SYMBOL Name COMPUTE INH.ContextIsReady += "yes"; END;
This macro is defined in definitions 35 and 36.
This macro is invoked in definition 38.

By using an accumulating computation for the void attribute ContextIsReady, we can allow any of several different computations to provide the precondition (see Accumulating Computations of LIDO -- Reference Manual).

If we use an attribute Name.Key to represent the entity named by a Name, then that attribute can be computed by a left-to-right (bottom-up) induction:

Qualified names lookup in complete graphs[36]==

SYMBOL QualifiedId INHERITS GCQualName, ChkIdUse END;

RULE: Name ::= Name '.' QualifiedId COMPUTE
  QualifiedId.ScopeKey = Name[2].Key <- Name[1].ContextIsReady;
  Name[1].Key = QualifiedId.Key;
END;

RULE: Name ::= SimpleName COMPUTE
  Name.Key = SimpleName.Key;
END;
This macro is defined in definitions 35 and 36.
This macro is invoked in definition 38.

At this point the abstract syntax contains four Name contexts in addition to the contexts in the qualified name rules:

Statement ::= Name '(' Arguments ')' ';'
Expr      ::= Name '(' Arguments ')'
Statement ::= Name '=' Expr ';'
Expr      ::= Name
In the first two contexts, Name is the name of a method, and in the last two it is the name of a variable. It is likely that method names and variable names will need different attributes, and that those attributes will differ from the attributes needed for qualified names. Since attributes are associated with symbols of the abstract syntax, it would be useful to add two new symbols, MethName and ExprName, to the abstract syntax. The technique is identical to the one we have been using to provide abstract syntax symbols to distinguish identifier contexts (see Basic Scope Rules of Name analysis according to scope rules). We replace Name in each of the four rules with the desired symbol and add rules defining each of the new symbols as a Name:

Make contexts of complete names explicit[37]==

RULE: Statement ::= MethName '(' Arguments ')' ';'      END;
RULE: Expr      ::= MethName '(' Arguments ')'          END;
RULE: Statement ::= ExprName '=' Expr ';'               END;
RULE: Expr      ::= ExprName                            END;

RULE: MethName  ::= Name                                END;
RULE: ExprName  ::= Name                                END;
This macro is defined in definitions 37, 44, 56, 57, 68, 77, and 98.
This macro is invoked in definition 38.

We need to attach all of these rules to the abstract syntax tree:

Abstract syntax tree[38]==

Establish the ownership relation[34]
Qualified names lookup in complete graphs[35]
Make contexts of complete names explicit[37]
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%Qual > .

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. Use `Bindings.specs' to generate a processor named `qual' that will show bindings for applied occurrences.

    1. Apply `qual' to `random.nl', `machar.nl', and `gcd.nl'. Do you get the output you expected? Explain briefly.

    2. Explain briefly why QualifiedId inherits the role ChkIdUse.

  2. In `random.nl', change the two assignments of the program body to:

    p = p.ran();
    q = Random.ra();
    

    1. Apply `qual' to the modified file. Did you get the result you expected?

    2. The two error reports produced by qual when it is applied to the modified `random.nl' are identical except for the undefined identifier. Do you think that the two errors should be reported in the same way? Explain briefly.

    3. How could you use the value of QualifiedId.Scope to provide different reports for the two errors in the modified `random.nl'? (See Error Reporting of The Eli Library, for information on constructing error reports.)

Inheritance

The program `gambler.nl' implements a coin tossing class Coin and a class Dice that throws an arbitrary number of dice. It uses inheritance to provide each class with its own random number generator, without duplicating code:

gambler.nl[39]==

class Random {
  int state = 100001;

  float ran() {
    state = state * 125;
    state = state - (state / 2796203) * 2796203;
    return state / 2796203.0;
  }
}

class Coin extends Random {
  int state = 0;

  int toss() {
    state = 2 * ran();
    return state;
  }
}

class Dice extends Random {
  int state = 1;

  int throw(int n) {
    state = n;
    return (6 * n) * ran() + 1;
  }
}

{ int n; float p;
  n = Coin.toss();
  n = Dice.throw(5);
  n = Coin.state;
  p = Coin.ran();
  n = Dice.state;
  p = Dice.ran();
}
This macro is attached to a non-product file.

Both Coin and Dice use extends clauses to name Random as their direct superclass from which they inherit. We use the following scope rule to define the effect of inheritance:

  • Let `C' be a class which inherits from its direct superclass `S'. Any binding having a scope that is the ClassBody of `S' also has a scope that is the ClassBody of `C', unless a defining occurrence of that binding's identifier has the ClassBody of `C' as its scope.

In `gambler.nl', Coin has Random as its direct superclass. A binding for ran has the ClassBody of Random as its scope, and no defining occurrence of ran has the ClassBody of Coin as its scope. Therefore the binding for ran that has the ClassBody of Random as its scope also has the ClassBody of Coin as its scope. This means that the applied occurrence of ran in the second line of toss names the ran method of class Random.

In contrast, although a binding for state has the ClassBody of Random as its scope, there is a defining occurrence of state having the ClassBody of Coin as its scope. Therefore the applied occurrence of state in the second line of toss names the state variable of class Coin.

Similar reasoning applies to class Dice: throw invokes the ran method of class Random and assigns a value to the state variable of class Dice.

It is important to understand that both Coin and Dice actually have two integer variables. One of these variables is inherited from Random, and the other is defined locally. The local definition has merely hidden the inherited integer variable in the body of the inheriting class. Thus the programmer cannot access it directly within the inheriting class. The ran method, however, accesses the state variable inherited from Random because that method is defined in Random.

An inheritance relation is modeled in the scope graph by a path edge that is directed from the node for a class to the node for its direct superclass (see Scope graphs of Name Analysis Reference Manual). The tip of that path edge is given by a name, and lookup operations are required to determine the node bound to that name. The lookup of an edge tip name may depend on the existence of path edges in the scope graph, and it contributes a path edge to the scope graph. In `edges.nl', for example, the lookup for the edge tip C.D contributes a path edge from node B to node D and depends on the existence of the edge from node C to node A:

edges.nl[40]==

class X {
  int k;

  class A {
    class D { int k; }
  }

  class B extends C.D {
    int m() {
      return k;
    }
  }

  class C extends A { }
}

{ }
This macro is attached to a non-product file.

Our previous analysis of applied occurrences was based on the roles GCSimpleName and GCQualName, which depend on a complete scope graph (see Graph-complete search of Name Analysis Reference Manual). Clearly those roles will not be satisfactory for analyzing the applied occurrences in a superclass name. The scope graph module provides two analogous roles, WLSimpleName and WLQualName, that solve potential ordering conflicts by using a worklist algorithm (see Worklist search of Name Analysis Reference Manual). In order to take advantage of these roles, we define the phrase structure using symbols that are different from those inheriting roles GCSimpleName and GCQualName:

Phrase structure[41]==

Inheritance:    'extends' WLName.

WLName:         Ident.
WLName:         WLName '.' Ident.
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 abstract syntax symbols to distinguish the two new identifier contexts found in this rule (see Representation of identifiers of Name Analysis Reference Manual). These contexts are analogous to SimpleName and QualifiedId, so we just add WL to distinguish them:

Abstract syntax of identifiers[42]==

RULE: WLName            ::= SimpleWLName                END;
RULE: SimpleWLName      ::= Ident                       END;

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

The attribute computations for WLName are very similar to those for Name. They also implement a left-to-right induction, but use FPItemPtr values instead of DefTableKey values. An FPItemPtr value represents a worklist computation, whereas a DefTableKey value represents the result of such a computation. FPItemPtr values can be computed before the worklist computation takes place because they specify what that computation is to do, rather than the result it will obtain:

Specify worklist computations[43]==

ATTR FPItem: FPItemPtr;

SYMBOL SimpleWLName INHERITS WLSimpleName, ChkIdUse END;
SYMBOL QualifiedWLId INHERITS WLQualName, ChkIdUse END;

RULE: WLName ::= SimpleWLName COMPUTE
  WLName.FPItem = SimpleWLName.FPItem;
END;

RULE: WLName ::= WLName '.' QualifiedWLId COMPUTE
  QualifiedWLId.DependsOn = WLName[2].FPItem;
  WLName[1].FPItem = QualifiedWLId.FPItem;
END;
This macro is invoked in definition 47.

WLName appears in one abstract syntax rule beyond the two above:

RULE: Inheritance ::= 'extends' WLName END;
As with Name in the last section, it is likely that the attributes needed for WLName in this context will differ from those needed to handle qualified names. Therefore we provide a new abstract syntax symbol, SuperClass, to distinguish this context:

Make contexts of complete names explicit[44]==

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

Each WLName represents a distinct source language entity, and therefore it must have a Key attribute:

Specify the Key attribute of a WLName[45]==

RULE: WLName ::= SimpleWLName COMPUTE
  WLName.Key = SimpleWLName.Key;
END;

RULE: WLName ::= WLName '.' QualifiedWLId COMPUTE
  WLName[1].Key = QualifiedWLId.Key;
END;
This macro is invoked in definition 47.

A path edge is established by the role WLCreateEdge (see Worklist search of Name Analysis Reference Manual). The tailEnv attribute of the node inheriting WLCreateEdge must be set to the NodeTuplePtr value representing the scope graph node that is the tail of the edge. The tipFPItem attribute of the node inheriting WLCreateEdge must be set to an FPItem value describing the computation of the tip of the edge. In our grammar, SuperClass provides an appropriate context for WLCreateEdge:

Establish a path edge to a superclass[46]==

SYMBOL SuperClass INHERITS WLCreateEdge END;

RULE: SuperClass ::= WLName COMPUTE
  SuperClass.tailEnv = INCLUDING Inheritance.SubClassEnv;
  SuperClass.tipFPItem = WLName.FPItem;
END;
This macro is invoked in definition 47.

This rule obtains the computation for the tip value from its child, but reaches up the tree for the tail value. We leave the computation of Inheritance.SubClassEnv to the reader as an exercise.

The NameLan inheritance scope rule says that an inherited binding hides bindings from enclosing ranges. That means that the generic search algorithm must deal with inheritance before it moves to an enclosing range (see The generic lookup of Name Analysis Reference Manual).

The two fragments discussed in this section form the basis for implementing NameLan inheritance:

Abstract syntax tree[47]==

Specify worklist computations[43]
Specify the Key attribute of a WLName[45]
Establish a path edge to a superclass[46]
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%Path > .

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. We have used the attribute Inheritance.SubClassEnv in the super class computations, but not specified how that attribute is set. Complete our specification, and ensure that the file containing your material is named in `Solutions.specs'. It must define the attribute Inheritance.SubClassEnv, and set its value to the NodeTuplePtr value representing the range of the subclass (see The RangeScope role of Name Analysis Reference Manual).

  2. Use the scope rule for inheritance to decide the meaning of the applied occurrence of k in the return statement of `edges.nl'.

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

  4. In `edges.nl', change the symbol A in the extends clause for class C to k. Apply `path' to the modified file. Do you get the output you expected? Explain briefly.

  5. Consider the program `wlblock.nl':

    wlblock.nl[48]==

    class CC { }
    
    class C1 extends C2.BB {
      class AA extends CC { }
    }
    
    class C2 extends C1.AA {
      class BB extends CC { }
    }
    
    { }
    
    This macro is attached to a non-product file.
    

    1. Draw a scope graph showing the path edges for the superclass relations in `wlblock.nl'.

    2. Simulate the worklist computation for `wlblock.nl'. Do you see any problems?

    3. Apply `path' to `wlblock.nl'. Did you get the result you expected?


Previous Chapter Next Chapter Table of Contents