Oil Reference Manual
To explain the relationship between the specification and the abstract
data type we will examine different extractions from some possible
specifications and review the behavior of some of the related functions in
the abstract data type.
Each entity defined in an OIL specification is represented by a definition
The OIL value is accessible as a property of that definition table key.
For example, suppose that
iAdd was defined by an
statement in OIL.
This would result in a known key
(see How to specify the initial state of Definition Table)
iAdd would also have an
OilOp property whose value was the
actual OIL operator (of type
Thus the actual OIL operator corresponding to
iAdd could be obtained
by the function call
(see Behavior of the basic query operations of Definition Table).
In order to avoid the overhead of querying a property for constant
information, OIL also defines an identifier as the actual OIL operator.
This identifier is constructed by prefixing the OIL identifier with
Thus the identifier
OilOpiAdd denotes the value that would be
obtained from the function call
Similar conventions are used for OIL types and OIL classes:
The OIL identifier denotes a known key, and the actual OIL entity (of type
tOilClass respectively) is denoted by prefixing
OilClass to that identifier.
A known key denoting an OIL type also has an
and a known key denoting an OIL class has an
Let us consider the following OIL specification:
iAdd ( int_t, int_t ): int_t; /* the usual '+' operators for Pascal */
rAdd ( real_t, real_t ): real_t;
sUnion ( set_t, set_t ): set;
Plus: iAdd, rAdd, sUnion; /* will be identified together */
COERCION Float( int_t ): real_t; /* usual Pascal coercion from int to real */
All of the identifiers in this specification will denote values to the
library functions. The functions in the library will be applied to values
constructed from these identifiers and will return values represented by
The most basic operation is that of operator identification so we will
start there. When semantically analyzing a binary expression formed with a
plus sign (+), the compiler would use the function
OilIdOp2 applied to
the value denoted by Plus (which indicates the syntactic operator) and
the types of the operands to the plus sign.
OilIdOp2( Plus, int_t, real_t ) would return the operator
rAdd because int_t was coercible to real_t. Similarly :
Any combination of operand types like real_t and set_t
would return a value denoting an erroneous operator.
OilIdOp2( Plus, set_t, set_t ) would return
OilIdOp2( Plus, int_t, int_t ) would return
OilIdOp2( Plus, real_t, real_t ) would return
OilIsValidOp( OilIdOp2( Plus, real_t, set_t ) )
would return an integer value of 0.
Once we have identified an operator will need to know its type signature
so that we may return the type of the subexpression computed by the
operator and so we may determine the types required by the operator from
its respective operands. The function
OilGetArg gives us that facility.
OilGetArg( iAdd, 0 ) would return int_t
as the type of the result of the operator iAdd.
OilGetArg( sUnion, 1 ) would
return set_t as the required type of the first operand to the `sUnion'
Once we have the type returned by an operator and know the type required
of this sub-expression (from the parent context) we may need to apply a
sequence of coercions on the result of the operator to satisfy the requirements
of the parent context. The function
OilCoerce supplies the necessary
In the case of our example we might require a real_t result from an
iAdd operator (which returns int_t.)
OilCoerce( int_t, real_t )
would return a coercion sequence which represented the coercion of
an int_t type
value to a real_t type value. This coercion sequence (call it
cs) would then be analyzed with the functions: OilEmptyCS, OilHeadCS
and OilTailCS. The expression
OilHeadCS( cs ) would
evaluate to Float and
OilEmptyCS( OilTailCS( cs ) ) would evaluate to true. These expressions
describe the fact that only the coercion operator Float was necessary to
transform int_t to real_t.
If no coercions were necessary then
OilEmptyCS( cs ) would have yielded
the value true. Likewise to detect an impossible coercion, the function
OilIsValidCS would be used.
OilIsValidCS( OilCoerce( real_t, set_t ) )
would yield the value false to indicate that such a coercion
was not possible.
Not all operator identification schemes can be implemented with the
simple bottom-up type evaluation shown in the previous section. Sometimes
the desired result type will affect which operator denotation is
identified with a given operator indication. OIL supplies this capability
with the set of types operations.
Below is an example OIL specification which is designed
to use set of types.
The specification shows that there are two multiplication
sMulS sMulD) on type
One multiplication operator returns a double length result
double) the other returns a single length
single.) These declarations have a natural correspondence
with many machine architectures. The operator indication
Mul is defined
to identify either
sMulS ( single, single ): single;
sMulD ( single, single ): double;
dMulD ( double, double ): double;
COERCION iCvtStoD ( single ): double;
Mul: dMulD, sMulD, sMulS
To use type set functions we must begin with constructing the
possible result type set of a terminal. For this we use the function
OilTypeToSet. Like so:
OilTypeToSet( single ) yields
[ single, double ]
OilTypeToSet( double ) yields
[ double ]
For the rest of this example we will use the identifiers
ds to represent the type sets for single and double,
To analyze an entire expression with type sets we must
also be able to determine the set of types associated with
an operator indication and its set of operands. For this we use the
OilIdResultTS* functions. Like so:
OilIdResultTS2( Mul, ss, ss) yields
[ single, double ]
OilIdResultTS2( Mul, ds, ss) yields
[ double ]
OilIdResultTS2( Mul, ds, ds) yields
[ double ]
When we get to the root of an expression (like in an assignment) we would
then use a desired type determined from the context of the root of the
expression (like the destination type of the assignment)
to determine which operator we wanted to
select. For this we use the
OilIdOpTS* functions. Like so:
OilIdOpTS2( single, Mul, ss, ss) yields
OilIdOpTS2( double, Mul, ss, ss) yields
OilIdOpTS2( double, Mul, ds, ss) yields
By using type sets, the operator indication Mul with
single operands can identify sMulD,
thus directly producing a double result;
whereas with the simple scheme used previously (see A simple example.)
an additional coercion would be needed to return a double result.
There are three steps to using classes: (1)specifying them with OIL,
(2)instantiating them using the
OilClassInst* functions and
(3)identifying the enriched indication mappings with enriched coercion
The following OILspecification allows us to define any number of
Sets during compilation. And we specify the overloading of the
loPlus) operator to allow set union.
CLASS Set( element ) BEGIN
INDICATION loPlus: soIadd, soRadd, soUnion;
We can then construct a simple binary expression compiler which uses a
constant set for one of its possible operand types.
NONTERM Expr: op:tOilOp, st:tOilType;
RULE Dyadic: Expr::= Term Ind Term
Expr.st:= OilClassInst1( Set, Set_name, tInt );
Expr.op:= OilIdOp2( Ind.op, Term.type, Term.type )
NONTERM Term: type:tOilType;
RULE Set: Term::= 's'
STATIC Term.type:= INCLUDING Expr.st END;
RULE Integer: Term::= 'i'
STATIC Term.type:= tInt END;
RULE Real: Term::= 'r'
STATIC Term.type:= tReal END;
NONTERM Ind: op:tOilOp;
RULE Plus: Ind::= '+'
STATIC Ind.op:= loPlus END;
We use the following request to construct the compiler: