10. Procedure tokens
- 10.1. General procedure tokens
- 10.2. Simple procedure tokens
- 10.3. Function procedure tokens
- 10.4. Defining procedure tokens
Consider the macro SWAP
defined below:
#define SWAP(T, A, B) { \ T x; \ x=B; \ B=A; \ A=x; \ }
SWAP
can be thought of as a statement that is parameterised by a type and two expressions.
Procedure tokens are based on this concept of parameterisation. Procedure tokens reference program constructs that are parameterised by other program constructs.
There are three methods of introducing procedure tokens. These are described in the sections below.
10.1. General procedure tokens
The syntax for introducing a general procedure token is:
general procedure: PROC { bound-toks? | prog-pars? } token-introduction simple procedure: PROC ( bound-toks? ) token-introduction bound-toks: bound-token bound-token, bound-toks bound-token: token-introduction name-space?identifier prog-pars: program-parameterprogram-parameter, prog-pars program parameter: EXP identifier STATEMENT identifier TYPE type-name-identifier MEMBER type-name-identifier : identifier
The final token-introduction specifies the kind of program construct being parameterised. In the current implementation of the compiler, only expressions and statements may be parameterised. The internal procedure token identifier is placed in the default name space of the program construct which it parameterises. For example, the internal identifier of a procedure token parameterising an expression would be placed in the macro name space.
The bound-toks
are the bound token dependencies which describe the program constructs upon which the procedure token depends. These should not be confused with the parameters of the token. The procedure token introduced in:
#pragma token PROC {TYPE t,EXP rvalue:t**:e|EXP e} EXP:rvalue:t:dderef#
is intended to represent a double dereference and depends upon the type of the expression to be dereferenced and upon the expression itself but takes only one argument, namely the expression, from which both dependencies can be deduced.
The bound token dependencies are introduced in exactly the same way as the tokens described in the previous sections with the identifier corresponding to the internal identification of the token. No external identification is allowed. The scope of these local identifiers terminates at the end of the procedure token introduction, and whilst in scope, they hide all other identifiers in the same name space. Such tokens are referred to as bound
because they are local to the procedure token.
Once a bound token dependency has been introduced, it can be used throughout the rest of the procedure token introduction in the construction of other components.
The prog-pars are the program parameters. They describe the parameters with which the procedure token is called. The bound token dependencies are deduced from these program parameters.
Each program parameter is introduced with a keyword expressing the kind of program construct that it represents. The keywords are as follows:
EXP
-
The parameter is an expression and the identifier following
EXP
must be the identification of a bound token for an expression. When the procedure token is called, the corresponding parameter must be an assignment-expression and is treated as the definition of the bound token, thereby providing definitions for all dependencies relating to that token. For example, the call of the procedure token dderef, introduced above, in the code below:char f(char **c_ptr_ptr) { return dderef(c_ptr_ptr); }
causes the expression, e, to be defined to be
c_ptr_ptr
thus resolving the typet**
to bechar **
. The typet
is hence defined to bechar
, also providing the type of the expression obtained by the application of the procedure tokendderef
; STATEMENT
-
The parameter is a statement. Its semantics correspond directly to those of
EXP
; TYPE
-
The parameter is a type. When the procedure token is applied, the corresponding argument must be a
type-name
. The parameter type is resolved to the argument type in order to define any related dependencies; MEMBER
-
The parameter is a member selector. The
type-name
specifies the composite type to which the member selector belongs and the identifier is the identification of the member selector. When the procedure token is applied, the corresponding argument must be a member-designator of the compound type.
Currently PROC
tokens cannot be passed as program parameters.
10.2. Simple procedure tokens
In cases where there is a direct, one-to-one correspondence between the bound token dependencies and the program parameters a simpler form of procedure token introduction is available.
Consider the two procedure token introductions below, corresponding to the macro SWAP
described earlier.
/* General procedure introduction */ #pragma token PROC{TYPE t,EXP lvalue:t:e1,EXP lvalue:t:e2 | \ TYPE t,EXP e1,EXP e2 } STATEMENT SWAP# /* Simple procedure introduction */ #pragma token PROC(TYPE t,EXP lvalue:t:,EXP lvalue:t: ) STATEMENT SWAP#
The simple-token syntax is similar to the bound-token syntax, but it also introduces a program parameter for each bound token. The bound token introduced by the simple-token syntax is defined as though it had been introduced with the bound-token syntax. If the final identifier is omitted, then no name space can be specified, the bound token is not identified and in effect there is a local hidden identifier.
10.3. Function procedure tokens
One of the commonest uses of simple procedure tokens is to represent function in-lining. In this case, the procedure token represents the in-lining of the function, with the function parameters being the program arguments of the procedure token call, and the program construct resulting from the call of the procedure token being the corresponding in-lining of the function. This is a direct parallel to the use of macros to represent functions.
The syntax for introducing function procedure tokens is:
function-procedure: FUNC type-name :
The type-name must be a prototyped function type. The pragma results in the declaration of a function of that type with external linkage and the introduction of a procedure token suitable for an in-lining of the function. (If an ellipsis is present in the prototyped function type, it is used in the function declaration but not in the procedure token introduction.) Every parameter type and result type is mapped onto the token introduction:
EXP rvalue:
The example below:
#pragma token FUNC int(int): putchar#
declares a function, putchar
, which returns an int
and takes an int
as its argument, and introduces a procedure token suitable for in-lining putchar. Note that:
#undef putchar
will remove the procedure token but not the underlying function.
10.4. Defining procedure tokens
All procedure tokens are defined by the same mechanism. Since simple and function procedure tokens can be transformed into general procedure tokens, the definition will be explained in terms of general procedure tokens.
The syntax for defining procedure tokens is given below and is based upon the standard parameterised macro definition. However, as in the definitions of expressions and statements, the #defines
of procedure token identifiers are evaluated in phase 7 of translation as described in the ISO C standard.
#define identifier ( id-list? ) assignment-expression #define identifier ( id-list? ) statement id-list: identifieridentifer, id-list
The id-list must correspond directly to the program parameters of the procedure token introduction. There must be precisely one identifier for each program parameter. These identifiers are used to identify the program parameters of the procedure token being defined and have a scope that terminates at the end of the procedure token definition. They are placed in the default name spaces for the kinds of program constructs which they identify.
None of the bound token dependencies can be defined during the evaluation of the definition of the procedure token since they are effectively provided by the arguments of the procedure token each time it is called. To illustrate this, consider the example below based on the dderef token used earlier.
#pragma token PROC{TYPE t, EXP rvalue:t**:e|EXP e}EXP rvalue:t:dderef# #define dderef (A) (**(A))
The identifiers t
and e
are not in scope during the definition, being merely local identifiers for use in the procedure token introduction. The only identifier in scope is A
. A
identifies an expression token which is an rvalue whose type is a pointer to a pointer to a type token. The expression token and the type token are provided by the arguments at the time of calling the procedure token.
Again, the presence of a procedure token introduction can alter the semantics of a program. Consider the program below.
#pragma token PROC {TYPE t, EXP lvalue:t:,EXP lvalue:t:}STATEMENT SWAP# #define SWAP(T, A, B) { \ T x; \ x = B; \ B = A; \ A = x; \ } void f(int x, int y) { SWAP(int, x, y) }
Function procedure tokens are introduced with tentative implicit definitions, defining them to be direct calls of the functions they reference and effectively removing the in-lining capability. If a genuine definition is found later in the compilation, it overrides the tentative definition. An example of a tentative definition is shown below:
#pragma token FUNC int(int, long) : func# #define func(A, B) (func) (A, B)