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

Name Analysis Reference Manual

Previous Chapter Next Chapter Table of Contents


Implementing Language-Specific Behavior

The ScopeGraphs module is based upon a lookup algorithm that implements concepts developed over the years to handle visibility of names (see The generic lookup). While these concepts are present in most languages, they are often embellished with additional rules and restrictions. It is impossible to build a module that will handle any but the simplest language "out of the box"; a developer must almost always specialize it for their application. Our intent is to provide a powerful substrate for the developer, and to make the critical controls easily available.

We have chosen to define a developer interface consisting of functions called at specific points in the lookup process. A developer may provide implementations of some, all, or none of these functions; default versions of those not provided will be used automatically. The arguments of these functions are definition table keys representing the applied occurrence being sought and the candidate binding(s) found by the generic lookup. Some of the properties of those keys are set by module computations. The developer is free to set arbitrary properties of keys, so that the functions can have access to whatever information the developer deems necessary.

Some of the interface functions are called when the generic lookup finds a candidate binding. Those functions may apply semantic information to accept or reject the candidate binding. If the binding is to be rejected, then the function can specify whether and in what manner the search is to continue. Other functions are called to resolve ambiguities when several bindings have satisfied the search criteria. Finally, initialization and finalization routines allow for the use of state information that may vary from one lookup to another.

Information about the applied occurrence and the candidate binding is not always sufficient to make a decision. Sometimes the path through a scope graph between applied and defining occurrences is important, and therefore the module provides functions that the developer can call to explore the graph.

The name of the file containing the default version of each of the developer-coded functions is given after the interface description. If the default behavior of one of these routines is inadequate, it is nevertheless a good starting point for implementing a replacement. To obtain a copy of (say) the source code for `LookupBegin' as file `LookupBegin.c' in your current directory, give the Eli request:

-> $elipkg/Name/LookupBegin.c > LookupBegin.c

After modifying `LookupBegin.c', simply add its name to your type-`specs' file to make it available.

Information access

Definition table keys are used as arguments to the interface functions because an arbitrary set of information can be associated with a definition table key without affecting the interface specification. This gives maximum flexibility without imposing either a physical or conceptual burden.

An argument representing the applied occurrence being sought is the value of the *UseKey attribute of that applied occurrence (see Applied Occurrences). Module computations set the following properties of the *UseKey:

Sym
is an integer-valued property holding the value of the applied occurrence's Sym attribute.

Coord
is a CoordPtr-valued property holding the source code location of the applied occurrence.

*GraphIndex
is an integer-valued property holding the value of the applied occurrence's GraphIndex attribute (see Isomorphic Scope Graphs).

Other *UseKey properties can be set by developer computations. To ensure that those property values are actually set before the search begins, an additional attribute, *GotUseKeyProp, is provided for all applied occurrences (see Applied Occurrences). *GotUseKeyProp should be stated as the postcondition of any computations the developer adds to set properties of *UseKey needed by language-specific functions. Accumulating computations must be used for this purpose (see Accumulating Computations of LIDO -- Reference Manual). Here is an example that sets a property called NodeTuple to hold a representation of the range containing the applied occurrence:

TREE SYMBOL MethodIdUse INHERITS GCSimpleName COMPUTE
  SYNT.GotUseKeyProp +=
    ResetNodeTuple(THIS.UseKey,INCLUDING AnyScope.Env);
END;

An argument representing a binding candidate is the value of the *Key attribute of the candidate's defining occurrence. See Defining Occurrences, for the properties of that value.

Obtain a range from a qualifier

The construct `q.i' consists of a qualifier `q' and a qualified identifier `i'. Name analysis of `q.i' begins by looking up the qualifier `q'. Suppose that this lookup results in the definition table key `k'. In order to look up the qualified identifier `i', an appropriate NodeTuplePtr value representing a range will be obtained from `k' and the *UseKey of `i' by invoking AccessNodesFromQualifier. If the developer does not provide that routine, the following will be used automatically:

NodeTuplePtr
AccessNodesFromQualifier(DefTableKey qualifier, DefTableKey app)
/* On entry-
 *   qualifier specifies an entity from which a range can be obtained
 *   app specifies the qualified identifier sought in that range
 * On exit-
 *   AccessNodesFromQualifier represents the range
 ***/
{ return GetOwnedNodeTuple(qualifier, NoNodeTuple); }

Source code: `$elipkg/Name/AccessNodesFromQualifier.c'

In the case of the computational role *GCQualName, the developer has additional options for obtaining a suitable range (see Graph-complete search). Those options are not available for the computational role *WLQualName, where AccessNodesFromQualifier is invoked by the module as soon as the qualifier's key is available (see Worklist search).

Is the binding acceptable?

If the generic algorithm finds a binding for an applied occurrence in @math{Bind(n)}, it immediately invokes one of three functions. The result of each function is one of three integer-valued flags with the names:

AcceptBinding
The generic algorithm should terminate, returning the definition key of the binding.

IgnoreContinue
The generic algorithm should continue as though no binding had been found.

IgnoreSkipPath
The generic algorithm should continue, but it should not explore paths starting at node @math{n} (see The generic lookup).

The function called depends on the context of the search:

isAcceptableSimple
is invoked in a search for a simple identifier when @math{n} is the initial node or a node reached by following a parent edge.

If the developer does not provide isAcceptableSimple, the following will be used automatically:

int
isAcceptableSimple (DefTableKey def, DefTableKey  app)
/* On entry-
 *   def is the key of the binding found
 *   app is the UseKey of the applied occurrence sought
 * On exit-
 *   isAcceptableSimple is the desired continuation
 ***/
{ return AcceptBinding; }

Source code: `$elipkg/Name/isAcceptableSimple.c'

isAcceptableQualified
is invoked in a search for a qualified identifier when @math{n} is the initial node.

If the developer does not provide isAcceptableQualified, the following will be used automatically:

int
isAcceptableQualified (DefTableKey def, DefTableKey  app)
/* On entry-
 *   def is the key of the binding found
 *   app is the UseKey of the applied occurrence sought
 * On exit-
 *   isAcceptableQualified is the desired continuation
 ***/
{ return AcceptBinding; }

Source code: `$elipkg/Name/isAcceptableQualified.c'

isAcceptablePath
is invoked in a search for a simple identifier or a qualified identifier when @math{n} is the tip of a path edge.

If the developer does not provide isAcceptablePath, the following will be used automatically:

int
isAcceptablePath (DefTableKey def, DefTableKey  app, int lab)
/* On entry-
 *   def is the key of the binding found
 *   app is the UseKey of the applied occurrence sought
 *   lab is the label of the path edge
 * On exit-
 *   isAcceptablePath is the desired continuation
 ***/
{ return AcceptBinding; }
Source code: `$elipkg/Name/isAcceptablePath.c'

Deciding among possible bindings

Recall that the generic algorithm traverses all paths starting at node @math{n} and made up solely of edges labeled @math{k} (see The generic lookup). When it finds an acceptable binding, it stops traversing the current path, backs up to node @math{n}, and traverses the next path made up solely of edges labeled @math{k} that starts at node @math{n}. This means that if there are several such paths, the search starting at node @math{n} may yield several bindings. The result is of type DefTableKeyList, implemented using the Eli PtrList module (see Linear Lists of Any Type of Abstract data types to be used in specifications).

When the complete search yields no bindings, the result is NoKey. If exactly one binding is found, the result is the key for that binding. If there is more than one binding, the generic algorithm invokes the function DisambiguatePaths and returns the result of that invocation.

If the developer does not provide DisambiguatePaths, the following will be used automatically:

DefTableKey
DisambiguatePaths (DefTableKeyList kl, DefTableKey app, int lab)
/* On entry-
 *   kl is the list of keys from the acceptable bindings
 *   app is the UseKey of the applied occurrence sought
 *   lab is the label of the edges making up the paths searched
 * On exit-
 *   DisambiguatePaths is the selected key
 ***/
{ return NoKey; }

Source code: `$elipkg/Name/DisambiguatePaths.c'

By returning the value NoKey, this implementation of DisambiguatePaths requires that all searches yield unambiguous results.

Another possible ambiguity occurs when the applied occurrence is in a weak context (see Isomorphic Scope Graphs). In that case, searches must be carried out in more than one scope graph and each search might yield an acceptable binding. When all searches are complete, the generic algorithm invokes DisambiguateGraphs and returns the result of that invocation.

If the developer does not provide DisambiguateGraphs, the following will be used automatically:

DefTableKey
DisambiguateGraphs (DefTableKey G[], int N, DefTableKey app)
/* On entry-
 *   G[] is an array with N elements giving the result for each search
 *   app is the UseKey of the applied occurrence sought
 * On exit-
 *   DisambiguateGraphs is the selected key
 ***/
{ int i;
  DefTableKey result = NoKey;

  for (i = 0; i < N; i++) {
    if (G[i] != NoKey) {
      if (result != NoKey) return NoKey;
      result = G[i];
    }
  }
  return result;
}

Source code: `$elipkg/Name/DisambiguateGraphs.c'

This implementation requires that exactly one of the graph searches yields an acceptable binding.

Initialization and finalization

The developer may sometimes need to initialize and/or finalize data structures that their functions use during a single lookup. Thus the generic algorithm invokes LookupBegin upon entry, and invokes LookupComplete as it exits.

If the developer does not provide LookupBegin, the following will be used automatically:

void
LookupBegin (DefTableKey app)
/* On entry-
 *   app is the UseKey of the applied occurrence sought
 ***/
{ return; }

Source code: `$elipkg/Name/LookupBegin.c'

If the developer does not provide LookupComplete, the following will be used automatically:

DefTableKey
LookupComplete(DefTableKey def, DefTableKey app)
/* On entry-
 *   def is the key of the binding found
 *   app is the UseKey of the applied occurrence sought
 * On exit-
 *   LookupComplete is the final result of the lookup
 ***/
{ return def; }

Source code: `$elipkg/Name/LookupComplete.c'

Useful graph operations

The functions described in this section are provided by the ScopeGraphs module. They may be invoked by developer-coded routines that need to traverse the scope graph in order to check language-specific name analysis rules. Their interfaces are defined in LangSpecFct.h, which must be included by any code using them.

The DefTableKey value of the *ScopeKey attribute of a symbol inheriting the *RangeScope role is used to represent a node in these functions (see The RangeScope role). That key has a property, NodeTuple, giving the value of the symbol's *Env attribute. Representing the node by a definition table key allows the developer to associate arbitrary language-specific information with a particular range.

Several of these functions require the developer to provide a function that will be called for each scope graph node visited. That function can determine whether or not the scope graph node satisfies some language-specific condition. The type of this function is CallBackDTKFct, defined by:

typedef int (*CallBackDTKFct)(DefTableKey)

The argument of a function of type CallBackDTKFct specifies the graph node to be examined. If the condition is not satisfied, the function returns 0.

CheckPaths
follows paths in the scope graph that are made up of path edges with a given label. A developer-coded function is invoked at each node.

int
CheckPaths(DefTableKey f, DefTableKey t, int i,
             CallBackDTKFct fnc)
/* On entry-
 *   f specifies the initial node of the path
 *   t specifies the final node of the path
 *   i specifies the label of the edges making up the path
 *   fnc is a developer-code function invoked at each node
 * If no such path exists then on exit-
 *   CheckPaths=0
 * Else if any invocation of fnc yields 0 then
 *   CheckPaths exits immediately
 *   CheckPaths=0
 * Else on exit-
 *   CheckPaths=1
 ***/

isAcceptablePath might invoke CheckPaths to check whether a certain property holds for each node on the path between the context of the use of an identifier and its definition. For example, in Java an inherited name must be accessible in every class along the inheritance path. In this case, fnc would be a developer-coded function that verified the accessibility of the identifier, using information from the node and information stored about the identifier by isAcceptablePath.

CheckPathsNsp
follows paths in the scope graph that are made up of path edges with a given label. A developer-coded function is invoked at each node.

CheckPathsNsp is identical to CheckPaths except that f and t are NodeTuple values instead of definition table keys.

int
CheckPathsNsp (NodeTuplePtr f, NodeTuplePtr t, int i,
                 CallBackDTKFct fnc)
/* On entry-
 *   f specifies the initial node of the path
 *   t specifies the final node of the path
 *   i specifies the label of the edges making up the path
 *   fnc is a developer-code function invoked at each node
 * If no such path exists then on exit-
 *   CheckPathsNsp=0
 * Else if any invocation of fnc yields 0 then
 *   CheckPathsNsp exits immediately
 *   CheckPathsNsp=0
 * Else on exit-
 *   CheckPathsNsp=1
 ***/

DFSCompleteFrom
does a depth-first scan of the scope graph using path edges with a given label. A developer-coded function is invoked at each node.

void
DFSCompleteFrom (DefTableKey f, int i, CallBackDTKFct fnc)
/* On entry-
 *   f specifies the starting node for the search
 *   i specifies the label of the path edges to be used
 *   fnc is a developer-code function invoked at each node
 *     the results returned by fnc calls are ignored
 ***/

HidesOnPaths
accesses bindings along a specified path that are hidden by a specified binding according to the generic lookup (see The generic lookup).

DefTableKey
HidesOnPaths (DefTableKey def, DefTableKey app, int i)
/* On entry-
 *   def specifies a defining occurrence that may hide other
 *         bindings
 *   app specifies an applied occurrence
 *   i specifies the label of the path edges to be searched
 * On exit-
 *   HidesOnPaths=binding that would be returned
 *     by the generic lookup when isAcceptablePath(def, app)
 *     returned IgnoreContinue
 ***/

HidesNestAndPaths
accesses bindings that are hidden by a specified binding according to the generic lookup (see The generic lookup).

DefTableKey
HidesNestAndPaths (DefTableKey def, DefTableKey app)
/* On entry-
 *   def specifies a defining occurrence that may hide other
 *         bindings
 * On exit-
 *   HidesNestAndPaths=binding that would be returned
 *     by the generic lookup when isAcceptibleSimple(def, app)
 *     returned IgnoreContinue
 ***/

reachableNode
checks for the existence of a path in a (possibly incomplete) scope graph. The path contains only path edges with a given label, and may or may not also contain parent edges.

int
reachableNode (DefTableKey f, DefTableKey t, int i, int par)
/* On entry-
 *   f specifies the initial node of the path
 *   t specifies the final node of the path
 *   i specifies the label of the edges making up the path
 *   if par==0 then the path cannot contain parent edges
 * If no such path exists then on exit-
 *   reachableNode=0
 * Else on exit-
 *   reachableNode=1
 ***/

Note that because the scope graph may not be complete when reachableNode is called, a node that is reported to be unreachable may become reachable later in the analysis.

GCPathReachable
tests for the existence of a path in a complete scope graph. The path contains only path edges with a given label.

int
GCPathReachable (NodeTuplePtr f, NodeTuplePtr t, int i)
/* On entry-
 *   f specifies the initial node of the path
 *   t specifies the final node of the path
 *   i specifies the label of the edges making up the path
 * If f != t and such path exists then on exit-
 *   GCPathReachable=1
 * Else on exit-
 *   GCPathReachable=0
 ***/

The scope graphs are assumed to be complete. The function does not walk the graph, but it uses bitsets to decide reachability.


Previous Chapter Next Chapter Table of Contents