General Information

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

Tutorials

 o Quick Reference Card
 o Guide For new Eli Users
 o Release Notes of Eli
 o Tutorial on Name Analysis
 o Tutorial on Type Analysis

Reference Manuals

 o User Interface
 o Eli products and parameters
 o LIDO Reference Manual

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

 Questions, Comments, ....

Type analysis tasks

Next Chapter Table of Contents


Basic Type Analysis

This module implements a set of concepts that are basic for the type analysis task in many statically typed languages: Definition and use of typed objects, notation of user defined types, and definition and use of type identifiers.

The use of this module is demonstrated in the following section, which intorduces a running example. A complete executable specification for an example is available in $/Type/Examples/Type.fw.

This module is instantiated by

   $/Type/Typing.gnrc +referto=|KEY| :inst
The referto parameter modifies the names of Key attributes, and hence, has to be the same as the referto parameter used for the module instance that supplies the key attributes.

The module uses the following basic concepts for implementation of type analysis tasks:

Types are represented by DefTableKeys. Such a key is created for each program construct which denotes a particular type. The roles of this module associate the property IsType (set to 1) to such type keys in order to distinguish them from keys for other objects. Further properties may be associated to characterize the particular type. NoKey represents the unknown Type.

The property TypeOf is associated to typed objects and states the type of the object.

Identifiers may be defined to denote types. They may be introduced by type definitions. In case of a type definition which simply renames the type given by a type identifier, it is assumed that both type identifiers refer to the same type.

This module uses an instance of the Defer module (see Deferred Property Association) to relate keys that represent the same type. A type identifier key refers indirectly via the Defer relation to a type key, where the type properties are associated to. Let k be a type identifier; then a call TransDefer (k) accesses the type key at the end of the Defer chain via arbitrary many steps.

This module provides the following computational roles:

RootType: is inherited by the grammar root by default.

TypedDefinition: a definition that defines one or several typed objects.

TypedDefId: a defining occurrence of an identifier for a typed object.

TypedUseId: an applied occurrence of an identifier for a typed object.

ChkTypedUseId: checks that the key represents a typed object.

TypeDefDefId: a defining occurrence of a type identifer.

ChkTypeDefDefId: checks that a type identifer is defined acyclic.

TypeDefUseId: an applied occurrence of a type identifer.

ChkTypeDefUseId: checks that the key represents a type.

TypeDenotation: a type denotation.

At least the roles TypedDefinition, TypedDefId, TypedUseId, and TypeDefUseId are used. Those are sufficient for languages that have only predefined types. User defined types require additionally the use of TypeDenotation. For definitions of names that denote types TypeDefDefId and ChkTypeDefDefId are necessary.

The roles are described as follows:

TypedDefinition is a context where one or several typed objects are defined in its subtree, using the role TypedDefId. They all have the same type which is to be computed as the attribute THIS.Type.

TypedDefId is a defining occurrence of an identifier for a typed object. The TypeOf property is associated to THIS.KEYKey. It is obtained from the attribute INH.Type. A default computation for INH.Type is provided that takes the type from INCLUDING TypedDefinition.Type. Further properties may be associated to the object along with its type. Then the postcondition of their computation should be INH.GotProp which is empty by default. It guarantees that the property values can be accessed where the typed object is used.

An attribute SYNT.TypeIsSet represents the state where the TypeOf property of this TypedDefId has been set. The default computations of TypedDefId and TypedUseId are set up such that the TypeOf properties of all TypedDefId are set before any TypeOf property of a TypedUseId is accessed. In cases where a different dependency pattern is needed, it can be obtained by overriding the computations of SYNT.TypeIsSet in the two contexts. An example for a different dependency pattern is a left-to-right chain which may allow to set the type in a defining occurrence using applied occurrences that have been set before.

TypedUseId is an applied occurrence of an identifier for a typed object. The attribute SYNT.Type is the type associated to the object. The Defer chain is already walked down to the type key. See the explanation of the attribute SYNT.TypeIsSet for the role TypedDefId.

ChkTypedUseId checks that the key represents a typed object, and issues an error message otherwise.

Types are denoted either by a used occurrence of a type identifier, role TypeDefUseId, or by some composite TypeDenotation. Either role provides a Type attribute representing the denoted type.

TypeDefUseId is an applied occurrence of a type identifier. The Type attribute is its key which is related to the type key by the Defer relation.

ChkTypeDefUseId is usually inherited along with TypeDefUseId. It issues an error message if the identifier does not denote a type.

TypeDenotation characterizes a context that denotes a user defined type, e.g. an array type. The attribute SYNT.Type is a new, unique type key. If the type rules of the language state that a thus denoted type is equivalent to any other type, suitable functions that check for type equivalence have to be used.

Any property that further describes the type has to be associated to SYNT.Type by computations that establish the postcondition SYNT.GotType. (Its default is the empty postcondition.) It guarantees that the properties can be accessed when an object having that type is used (role TypedUseId).

TypeDefDefId is a defining occurrences of a type identifier. Its key is related by the Defer relation to the attribute THIS.Type. Its computation has to be provided.

ChkTypeDefDefId checks that its attribute THIS.Type refers to a type, and that the type does not refer to itself. It is usually inherited along with TypeDefDefId.

RootType is inherited by the grammar root by default. It ensures that all TypeOf and Defer properties are established when a Type attribute is computed. (The same holds for properties that are defined along with types.) That condition is provided by the condition attribute SYNT.GotType. Computations that access type properties, e.g. those which decompose composite types, have to be specified to depend on INCLUDING RootType.GotType.

Example for Basic Type Analysis

The use of the basic type module is demonstrated by introducing type analysis to our running example. A complete executable specification of our running example for the type analysis task is available in $/Type/Examples/Type.fw.

For the moment we assume the there are only three predefined types int, float, and bool. They are represented by the keys intType, realType, and boolType as introduced in (see Predefined Identifiers of Name analysis according to scope rules).

We add a .pdl specification which states that those keys are types and which defines the types of the constants true and false. A voidType is introduced here although there is no predefined identifier to denote it. It enables us to express that in certain contexts type coercion to voidType is to be applied.

  intType -> IsType = {1};
  realType -> IsType = {1};
  boolType -> IsType = {1};
  voidType -> IsType = {1};

  trueKey -> TypeOf = {boolType};
  falseKey-> TypeOf = {boolType};

  intKey -> Defer = {intType};
  realKey -> Defer = {realType};
  boolKey -> Defer = {boolType};
The last group relates the keys of the predefined type identifiers to the type keys. It is not recommended to use intKey etc. themselves as type keys. In that case it would not be possible to have different predefined type identifiers that could denote the same types.

The following association of module roles to grammar symbols in our .lido specification is obvious:

  SYMBOL DefIdent INHERITS TypedDefId END;
  SYMBOL UseIdent INHERITS TypedUseId, ChkTypedUseId END;
  SYMBOL TypeUseIdent INHERITS TypeDefUseId, ChkTypeDefUseId END;

Our language construct ObjDecl defines typed objects. It specifies that the Type attribute is taken from the TypeDenoter. Until now, a TypeDenoter can have only one form: TypeUseIdent. Its key is taken to represent the denoted type:

  SYMBOL ObjDecl INHERITS TypedDefinition END;
  RULE: ObjDecl ::= TypeDenoter DefIdent COMPUTE
    ObjDecl.Type = TypeDenoter.Type;
  END;

  RULE: TypeDenoter ::= TypeUseIdent COMPUTE
    TypeDenoter.Type = TypeUseIdent.Type;
  END;

  ATTR Type: DefTableKey;  
The last line allows us to use Type attributes without specifying their type repeatedly.

The types of literal expressions are then specified by

   RULE: Expression ::= IntNumber COMPUTE
     Expression.Type = intType;
   END;

   RULE: Expression ::= RealNumber COMPUTE
     Expression.Type = realType;
   END;

Computations like the following propagate type information upwards through adjacent contexts:

  RULE: Expression ::= Variable COMPUTE
    Expression.Type = Variable.Type;
  END;

  RULE: Variable ::= UseIdent COMPUTE
    Variable.Type = UseIdent.Type;
  END;

Occurrences of Expression in certain contexts require that their type is compatible to a type imposed by the context. Hence, we introduce an inherited attribute ReqType and a check for compatibility. The two rules demonstrate how such type requirements are imposed:

  SYMBOL Expression: ReqType: DefTableKey;
  SYMBOL Expression COMPUTE
    IF (NOT (Compatible (THIS.ReqType, THIS.Type)),
    message (FATAL, "expression does not have the required type",
             0, COORDREF));
  END;

  RULE: Statement ::=  Expression ';' COMPUTE
    Expression.ReqType = voidKey;
  END;

  RULE: Statement ::= Variable '=' Expression ';' COMPUTE
    Expression.ReqType = Variable.Type;
  END;

The function Compatible needs to be implemented according to the type rules of the particular language. (An example implementation is shown in $/Type/Examples/Type.fw).

We now introduce type definitions to our language. The concrete production is

   Declaration:    'type' TypeDenoter TypeDefIdent ';'.
   TypeDefIdent:   Ident.
From the view of language design such type definitions only make sense if there are other than the predefined types that can be given a name. In (see Properties of Types) it is demonstrated how to introduce some common types.

The TypeDefIdent has the role of a defining type identifier occurrence that renames the type of the TypeDenoter:

   SYMBOL TypeDefIdent INHERITS
          IdDefScope, IdentOcc,
          TypeDefDefId, ChkTypeDefDefId
   END;

   RULE: Declaration ::= 'type' TypeDenoter TypeDefIdent ';' COMPUTE
     TypeDefIdent.Type = TypeDenoter.Type;
   END;


Next Chapter Table of Contents