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


Interaction with Type Analysis

Classes provide the structure for objects. An object can be created by applying the new operator to a class. The state of an object, embodied in the values of its variables, can be changed by executing operations on it. A reference to an object can be assigned to a variable of the object's class type or any of its superclass types.

Here is an example whose classes implement a very simple NameLan expression tree:

expr.nl[75]==

class Expr {
  int eval() { }
}

class Dyadic extends Expr {
  Oper op; Expr left; Expr right;
  int eval() {
    return op.eval(left.eval(), right.eval());
  }
}

class IntDenot extends Expr {
  int v;        /* v is set when the object is created */
  int eval() { return v; }
}

class Oper {
  int eval(int l, int r) { }
}

class Plus extends Oper {
  int eval(int l, int r) { return l + r; }
}

class Minus extends Oper {
  int eval(int l, int r) { return l - r; }
}

{ IntDenot l = new IntDenot; l.v = 3;
  Expr e = l;
  int v = e.eval();
  /* v now holds the integer value 3 */
}
This macro is attached to a non-product file.

The block here creates an object representing an integer expression "3" and then evaluates that expression.

The only obvious extensions necessary to accommodate this example are to accept a Name as a Type and to recognize object creation:

Phrase structure[76]==

Type:   Name.
Expr:   'new' Name.
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.

In these contexts, the Name will always be a class name:

Make contexts of complete names explicit[77]==

RULE: Type      ::= ClassName       END;
RULE: Expr      ::= 'new' ClassName END;
RULE: ClassName ::= Name            END;
This macro is defined in definitions 37, 44, 56, 57, 68, 77, and 98.
This macro is invoked in definition 38.

Although these syntactic extensions allow us to accept programs like `expr.nl', they are not sufficient to support name analysis.

Consider the three occurrences of eval in the return statement in class Dyadic. In none of these cases is eval's qualifier the name of a class having members. Instead, each is a variable containing a reference to an object of a class which has members. The type of that variable tells us the class from which the object was created. For this reason, we speak of a qualified name in which the qualifier is a variable as a type-qualified name.

Notice that a type-qualified ClassName makes no sense: variables do not have members that are types.

You can see the effect of type-qualified names by generating a processor that is missing computations to derive members from types, and applying that processor to `expr.nl' (see Execute a command in a specified directory of Products and Parameters Reference Manual).

-> $elipkg/Name/LearnSG%Type > .
-> . +cmd=(NameLan.specs :exe) (expr.nl) :run

Connect to the Typing module

In order to analyze a type-qualified name, we need to know the qualifier's type. The task of type analysis is to determine a type for every construct that has a type, and therefore name analysis of type-qualified names needs support from type analysis.

The basic type analysis computations are provided by the Eli Typing module (see Typed Entities of Type Analysis Reference Manual). We should instantiate this module in order to help us to analyze type-qualified names:

Specification files[78]==

$/Type/Typing.gnrc :inst
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.

NameLan uses name equivalence for types, so Typing represents each type by a unique definition table key. There are three language-defined types, and each class is a user-defined type.

We need to initialize definition table keys for the three language-defined types (see How to specify the initial state of The Property Definition Language). Each of these definition table keys should have a property IsType that is set to 1. The type analysis module uses the IsType property to distinguish keys representing types from keys representing other entities:

Properties and property computations[79]==

intType   -> IsType={1};
floatType -> IsType={1};
voidType  -> IsType={1};
This macro is defined in definitions 79, 94, 126, 132, 143, and 148.
This macro is invoked in definition 80.

Property definitions and computations are specified in a file of type `.pdl', which must be added to the set of specifications:

NameLan.pdl[80]==

Properties and property computations[79]
This macro is attached to a product file.

Specification files[81]==

NameLan.pdl
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.

Definition table keys for user-defined types are created by the TypeDenotation role (see User-Defined Types of Type Analysis Reference Manual). If a symbol inherits this role, a module computation creates a new key, sets its IsType property to 1, and stores it as the symbol's Type attribute:

Construct that represents a subtree denoting a type[82]==

SYMBOL ClassBody INHERITS TypeDenotation END;
This macro is invoked in definition 88.

This guarantees that each ClassBody.Type has a unique definition table key whose IsType property value is 1.

Type keys are made available to a variable declaration via the DefTableKey-valued Type attribute of the Type symbol. The Type symbol for a language-defined type is a keyword, while the Type symbol for a user-defined type is an identifier:

Establish the Type attribute of Type[83]==

ATTR Type: DefTableKey;

RULE: Type ::= 'int'            COMPUTE Type.Type = intType;        END;
RULE: Type ::= 'float'          COMPUTE Type.Type = floatType;      END;
RULE: Type ::= 'void'           COMPUTE Type.Type = voidType;       END;
RULE: Type ::= ClassName        COMPUTE Type.Type = ClassName.Type; END;
This macro is invoked in definition 88.

Note that in order to establish the Type attribute of a Type construct representing a class type, name analysis must first determine the meaning of a (qualified) name. We will return to this point in the next section.

An identifier that is a Type symbol plays a different role in type analysis than an identifier that names a typed entity. That means we need to inherit relevant type analysis roles and set any attribute values that the Typing module requires:

Construct defining one or more entities of the same type[84]==

SYMBOL VarDecl INHERITS TypedDefinition END;

RULE: VarDecl ::= Type VarDefs ';' COMPUTE
  VarDecl.Type = Type.Type;
END;

SYMBOL Parameter INHERITS TypedDefinition END;

RULE: Parameter ::= Type ParamDefName COMPUTE
  Parameter.Type = Type.Type;
END;
This macro is invoked in definition 88.

Defining occurrence of an identifier for a typed entity[85]==

SYMBOL VarDefName       INHERITS TypedDefId END;
SYMBOL ParamDefName     INHERITS TypedDefId END;
This macro is invoked in definition 88.

Defining occurrence of a type identifier[86]==

SYMBOL ClassDefName INHERITS TypeDefDefId END;

RULE: ClassDecl ::= 'class' ClassDefName Inheritance ClassBody COMPUTE
  ClassDefName.Type = ClassBody.Type;
END;
This macro is invoked in definition 88.

Applied occurrence of a type identifier[87]==

SYMBOL ClassName INHERITS TypeDefUseId END;

RULE: ClassName ::= Name COMPUTE
  ClassName.Key = Name.Key;
END;
This macro is invoked in definition 88.

In summary, the computations to connect the Typing module to our existing name analysis framework are:

Abstract syntax tree[88]==

Establish the Type attribute of Type[83]
Construct defining one or more entities of the same type[84]
Defining occurrence of an identifier for a typed entity[85]
Construct that represents a subtree denoting a type[82]
Defining occurrence of a type identifier[86]
Applied occurrence of a type identifier[87]
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.

Type-qualified entity names

Consider the declaration of Dyadic from `expr.nl':

class Dyadic extends Expr {
  Oper op; Expr left; Expr right;
  int eval() {
    return op.eval(left.eval(), right.eval());
  }
}
The variable op is a qualifier in the return expression, so the processor must determine its type in order to find the member set containing the binding for eval. But before the type property of the variable op can be determined, name analysis must find the meaning of Oper. (We mentioned this constraint in the last section.)

We must make certain that the type of op is known before we can analyze the type-qualified name op.eval. The type module provides the necessary information by setting the attribute RootType.TypeIsSet after all typed identifiers have their type properties set (see Dependence in Type Analysis of Type Analysis Reference Manual). We can use this attribute to establish the precondition for analyzing qualified names in the contexts where those names might be type-qualified (see Qualified names). The precondition must be passed down to every qualifier in a qualified name:

Abstract syntax tree[89]==

RULE: MethName  ::= Name COMPUTE
  Name.ContextIsReady += INCLUDING RootType.TypeIsSet;
END;

RULE: ExprName  ::= Name COMPUTE
  Name.ContextIsReady += INCLUDING RootType.TypeIsSet;
END;

RULE: Name ::= Name '.' QualifiedId COMPUTE
  Name[2].ContextIsReady += Name[1].ContextIsReady;
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.

We cannot make RootType.TypeIsSet a precondition for names in the ClassName context without introducing an extra worklist algorithm, because a ClassName can set the type of variable and therefore contributes to RootType.TypeIsSet. As language designers, we choose to avoid this complexity by introducing the following rule:

  • Variable names are not allowed in ClassName contexts.
This restriction has no significant effect on the expressive power of NameLan.

The generic lookup invokes AccessNodesFromQualifier when dealing with a QualifiedId, to obtain the set of bindings in which that applied occurrence must be sought (see Obtain a range from a qualifier of Name Analysis Reference Manual). The default implementation of AccessNodesFromQualifier yields the set of bindings owned by the qualifier. If the qualifier is a variable, however, AccessNodesFromQualifier must yield the set of bindings owned by that variable's type. Thus, as developers, we need to override the default implementation of AccessNodesFromQualifier:

AccessNodesFromQualifier.c[90]==

#include "AccessNodesFromQualifier.h"
#include "pdl_gen.h"
NodeTuplePtr
AccessNodesFromQualifier(DefTableKey qualifier, DefTableKey appselector)
{ NodeTuplePtr res = GetOwnedNodeTuple(qualifier, NoNodeTuple);
  if (res == NoNodeTuple)
    res = GetOwnedNodeTuple(GetTypeOf(qualifier, NoKey), NoNodeTuple);
  return res;
}
This macro is attached to a product file.

Note that this implementation of AccessNodesFromQualifier first assumes that the qualifier owns a set of members. If that assumption is correct, the set members is returned. Only if the qualifier does not own any members does AccessNodesFromQualifier obtain the qualifier's type and seek the members of that type. This behavior is necessary because AccessNodesFromQualifier is invoked for every QualifiedId regardless of the context.

`AccessNodesFromQualifier.c' is one of the specification files:

Specification files[91]==

AccessNodesFromQualifier.c
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.

The only remaining issue is how to report a violation of the rule that a variable name is not allowed in a ClassName context. That report should be attached to the leftmost incorrect component of the ClassName, which might be either a SimpleName or a QualifiedId. We'll wrap the necessary computation in a role named ChkLegalClassName:

Abstract syntax tree[92]==

SYMBOL SimpleName  INHERITS ChkLegalClassName END;
SYMBOL QualifiedId INHERITS ChkLegalClassName 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.

ChkLegalClassName should report an error if and only if all of the following conditions are satisfied:

  • The applied occurrence identifies a binding `(i, k)'.

  • The entity `k' is a variable.

  • The applied occurrence is in a ClassName context.

Abstract syntax tree[93]==

SYMBOL ChkLegalClassName COMPUTE
  IF(AND(AND(
      NOT(EQ(THIS.Key, NoKey)),
      GetIsVariable(THIS.Key, 0)),
      INCLUDING (ClassName.InClassName, RootScope.InClassName)),
    message(
      ERROR,
      CatStrInd ("Must not occur in a class name: ", THIS.Sym),
      0,
      COORDREF));
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 IsVariable property must be set to distinguish entities that are variables:

Properties and property computations[94]==

IsVariable: int;        /* 1 if the entity is a variable */
This macro is defined in definitions 79, 94, 126, 132, 143, and 148.
This macro is invoked in definition 80.

Abstract syntax tree[95]==

SYMBOL VarDefName COMPUTE
  SYNT.GotDefKeyProp += ResetIsVariable(THIS.Key,1);
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.

Making GotDefKeyProp depend on the setting of the IsVariable property ensures that the property's value will be available before any computation attempts to access it (see Defining Occurrences of Name Analysis Reference Manual).

Finally, we need to set the InClassName attribute that defines the context:

Abstract syntax tree[96]==

SYMBOL ClassName, RootScope: InClassName: int;
SYMBOL ClassName COMPUTE SYNT.InClassName = 1; END;
SYMBOL RootScope COMPUTE SYNT.InClassName = 0; 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%TQ > .
None of these files will have write permission in your current directory.

  1. Verify that type-qualified names are correctly handled by generating a processor called tq and applying that processor to `expr.nl'.

  2. List the four kinds of entity that can be bound to NameLan identifiers.

  3. An identifier bound to an entity of each kind might occur in a qualified name. Write a correct NameLan program in which an identifier bound to each kind of entity occurs in some qualified name. Verify your program using tq.

  4. State the kinds of entity that can not legally be bound to an identifier occurring in each of the following contexts:
    • after a dot in a qualified name
    • before a dot in a qualified name
    • in a qualified ClassName
    • at the end of a type-qualified name
    Briefly explain each of your answers.

  5. Write a syntactically correct program with qualified names that are illegal for the reasons you stated above, and check it with tq. Are you satisfied by the results? Explain briefly.

Type-qualified edge names

We have seen that type-qualified names require cooperation between name and type analysis modules, and how they introduce dependence among computations. The type-qualified names in `expr.nl' describe components of an expression in a value context. Suppose that, for some reason, there is a section of code containing a number of references to the members of some computed object. The NameLan with statement allows the user to use simple names to refer to the members of a class:

Phrase structure[97]==

Statement:      'with' Name 'do' WithBody.
WithBody:       Statement.
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 attributes needed for a Name in this context will differ from those needed in other contexts, so we define a new abstract syntax symbol to differentiate the context:

Make contexts of complete names explicit[98]==

RULE: Statement ::= 'with' WithName 'do' WithBody END;
RULE: WithName  ::= Name                          END;
This macro is defined in definitions 37, 44, 56, 57, 68, 77, and 98.
This macro is invoked in definition 38.

Because the WithName may be type-qualified, we need to ensure that all types have been defined before analyzing it:

Ensure that types are defined[99]==

RULE: WithName ::= Name COMPUTE
  Name.ContextIsReady += INCLUDING RootType.TypeIsSet;
END;
This macro is invoked in definition 105.

Here is an example in which the WithName is a simple variable name:

with.nl[100]==

class T { int a, b, c; }
{ float a, c = 1.2;
  T p = new T; p.c = 3;
  T r = new T; r.b = 4;
  a = r.b + c;
  with p do a = r.b + c;
}
This macro is attached to a non-product file.

Our scope rule for a NameLan with statement is:

  • The WithBody is an anonymous class whose superclass is the class of the WithName.
This rule implies that the WithBody must be a range:

Abstract syntax tree[101]==

SYMBOL WithBody 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.

Recall that inheritance is modeled by a path edge in the scope graph (see Inheritance). We need to add that edge to the graph, but the edge tip is defined by a name that may be type-qualified. Therefore the inheritance edge cannot be constructed until all edges have been added to the graph and the types of all typed identifiers have been determined. This is clearly impossible: "all edges have been added" cannot be a precondition for "add an edge".

The paradox can be avoided by using the OutSideInDeps role to partition the name analysis of `with.nl' into two problems (see The OutSideInDeps role of Name Analysis Reference Manual).

Partition the program analysis[102]==

SYMBOL WithBody INHERITS OutSideInDeps END;
This macro is defined in definitions 102 and 104.
This macro is invoked in definition 105.

This statement implies that there is a subgraph `W' of the scope graph `G', such that no edge may have its tail in `G-W' and its tip in `W'. Subgraph `W' corresponds to the abstract syntax subtree rooted in the WithBody node. Given this property of the scope graph, we can complete the name and type analysis of lines 1-4 of `with.nl' without considering the inheritance edge. At that point we will have all of the information necessary to add the inheritance edge and analyze the WithBody.

The OutSideInEdge role attaches computations to add the inheritance edge. The symbol inheriting OutSideInEdge corresponds to the scope graph node that is the tail of the inheritance edge, and the tip is the node obtained from the Key of the WithName (see The OutSideInDeps role of Name Analysis Reference Manual).

Add the path edge to the graph[103]==

SYMBOL WithBody INHERITS OutSideInEdge END;

RULE: Statement ::= 'with' WithName 'do' WithBody COMPUTE
  WithBody.tipEnv = AccessNodesFromQualifier(WithName.Key, NoKey);
END;

RULE: WithName ::= Name COMPUTE
  WithName.Key = Name.Key;
END;
This macro is invoked in definition 105.

We need to partition the type analysis as well (see Interaction with type analysis of Name Analysis Reference Manual).

Partition the program analysis[104]==

SYMBOL TypedDefId INHERITS SetTypeOfEntity END;
SYMBOL TypedUseId COMPUTE
  SYNT.TypeIsSet=INCLUDING OutSideInDeps.GotEntityTypes;
END;
This macro is defined in definitions 102 and 104.
This macro is invoked in definition 105.

Combining the attribute computations and adding them to the other specifications gives us a complete specification from which we can generate a processor that handles programs like `with.nl':

Abstract syntax tree[105]==

Ensure that types are defined[99]
Partition the program analysis[102]
Add the path edge to the graph[103]
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%With > .

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 with that will show the bindings for applied occurrences, and apply it to `with.nl'. Is the result what you expected?

  2. The variable r.b is accessed via a qualified name within the with statement of `with.nl'. Briefly explain why you could not implement a construct like the following to allow r.b to be accessed via a simple name:

    with p,r do a = b + c;
    

  3. Suppose that a language designer allowed the WithName to be a list as in the previous exercise. Provide reasonable scope rules for such a construct.


Previous Chapter Next Chapter Table of Contents