home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The World of Computer Software
/
World_Of_Computer_Software-02-387-Vol-3of3.iso
/
p
/
pascal-.zip
/
pascal-
/
specs
/
Code.fw
next >
Wrap
Text File
|
1993-01-05
|
27KB
|
770 lines
@=~
~A~<Code Generation~>
The purpose of code generation is to translate the concepts of the source
language into the concepts of the target machine.
No errors are detected or reported during code generation.
Code generation is an attribution process.
Given the information provided by type analysis, it
determines the amount of memory required to store objects of each type,
allocates storage to parameters and variables,
and then produces a target implementation of the algorithm.
~B
Every instruction output by the compiler consists of an operation part
followed by zero or more operands.
The operation parts are provided as literal strings by the templates
described in Chapter 8.
Each template has a name, and the code corresponding to the template is
generated by invoking a function whose name is ~{PTG~} followed by the
template name.
For example, code for the instruction ~{Variable(Level,Displ)~} is
generated by the call ~{PTGVariable(Level,Displ)~}.
The result of ~{PTGVariable(Level,Displ)~} is the generated code in the
form of a ~{PTGNode~} value that can be used as an argument to other code
generation functions.
The generated code is not actually emitted by the generation functions, as
it is by Brinch-Hansen's ~{Emit~} functions.
Instead, an explicit data structure is constructed that contains the
generated code.
This data structure is a tree, and it is actually output by passing its
root to the function ~{PTGOut~}:
~$~<Operation parts~>==~{
SYMBOL Program COMPUTE PTGOut(THIS.Code) END;
~}
~B~<Variable addressing~>
Variable access in the Pascal computer is described in Section 8.1:
The address generated by the compiler specifies the number of steps that
must be taken along the static chain to obtain the address of the proper
activation record, and the relative address of the variable within that
activation record.
To compute these two components, the compiler must gather information from
the program's declarations and store it in the definition table.
An address can then be generated by combining information about the object
with information determined from the context.
~C
The compiler associates a ~/static nesting level~/ with the program and
with each procedure.
It is 1 for the program, 2 for each procedure declared in the program, 3
for each procedure declared in a level-2 procedure, and so forth.
A ~{Contour~} is is a phrase that has a static nesting level associated
with it.
Each variable is then given a property whose value is the static nesting
level at which that variable was declared.
~$~<Static nesting level property~>==~{
Level: int;
~}
~$~<Determination of the static nesting level~>==~{
ATTR Level: int;
SYMBOL Contour COMPUTE INH.Level=ADD(INCLUDING Contour.Level,1); END;
SYMBOL Program INHERITS Contour COMPUTE INH.Level=1; END;
SYMBOL ProcedureBlock INHERITS Contour END;
~}
The number of steps that must be taken along the static chain to obtain the
address of the activation record containing a variable is the difference
between the static nesting level of the procedure containing the reference
and the static nesting level of the procedure in which the variable was
declared.
~$~<Static chain steps to find the proper activation record address~>~M==~{
PTGNumb(SUB(INCLUDING Contour.Level,GetLevel(VariableNameUse.Key,0)))
~}
There are two kinds of PTG functions: those that create data leaves and
those that create nodes without data.
Data can be stored only at data leaves, and a single data leaf can store
an arbitrary number of data items of arbitrary types.
The number of static chain steps to find the proper activation record
address is an integer datum that must be stored at a data leaf, and
~{PTGNumb~} is a library function that creates a data leaf holding a
single integer value.
This function is obtained by instantiating the generic library module
~{$/Tool/lib/Tech/LeafPtg.gnrc~}.
~C
In order to compute relative addresses within an activation record,
the compiler needs to know the size of each variable.
Brinch-Hansen, in his Algorithm 9.3, computes the size of each declared
object at the point of declaration from the definition of its type.
Thus the size of objects of a particular type is computed once for every
object declared to be of that type.
A better approach is to compute an appropriate size when a type is defined,
and then use a property of the type to make that size available
when the declaration of a new object of that type is encountered.
~$~<Storage requirement property~>==~{
Space: StorageRequired;
~}
Because all symbols must be defined before they are used in Pascal-,
storage requirements can be computed in textual order.
The sizes of the standard types ~{Boolean~} and ~{integer~} are determined
by the specification, while the sizes of user-defined types are determined
by the compiler.
~$~<Determination of storage requirements~>+=~{
CHAIN Storage: VOID;
SYMBOL StandardBlock COMPUTE
CHAINSTART HEAD.Storage=
ORDER(
SetSpace(IntegerKey,NewStorage(1,1,0),NoStorage),
SetSpace(BooleanKey,NewStorage(1,1,0),NoStorage));
END;
~}
In the Pascal computer, each value of standard type occupies one memory
location.
An array type requires storage equal to the number of elements times the
storage requirement of a single element.
~$~<Determination of storage requirements~>+=~{
RULE ArrayDef: NewArrayType ::= 'array' '[' IndexRange ']' 'of' TypeNameUse
COMPUTE
NewArrayType.Storage=
SetSpace(
INCLUDING NewType.Key,
ArrayStorage(
ADD(SUB(IndexRange.UpperBound,IndexRange.LowerBound),1),
GetSpace(TypeNameUse.Type,NoStorage)),
NoStorage)
DEPENDS_ON NewArrayType.Storage;
END;
~}
~{ArrayStorage~} is an operation exported by the data mapping module of the
Eli library.
It computes the storage requirements for an array of objects whose
individual storage requirements are known.
The first argument of ~{ArrayStorage~} must be the number of elements in
the array, and the second must be the storage requirements of the element
type.
The data mapping module is an abstract data type, not a generic module.
Therefore it need not be instantiated, but
its interface must be made available:
~$~<Data mapping module interface~>==~{
#include "storage.h"
~}
Record types are more complex, because in addition to determining the total
storage requirement of the record, the compiler must assign a relative
address to each field.
The compiler begins by establishing an empty storage area for the record.
It then processes the field definitions in textual order, concatenating the
storage required by each field to the record's storage area.
The concatenation operation ~{Concatenate~} is exported by the data mapping
module.
It takes two arguments: the storage requirements of the area to which the
field must be added and the storage requirements of the field type.
The function returns the address of the field relative to
the beginning of the record, and updates the storage requirement of the
area to reflect the addition of the field.
The relative address returned by ~{Concatenate~} is stored as the ~{Displ~}
property of the field object (see the next section).
~$~<Determination of storage requirements~>+=~{
SYMBOL NewRecordType: Area: StorageRequired;
RULE RecordDef: NewRecordType ::= 'record' FieldList 'end'
COMPUTE
NewRecordType.Area=NewStorage(0,1,0) DEPENDS_ON NewRecordType.Storage;
NewRecordType.Storage=
SetSpace(INCLUDING NewType.Key,NewRecordType.Area,NoStorage);
END;
SYMBOL FieldNameDef COMPUTE
THIS.Storage=
SetDispl(
THIS.Key,
Concatenate(INCLUDING NewRecordType.Area,INCLUDING Group.Space),
0)
DEPENDS_ON THIS.Storage;
END;
~}
Objects (variables, parameters, fields) are declared in groups in Pascal-:
If several objects of the same type are being declared, the type is only
stated once.
This mechanism requires that the storage requirement of the type be made
the value of an attribute of the phrase representing the group of objects,
so that it will be available at all of the object declarations:
~$~<Distributing a storage requirement~>==~{
ATTR Space: StorageRequired;
SYMBOL Group COMPUTE
SYNT.Space=
GetSpace(CONSTITUENT TypeNameUse.Type,NoStorage)
DEPENDS_ON THIS.Storage;
END;
SYMBOL VariableDefinition INHERITS Group END;
SYMBOL ParameterDefinition INHERITS Group END;
SYMBOL RecordSection INHERITS Group END;
~}
~C
The relative address of an object within its storage area (a record for
fields, an activation record for parameters and variables)
is a property of that object.
~$~<Relative address property~>==~{
Displ: int;
~}
A relative address is computed from an object's declaration,
in the process of allocating space in a storage area.
Allocation for field objects was specified in the last section,
and allocation for objects in activation records is similar.
The major difference is that an activation record has more structure than a
record type.
Three kinds of information are stored in an activation record:
parameters, local variables, and ~/overhead~/ (information like the static
chain, needed to maintain the relationships among activation records).
Parameter and local variable storage is determined by the declarations for
the ~{Contour~}, but storage for the overhead is determined by the target
computer.
Overhead information is maintained as a side effect of executing the
instructions of the Pascal computer, and hence its size and placement
depend on the implementation of those instructions.
Brinch-Hansen, in Section 8.2 of his book, defines the overhead information
to be three storage locations at the beginning of the storage allocated for
variables.
The first location is the static chain, holding the address of the
activation record of the enclosing procedure, the second location is the
~/dynamic chain~/, holding the contents of the base register during
execution of the caller, and the third location is the ~/return address~/,
holding the location of the instruction at which execution should resume
when the current procedure terminates.
In the C implementation of the Pascal computer that accompanies this
specification, there is no explicit return address.
The third location of the overhead information is therefore free to hold
the address of the parameter portion of the activation record.
~$~<Storage allocation~>+=~{
SYMBOL Contour: Parameters, Variables: StorageRequired;
SYMBOL Contour COMPUTE
INH.Parameters=NewStorage(0,1,0);
INH.Variables=NewStorage(3,1,0);
END;
~}
Parameter and variable objects have the ~{Displ~} property, just like field
objects, but they also have the ~{Level~} property to record the static
nesting level of the contour in which they are declared.
Also, the storage requirement for a variable parameter is independent of
its type because only the address of the parameter's value is stored.
An address occupies one storage unit of the Pascal computer.
~$~<Storage allocation~>+=~{
RULE VarParmDef:
ParameterDefinition ::= 'var' ParameterNameDefList ':' TypeNameUse
COMPUTE
ParameterDefinition.Space=NewStorage(1,1,0);
END;
SYMBOL VariableNameDef COMPUTE
THIS.Storage=
ORDER(
SetDispl(
THIS.Key,
Concatenate(INCLUDING Contour.Variables,INCLUDING Group.Space),
0),
SetLevel(THIS.Key,INCLUDING Contour.Level,0))
DEPENDS_ON THIS.Storage;
END;
SYMBOL ParameterNameDef COMPUTE
THIS.Storage=
ORDER(
SetDispl(
THIS.Key,
Concatenate(INCLUDING Contour.Parameters,INCLUDING Group.Space),
0),
SetLevel(THIS.Key,INCLUDING Contour.Level,0))
DEPENDS_ON THIS.Storage;
END;
~}
~C
Object access code pushes the address of the desired object onto the
processor's stack in the Pascal computer.
Access to an object of one of the standard types is specified in Pascal- by
giving the name of the object, while access to an object of a user-defined
type may involve subscripting or field selection.
In that case the address of the object (array or record) containing the
desired object is placed on the stack and then the appropriate selection
operations are executed.
Section 8.1 pointed out the differences among the accessing operations for
variables, value parameters and variable parameters.
In addition, as noted in Section 2.3.2, a ~{VariableNameUse~} may actually
be a constant name.
Since constants are not stored in memory, the code generated for a constant
name must push the constant itself, not the address of the constant, onto
the processor's stack.
~$~<Accessing an object~>==~{
RULE VarIdn: VariableAccess ::= VariableNameUse
COMPUTE
VariableAccess.Code=
IF(EQ(VariableAccess.Kind,Constantx),
PTGConstant(PTGNumb(GetValue(VariableNameUse.Key,0))),
IF(EQ(VariableAccess.Kind,ValueParameter),
PTGValParam(
~<Static chain steps to find the proper activation record address~>,
PTGNumb(GetDispl(VariableNameUse.Key,0))),
IF(EQ(VariableAccess.Kind,VarParameter),
PTGVarParam(
~<Static chain steps to find the proper activation record address~>,
PTGNumb(GetDispl(VariableNameUse.Key,0))),
PTGVariable(
~<Static chain steps to find the proper activation record address~>,
PTGNumb(GetDispl(VariableNameUse.Key,0))))));
END;
RULE Index: VariableAccess ::= VariableAccess '[' Expression ']'
COMPUTE
VariableAccess[1].Code=
PTGIndex(
VariableAccess[2].Code,
Expression.Code,
PTGNumb(GetLowerBound(VariableAccess[2].Type,0)),
PTGNumb(GetUpperBound(VariableAccess[2].Type,0)),
PTGNumb(
StorageSize(
GetSpace(GetElementType(VariableAccess[2].Type,NoKey),NoStorage))),
PTGNumb(LINE));
END;
RULE Select: VariableAccess ::= VariableAccess '.' FieldNameUse
COMPUTE
VariableAccess[1].Code=
PTGField(
VariableAccess[2].Code,
PTGNumb(GetDispl(FieldNameUse.Key,0)));
END;
~}
~B
Code for an expression always leaves the value of the expression at the top
of the processor's stack.
If the expression is a ~{Numeral~} or a ~{VariableAccess~} then that
value is obtained from either an operation or memory, respectively.
Because a ~{VariableAccess~} may actually be a reference to a constant, the
compiler must check its ~{Kind~} to determine whether the value at the top of
the processor's stack is a value or an address at which a value can be found.
When the top element of the processor's stack is an address, the compiler
must emit a ~{Value~} operation to obtain the contents of that address if
the context demands a value.
~$~<Expression code~>+=~{
RULE Denotation: Expression ::= Numeral
COMPUTE
Expression.Code=PTGConstant(PTGNumb(Numeral.Value));
END;
RULE VariableExpr: Expression ::= VariableAccess
COMPUTE
Expression.Code=
IF(OR(Expression.ExpectedVar,EQ(VariableAccess.Kind,Constantx)),
VariableAccess.Code,
PTGValue(
VariableAccess.Code,
PTGNumb(StorageSize(GetSpace(VariableAccess.Type,NoStorage)))));
END;
~}
If the expression is neither a ~{Numeral~} nor a ~{VariableAccess~} then
its value is obtained by applying the appropriate operator to one or two
operands.
The code generation process is independent of the particular operator,
which is supplied by the operator child of the expression,
although it does depend on the number of operands.
LIDO does not allow an attribute to be applied as a function, however, so a
circumlocution is necessary:
~$~<Expression code~>+=~{
ATTR Op: PtgFunc;
RULE Monadic: Expression ::= Unop Expression
COMPUTE
Expression[1].Code=Apply1(Unop.Op,Expression[2].Code);
END;
RULE Dyadic: Expression ::= Expression Binop Expression
COMPUTE
Expression[1].Code=Apply2(Binop.Op,Expression[2].Code,Expression[3].Code);
END;
~}
~{PtgFunc~}, ~{Apply1~} and ~{Apply2~} are defined in C:
~$~<PTG functions as attributes~>==~{
typedef PTGNode (*PtgFunc)();
#define Apply1(f,x) ((*f)(x))
#define Apply2(f,x,y) ((*f)(x,y))
~}
These definitions say that ~{PtgFunc~} objects are functions that return
PTG nodes, ~{Apply1~} applies its first argument to its second, and
~{Apply2~} applies its first argument to its second and third.
All of the operator rules have exactly the same form, defining the ~{Op~}
attribute of the left-hand side as the appropriate PTG function:
~$~<Operator encoding~>~(~3~)~M==~{
RULE r~1: ~2 ::= ~3 COMPUTE ~2.Op=PTG~1 END;
~}
~$~<Expression code~>+=~{
~<Operator encoding~>~(Lss~,Binop~,'<'~)
~<Operator encoding~>~(Leq~,Binop~,'<='~)
~<Operator encoding~>~(Gtr~,Binop~,'>'~)
~<Operator encoding~>~(Geq~,Binop~,'>='~)
~<Operator encoding~>~(Equ~,Binop~,'='~)
~<Operator encoding~>~(Neq~,Binop~,'<>'~)
~<Operator encoding~>~(Add~,Binop~,'+'~)
~<Operator encoding~>~(Sub~,Binop~,'-'~)
~<Operator encoding~>~(Mul~,Binop~,'*'~)
~<Operator encoding~>~(Div~,Binop~,'div'~)
~<Operator encoding~>~(Mod~,Binop~,'mod'~)
~<Operator encoding~>~(Neg~,Unop~,'-'~)
~<Operator encoding~>~(Nop~,Unop~,'+'~)
~<Operator encoding~>~(And~,Binop~,'and'~)
~<Operator encoding~>~(Or~,Binop~,'or'~)
~<Operator encoding~>~(Not~,Unop~,'not'~)
~}
~B
A ~{Statement~} is a sequence of operations, which may be empty.
The processor's stack is always empty before the operations of a statement
are executed and after their execution is complete.
Thus a statement may affect the state of the memory or external devices,
but it neither expects nor yields a value.
For example, the assignment statement consists of a sequence of operations
that push the address of the variable to be assigned onto the processor's
stack, push the value to be assigned, and then carry out the assignment
removing both elements from the processor's stack.
~$~<Statement code~>+=~{
RULE Empty: Statement ::=
COMPUTE
Statement.Code=PTGNULL;
END;
RULE Assign: Statement ::= VariableAccess ':=' Expression
COMPUTE
Statement.Code=
PTGAssignmentStatement(
VariableAccess.Code,
Expression.Code,
PTGNumb(StorageSize(GetSpace(VariableAccess.Type,NoStorage))));
END;
RULE Compound: Statement ::= CompoundStatement
COMPUTE
Statement.Code=CompoundStatement.Code;
END;
SYMBOL CompoundStatement COMPUTE
SYNT.Code=
CONSTITUENTS Statement.Code
SHIELD Statement
WITH (PTGNode, PTGSequence, IDENTICAL, PTGNull);
END;
~}
The ~{CONSTITUENTS~} construct gathers all of the ~{Code~} attributes of
component ~{Statement~} nodes, using ~{PTGSequence~} to combine them
pairwise.
Note, however, that many ~{Statement~} nodes are actually the roots of
subtrees that contain ~{Statement~} nodes themselves.
The ~{SHIELD~} clause prevents the ~{CONSTITUENTS~} construct from
gathering the ~{Code~} attributes of the ~{Statement~} nodes within such
subtrees.
Statements that alter the normal sequence of operations must be able to
nominate a successor to the current operation.
Section 8.3 described how operations can be named via the ~{DefAddr~}
pseudo-operation.
The compiler must generate these names; it does so by obtaining a sequence
of unique integers from the ~{NewInteger~} module:
~$~<Unique integers~>==~{
static int Next = 1; /* Next integer to be delivered */
int NewInteger() { return Next++; }
~}
The number of labels needed by the statement depends on its internal
structure, described in Section 8.3.
~$~<Statement code~>+=~{
RULE While: Statement ::= 'while' Expression 'do' Statement
COMPUTE
Statement[1].Code=
PTGWhileStatement(
PTGNumb(NewInteger()),
Expression.Code,
Statement[2].Code,
PTGNumb(NewInteger()));
END;
RULE OneSided: Statement ::= 'if' Expression 'then' Statement
COMPUTE
Statement[1].Code=
PTGOneSided(
Expression.Code,
Statement[2].Code,
PTGNumb(NewInteger()));
END;
RULE TwoSided: Statement ::= 'if' Expression 'then' Statement 'else' Statement
COMPUTE
Statement[1].Code=
PTGTwoSided(
Expression.Code,
Statement[2].Code,
PTGNumb(NewInteger()),
Statement[3].Code,
PTGNumb(NewInteger()));
END;
~}
~B
Most of the code for a procedure definition is generated by its children.
The storage requirements must be summarized and a label generated so that
invocations can refer to the procedure.
~$~<Procedure code~>+=~{
ATTR Label: int;
RULE ProcDef:
ProcedureDefinition ::= 'procedure' ProcedureNameDef ProcedureBlock ';'
COMPUTE
.Label=NewInteger();
ProcedureDefinition.Code=
PTGProcedureDefinition(
PTGNumb(StorageSize(ProcedureBlock.Parameters)),
PTGNumb(StorageSize(ProcedureBlock.Variables)),
PTGNumb(0), /*Temp size*/
PTGNumb(.Label),
PTGNumb(LINE),
CONSTITUENTS ProcedureDefinition.Code
SHIELD ProcedureDefinition
WITH (PTGNode, PTGSequence, IDENTICAL, PTGNull),
CONSTITUENTS CompoundStatement.Code
SHIELD (ProcedureDefinition, CompoundStatement)
WITH (PTGNode, PTGSequence, IDENTICAL, PTGNull))
DEPENDS_ON ProcedureBlock.Storage;
ProcedureBlock.Storage=
ORDER(
SetLevel(ProcedureNameDef.Key,ProcedureBlock.Level,0),
SetLabel(ProcedureNameDef.Key,.Label,0))
DEPENDS_ON ProcedureDefinition.Storage;
END;
~}
The ~{SHIELD~} clauses in this rule avoid duplication of code:
~{CONSTITUENTS CompoundStatement.Code~} would gather the bodies of nested
procedures if it could penetrate the subtrees rooted in
~{ProcedureDefinition~} nodes.
In addition to the ~{Level~} property that it shares with other objects, a
procedure name must have the procedure's label as a property.
~$~<Label property~>==~{
Label: int;
~}
The label property is accessed at the procedure call.
Read and write statements in Pascal- have the form of procedure calls, but
they are implemented by distinct operations of the Pascal computer.
They must therefore be recognized specially during code generation.
When a user-defined procedure is invoked, it must be provided with the
address of the activation record of the procedure in which it was declared.
That address can be found by stepping along the static chain, exactly as in
the case of variable addressing.
There is a small difference, however:
The number of steps is one larger than the difference between the current
static nesting level and the static nesting level of the procedure being
called.
The reason is that that address sought is the activation record address for
the procedure in which the called procedure was declared; the called
procedure itself has no activation record until the ~{ProcCall~} operation
(Section 8.4) has been executed.
~$~<Steps to find the called procedure's environment~>==~{
PTGNumb(
ADD(
SUB(INCLUDING Contour.Level,GetLevel(ProcedureNameUse.Key,0)),
1))
~}
~$~<Procedure code~>+=~{
RULE Call: Statement ::= ProcedureNameUse ActualParameterList
COMPUTE
.Code=CONSTITUENTS Expression.Code SHIELD Expression
WITH (PTGNode, PTGSequence, IDENTICAL, PTGNull);
Statement.Code=
IF(EQ(ProcedureNameUse.Key,ReadKey),
PTGReadStatement(.Code),
IF(EQ(ProcedureNameUse.Key,WriteKey),
PTGWriteStatement(.Code),
PTGProcedureStatement(
.Code,
~<Steps to find the called procedure's environment~>,
PTGNumb(GetLabel(ProcedureNameUse.Key,0)))))
DEPENDS_ON Statement.Storage;
END;
~}
The ~{SHIELD~} clause forces the ~{CONSTITUENTS~} construct to gather the
~{Code~} attributes only from the argument expressions, and not from any of
their components.
~B
The code for the program definition is almost identical to the code for a
procedure definition.
There are no parameters for the program, and since the program is not
invoked there is no need for a ~{Label~} property.
~$~<Program code~>==~{
RULE Source: Program ::= 'program' ProgramName ';' BlockBody '.'
COMPUTE
Program.Code=
PTGText(
PTGProgram(
PTGNumb(StorageSize(Program.Variables)),
PTGNumb(0), /*Temp size*/
PTGNumb(LINE),
CONSTITUENTS CompoundStatement.Code
SHIELD (ProcedureDefinition,CompoundStatement)
WITH (PTGNode, PTGSequence, IDENTICAL, PTGNull)),
CONSTITUENTS ProcedureDefinition.Code
SHIELD ProcedureDefinition
WITH (PTGNode, PTGSequence, IDENTICAL, PTGNull));
END;
~}
The ~{SHIELD~} clauses prevent duplicate code:
~{CONSTITUENTS CompoundStatement.Code~} would gather the bodies of all
nested procedures as well as the body of the program if it were allowed to
penetrate the subtrees rooted in ~{ProcedureDefinition~} nodes.
~B~<Specification files for code generation~>
Five kinds of specifications are needed to define the Pascal- code
generation problem to Eli.
~C
A type-~{lido~} file describes the attribution needed to gather the
information and relate the generated code fragments.
Eli will merge these attributions with others specified in this document
and create a tree traversal algorithm.
~O~<code.lido~>~{
ATTR Code: PTGNode;
~<Operation parts~>
~<Determination of the static nesting level~>
~<Determination of storage requirements~>
~<Distributing a storage requirement~>
~<Storage allocation~>
~<Accessing an object~>
~<Expression code~>
~<Statement code~>
~<Procedure code~>
~<Program code~>
~}
~C
A type-~{c~} file describes a problem by giving its solution.
In the case of the code generation, the problem was that of generating
unique labels.
~O~<code.c~>~{
#include "code.h"
~<Unique integers~>
~}
~C
A type-~{h~} file gives the interface for the module described by the
type-~{c~} file.
By convention, an interface file is included if it is needed explicitly.
This can lead to multiple inclusions, so every interface file must protect
itself against this possibility as indicated.
Conventionally, the symbol used is the upper-case version of the file name,
with ``.'' replaced by ``_''.
~O~<code.h~>~{
#ifndef CODE_H
#define CODE_H
extern int NewInteger();
#endif
~}
~C
A type-~{head~} file places CPP directives into the generated attribution
routine.
The specification of the PTG functions as attributes needs
the definition of ~{PTGNode~}, which is given by ~{ptg_gen.h~}.
~O~<code.head~>~{
#include "code.h"
~<Data mapping module interface~>
#include "ptg_gen.h"
~<PTG functions as attributes~>
~}
~C
A type-~{pdl~} file defines properties.
If any of the properties are objects whose types are not basic data types
of C, the type-~{pdl~} file must contain a string naming an interface file
where the properties' types are defined.
Some of the properties defined here are of type ~{StorageRequirement~},
which is defined in the file ~{storage.h~}.
~O~<code.pdl~>~{
~<Static nesting level property~>
~<Relative address property~>
"storage.h"
~<Storage requirement property~>
~<Label property~>
~}