REF DEFSTRUCT John Gibson Aug 1992
COPYRIGHT University of Sussex 1992. All Rights Reserved.
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<< POP-11 SYNTAX FOR DEFINING >>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<< AND ACCESSING STRUCTURES >>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
The file REF * KEYS describes the procedures conskey and cons_access,
which allow the construction of new user-defined record or vector
classes and/or procedures to access the data within them. This file
describes the associated Pop-11 syntax interface to the same facilities
(which in normal programming contexts allows them to be used in a more
convenient way). Information is given on defclass which provides for
making new Poplog record and vector classes and the procedures to access
their fields. Details are also given of defining externally accessing
procedures.
CONTENTS - (Use <ENTER> g to access required sections)
1 Introduction
2 Basic Field-Type Specifications
2.1 N.B.
3 Defining New Record/Vector Classes
3.1 Examples
3.2 Note on Recompiling Record and Vectors
4 Declaring New Field Types
4.1 Examples
5 Field Value Conversion Procedures
6 External Data Accessing: Overview
6.1 Another N.B.
7 External Compound Types
7.1 Structures
7.2 Overlaid Fields in Structures
7.3 Arrays
7.4 Functions
8 Defining External Access Procedures
8.1 Example
9 Implicit Access Procedures in External Type Specs
10 Implicit Type Access and Typing on External Pointers
10.1 Pointer Typing
11 In-Line Code for External Access
11.1 Examples
12 Address Mode Accessing
13 Updating External Data: Non-Writeable Types
13.1 General Points
13.2 Using "!"
13.3 Compound Types
14 Pointer Values as Data
15 External Structure Fields in Records
16 Summary of typespec Syntax
17 Notes on Efficiency
18 Miscellaneous
---------------
1 Introduction
---------------
The construct defclass provides for making new Poplog record and vector
classes and procedures for accessing their fields.
For external data, i.e. that referenced by external pointer class
records, access code can either be generated in-line with the construct
exacc, or produced as procedures with defexacc (this includes calling
external functions, which is a special kind of data access).
These constructs require data types to be specified for the fields
within the structures being defined (i.e. whether a field can contain
any Poplog item, a packed integer field of a given size, a
floating-point quantity, etc). While the basic allowable field values
are governed by a set of built-in types, 'conversion' procedures can be
added on top of basic types to enable field values to be converted
automatically in any appropriate way.
New types including such procedures can then be declared with the
constructs p_typespec or l_typespec (where the different prefixes
control the scope of the declaration).
----------------------------------
2 Basic Field-Type Specifications
----------------------------------
Throughout this file, the meta-notation <typespec> is used to indicate a
type specification for a data field. Later sections will elaborate this
in full, but for now we just define the basic forms. The simplest
<typespec> is a colon followed by a <basetype>, i.e.
<typespec> --> :<basetype>
The values for <basetype> are given below (these are described only
informally here; for a fuller description see Field Specifiers for
Poplog Structures in REF * KEYS):
Type Field Value
---- -----------
full Any Poplog item
word Signed integer (natural wordsize of machine)
uword Unsigned integer (natural wordsize of machine)
pint As "word", but value within Poplog simple int
long Signed integer (as C type 'long')
ulong Unsigned integer (as C type 'unsigned long')
int Signed integer (as C type 'int')
uint Unsigned integer (as C type 'unsigned int')
short Signed integer (as C type 'short')
ushort Unsigned integer (as C type 'unsigned short')
sbyte Signed byte
byte Unsigned byte
-N Signed field of integer N bits
N Unsigned field of integer N bits
dfloat Double length float-point
sfloat Single length floating-point
float As "sfloat", except for external function
results (see REF * EXTERNAL
exptr Pointer to external data (m/c wordsize)
exval Any external data value (m/c wordsize)
Thus for example, ':full' specifies a field to contain any Poplog item,
while ':-17' specifies a signed integer bitfield of 17 bits.
The next <typespec> form defines the layout of a structure, that is
an aggregate of (zero or more) fields in a given order. Each field has
an identifying name and a individual <typespec> to say what it can hold:
together these two form a <fieldspec>, i.e.
<fieldspec> --> <fieldname> <typespec>
The syntax for the overall structure <typespec> is then
<typespec> --> { <fieldspec-1>, <fieldspec-2>, ..., <fieldspec-N> }
that is, a comma-separated list of <fieldspec>s contained in curly
brackets. For example:
{ person_name :full,
person_address :full,
person_age :byte,
person_sex :1
}
specifies a 4-field structure.
Note that, for Poplog records and vectors only (i.e. NOT for
external structures), a <typespec> may also be empty to indicate a
'full' field. Thus
{ person_name, person_address, person_age:byte, person_sex:1 }
is equivalent to the above. The <fieldname> may also be omitted for any
field: this is generally only useful for external structures, where it
prevents the generation of an access procedure for that field.
2.1 N.B.
---------
A structure may also contain one occurrence of the special ">->"
fieldname, with no associated <typespec>, e.g.
{ field1, >-> field2:int }
etc. This is relevant only for a record class structure which is
required to be used by external procedures; the 'pointer' symbol >->
causes the fields following it to be allocated starting at zero offset
from the structure pointer. See "Format of Data Structures" in
REF * DATA and "External Structure Specification" below.
-------------------------------------
3 Defining New Record/Vector Classes
-------------------------------------
Two distinct kinds of new Poplog object classes can be constructed:
record-class and vector-class. A record is a structure containing a
fixed number of distinct and possibly different fields, whereas a vector
consists of a variable number of similar fields. (For example, a pair is
a record-class, whereas standard full vectors and strings are
vector-types. The built-in classes in the system also include other
types which do not fall into these categories and which cannot be
user-defined, e.g. keys, procedures, processes, etc.)
defclass [syntax]
Used to construct new record and vector classes. It uses conskey
to create a new key structure for the class being defined (see
REF * KEYS), and then assigns to identifiers both the key itself
and all the "class_" procedures it contains.
The construct has the form
defclass <declaration> <dataword> <attributes> <typespec> ;
with the following parts:
<declaration>
Specifies the declaration for the identifiers to receive the
key and the procedures, and is identical in all respects to
the declaration in a define header, including default
declarations when omitted. (Except that dlocal is NOT
allowed, and if "procedure" identprops are specified either
explicitly or by default, this applies only to the procedure
identifiers, not to the key identifier.)
<dataword>
The dataword of the new class (i.e. its class name).
<attributes>
This is optional; if present it is a (square) bracketed list
of names specifying special attributes for the class (e.g.
[writeable] ). Permissible attributes are described in
* conskey.
<typespec>
A <typespec> as described above. If this is a structure
(i.e. {...} ) it specifies a record class, otherwise a
single type specifies a vector class.
The identifiers declared and initialised correspond to the procedures
contained by the key, some being prefixed/suffixed by the class name.
Calling this X, the following identifiers are common to both records and
vectors:
X_key (class key)
isX (recogniser procedure)
consX (constructor procedure)
destX (destructor procedure)
These three are specific to vectors,
initX (initialiser procedure)
subscrX (subscriptor procedure with updater)
fast_subscrX (fast subscriptor procedure with updater)
while for records, each field name in the structure defines an
identifier of that name containing the field access/update procedure (if
a field name is omitted from the structure definition, no identifier is
generated, but the procedure is still in the key).
3.1 Examples
-------------
To turn the structure given in the section above into a new class of
Poplog records:
defclass person
{ person_name,
person_address,
person_age :byte,
person_sex :1
};
This defines a record class whose class name is "person", and instances
of the class will contain the given fields. The identifiers defined are
person_key,
isperson, consperson, destperson,
person_name, person_address, person_age, person_sex
For vectors,
defclass unsigned :uint;
defclass lconstant eyefull;
create vector classes with class names "unsigned" and "eyefull", and
whose element types are respectively "uint" and "full" (the latter by
default for an empty <typespec>). The identifiers defined by the second
are
eyefull_key,
iseyefull, conseyefull, desteyefull, initeyefull,
subscreyefull, fast_subscreyefull
and similarily for the first, etc.
3.2 Note on Recompiling Record and Vectors
-------------------------------------------
A potential problem can arise when re-compiling a defclass statement for
which records/vectors of the class have already been created (and are
still in existence). Since all objects are identified by their key
(which is unique for a given class), the creation of a NEW key on
recompilation would invalidate all such existing structures using the
old key (in the sense that they would no longer be recognised by the
procedures associated with the new key).
To obviate this problem (at least when <declaration> specifies
permanent identifiers), defclass operates as follows: If a key
identifier (i.e. X_key) already exists for the class name X being
defined, and contains a key whose class_field_spec exactly matches the
<typespec> of the current definition (and which also has the same
<attributes>), then this key is used instead of creating a new one. The
rest continues as normal, i.e. the identifiers (including X_key) are
(re)declared and the key and its procedures assigned to them.
If you wish to stop the old key being used, you can simply assign
any non-key object to X_key before the defclass statement, e.g.
undef -> X_key
For lexical identifiers (e.g. lconstant), the strategy of trying to
use an existing key from X_key doesn't work (because being
lexically-scoped, the X_key identifier will have ceased to exist after
the initial compilation). In this case, if the problem arises you will
have to deal with it in some other way.
----------------------------
4 Declaring New Field Types
----------------------------
p_typespec [syntax]
l_typespec [syntax]
i_typespec [syntax]
These constructs can be used to declare new named field types in
terms of existing ones. The three differ only in the scope of
the declaration being made (see below), and are otherwise
identical, so x_typespec will be used to represent any of them.
The format of an x_typespec statement is very simple: letting
<name-typespec> mean a name followed by a <typespec> (as defined
elsewhere in this file), i.e.
<name-typespec> --> <typename> <typespec>
it is just
x_typespec <name-typespec-1>, ..., <name-typespec-N> ;
that is, a comma-separated list of <name-typespec>s terminated
by a semicolon. The effect of the statement is to associate each
<typespec> with its given <typename>; the <typename>s can then
occur anywhere a <basetype> can, i.e. the definition of
<typespec> can be extended to include
<typespec> --> :<typename>
4.1 Examples
-------------
After writing
p_typespec
an_age :byte,
a_bit :1
;
we could employ the new types declared to rewrite the "person" record
class example from the last section:
defclass person
{ person_name,
person_address,
person_age :an_age,
person_sex :a_bit
};
To enable type declarations to be scoped in a manner similar to
ordinary program identifiers, the x_typespec forms store and retrieve
the details of each type declared from an identifier (whose name is
derived from the typename, e.g. "an_age:typespec"). The declarations of
these identifiers (and thus the scope required) may then be controlled
by using the construct with the appropriate prefix, i.e.
Construct Typename Scope
--------- --------------
p_typespec permanent (identifier declared as constant)
l_typespec lexical (identifier declared as lconstant)
The i_typespec form is for .ph include files, and behaves as l_typespec
in a #_INCLUDEd file, but p_typespec when directly compiled (this is
achieved by testing the variable used by #_INCLUDE, see
REF * Pop_#_INCLUDE_STACK).
------------------------------------
5 Field Value Conversion Procedures
------------------------------------
Although defining a new type name for a basetype is useful, it does not
affect the actual value that can result from accessing a field of that
type in a structure (for example, defining the "person_sex" field in the
last section as
person_sex :a_bit
makes no difference to the result of applying the person_sex procedure
to a "person" record, which will still be 0 or 1).
However, field typespecs also allow for the inclusion of one or more
'conversion' procedures. These are applied successively when accessing a
field, to convert its actual value to some final output form. Moreover,
since data in that same form should also be input to the field when
updating it, corresponding procedure(s) are required to convert the
input value back to the actual value to be assigned into the field. A
convenient way of doing this is to require each conversion procedure to
have an updater which performs the opposite conversion (and the updaters
are run in the opposite order).
The syntax for specifying a conversion procedure is
<typespec> --> <typespec> # <identifier name>
where <identifier name> is the name of an identifier whose value is
taken as the conversion procedure (N.B. its current, compile-time value
- the <typespec> cannot indirect through a variable at run-time). The
procedure must also have an updater (at least, it must for record and
vector class fields; for external fields that are not assigned to the
updater may be absent). A conversion procedure conv_p and its updater
are called as
conv_p(value) -> converted_value
-> conv_p(converted_value) -> value
(N.B. Inasmuch as the updater is expected to return a result, this is a
somewhat strange way of using updaters!)
Applying conversion procedures to the above example, suppose we
wanted the "person_sex" field to produce "male" or "female" when
accessed (by the field procedure person_sex or the destructor
destperson), and to take the same values when updated (by person_sex or
the constructor consperson). The following procedure would achieve this:
define lconstant bitval_to_sex(bit);
lvars bit;
if bit == 0 then "male" else "female" endif
enddefine;
;;;
define updaterof bitval_to_sex(sex);
lvars sex;
if sex == "male" then
0
elseif sex == "female" then
1
else
mishap(sex, 1, 'male OR female NEEDED FOR FIELD VALUE')
endif
enddefine;
The procedure could then be specified directly in the definition of the
'person_sex' field
person_sex :1#bitval_to_sex
or incorporated into a new type first
p_typespec sex :1#bitval_to_sex;
and the field defined as
person_sex :sex
etc.
------------------------------------
6 External Data Accessing: Overview
------------------------------------
'External' data is data maintained in memory outside the Poplog system
proper by external procedures, i.e. those written in non-Poplog
languages. Such data is represented and manipulated inside Poplog by
'external pointer-class' structures, which are ordinary Poplog records
having an "exptr" field in a fixed position. Access code generated by
exacc, or access procedures defined by defexacc, can then be applied to
such structures to extract or update external data via their pointer
fields. (See REF * EXTERNAL_DATA for a full explanation of external
pointer-class structures.)
A special case of an external data structure is a function (or
procedure -- the names mean the same); here 'accessing' the data means
calling the function to produce its result (if any). Thus exacc and
defexacc can also be used to call functions pointed to by external
pointers. (REF * EXTERNAL deals in more detail with calling external
functions.)
Because external data structures (a) do not have to be made by
Poplog -style constructor procedures, (b) are not relocatable and always
reside in fixed memory locations, and (c) are not processed by the
garbage collector, they allow a greater range of field types than for
native Poplog structures. In particular, they allow 'compound' fields,
i.e. fields which are sub-structures or arrays, and for which the access
procedure can return the address of the field (as another external
pointer).
Alternatively, such address-value fields (as well as "exptr" fields
directly containing an address) can include in their specification
automatic access through the address to the underlying data, either in
terms of further type-specifications for that data, or
previously-defined access procedures for it.
6.1 Another N.B.
------------------
While "full" fields can be used in external structures, they must be
used with EXTREME caution. Unlike a record or vector class (which has a
key structure describing itself, used by garbage collection in
determining the position of "full" fields containing Poplog structure
pointers), external pointers contain no description of what they point
to.
Thus a "full" field in an external structure is NOT processed by the
garbage collector, and the assignment of a Poplog structure into such a
field may result in it containing junk after a garbage collection --
this will certainly be the case if the structure is not fixed-address
(see Fixed-Address Poplog Structures for External Use in
REF * EXTERNAL_DATA). In an attempt to guard against this, an access
procedure for such a field mishaps if its value does not satisfy
is_poplog_item (which is by no means guaranteed to pick up all errors).
--------------------------
7 External Compound Types
--------------------------
7.1 Structures
---------------
Note first that (to allow the material prior to this point to be
concerned mainly with defining Poplog structures), the description of
the x_typespec constructs above omitted to stress that structure types
can be declared, e.g. as in
p_typespec timeval
{ tv_sec :long,
tv_usec :long
};
This is of limited relevance for Poplog structures because such types
cannot be specified as elements of record or vectors (although they can
be 'exploded' as multiple fields in a record class, see below). In
addition, it should be noted that (as described under 'Format of Data
Structures' in REF * DATA), a Poplog structure has a 2-word header
BEHIND the structure pointer, and that by default, structures specified
to defclass will use the spare word in this header where possible (and
thus do not necessarily start at the pointer).
While Poplog records that are required to be used externally can include
the `pointer' symbol >-> in their structure spec to force data to start
at the pointer, structures declared with x_typespec (or given to
defexacc) AUTOMATICALLY do so (i.e. they have >-> added before the first
field).
A structure type as above (or indeed an explicit structure) can thus
appear as a field in an external structure. For example:
p_typespec rusage
{ ru_utime :timeval,
ru_stime :timeval,
ru_maxrss :int,
<rest of fields>
};
As mentioned above, a sub-structure field such as "ru_stime" is a
`compound' field; this means that an access procedure for the field
returns an external pointer to the start of the field data (i.e. when
applied to an pointer to an "rusage" structure). The result pointer
could then in turn be used with an access procedure for one of the
'timeval' structure fields, etc.
7.2 Overlaid Fields in Structures
----------------------------------
To allow alternate sets of fields within a single external structure
(like `unions' in C), the symbol "|" may occur anywhere between fields,
e.g.
p_typespec foo
{ a1_fld1 :int, a1_fld2 :int
| a2_fld1 :dfloat
| a3_fld1 :short, a3_fld2 :byte
}
The effect of "|" is simply to reset the offset of the next field to 0,
i.e. back to the pointer position. Thus the fields in each alternate set
will actually overlay each other in memory (and the size of the
structure as a whole is the greatest of any overlay).
Aside from this, "|" has no other effect; each field in the structure is
accessed just like a field in a structure without overlays (in
particular, "|" does not allow alternatives in the sense of re-using the
same name for different fields, etc).
7.3 Arrays
-----------
The second compound type allowed is a sized or unsized array (currently,
only 1-dimensional). Defining <element-typespec> by
<element-typespec> --> :<basetype or typename>
--> { <fieldspec>, ... }
an array is a <element-typespec> followed by square brackets containing
an integer >= 0 for sized, or nothing for unsized, i.e.
<typespec> --> <element-typespec> [ <integer> ]
--> <element-typespec> []
Some examples:
p_typespec
timeval2 :timeval[2],
bytearray :byte[],
direct { dir_off :long,
dir_ino :long,
dir_reclen :short,
dir_namlen :short,
dir_name :bytearray
}
;
As with structures, the access procedure for an array-type field returns
a pointer to its first element. Thus when applied to a "direct"
structure, the field procedure for "dir_name" would return a pointer to
its first byte; this could then be used with a subscriptor access
procedure for a byte array.
Note that subscript values within arrays are as for Poplog vectors, i.e.
the first element is numbered 1. A (non-fast) array subscriptor
procedure therefore checks its subscript >= 1; if in addition the array
is sized, the subscript is checked <= size. (For obvious reasons, there
are various restrictions on the use of unsized arrays: in a structure,
an unsized array type can only appear as the last field, and such a
structure cannot itself be arrayed, or appear as anything but the last
field in an outer structure, etc.)
7.4 Functions
--------------
The final compound type is an external function. A function is
characterised by the number of arguments it takes, and the type of its
result (if any). Syntactically, this is specified by
<typespec> --> ( <arglist> ) <typespec>
--> ( <arglist> ) :void
--> ( <arglist> )
where <typespec> refers to the result of the function.
The result <typespec> is restricted to non-compound types (i.e. cannot
be a structure, array or function, although it can be a POINTER to any
of these). It can also be given as :void or omitted altogether for a
function not returning a result.
Although the result of an external function is `statically typed', its
arguments are `dynamically typed'. That is, the actual values passed to
a function depend solely on the run-time arguments you supply to it, and
(with the exception of (d)decimals), any particular kind of Poplog
object is always passed in the same way. Argument processing is dealt
with in detail in the section Calling External Functions in
REF * EXTERNAL
Syntactically therefore, all that needs to be specified is:
¤ The number of arguments to the function if this is fixed, or
alternately, that the function is variadic, i.e. takes a
variable number of arguments. In the latter case, the actual
number of arguments to a given call of the function must be
supplied as an extra last argument.
¤ The treatment of pop decimals or ddecimals when passed for given
argument(s).
<arglist> is thus a comma-separated sequence of zero or more <argspec>s,
i.e.
<arglist> --> <argspec>, <argspec>, ..., <argspec>
where each <argspec> can be either
¤ A simple argument name, e.g.
(x, y, z)
Except for the name "N" (see below), no significance is attached
to the names; they merely serve to mark the argument positions,
and can be omitted if desired, e.g.
( , , )
has the same effect.
¤ An integer, standing for that number of arguments. E.g.
(3)
(x, y, 4, z) ;;; = 7 arguments altogether
¤ The special argument name "...". This must come last, and
specifies an indeterminate number of further arguments, i.e.
that the function is variadic:
(...)
(x, y, z, ...)
¤ Any of the above followed by <SF>. This indicates that if a
(d)decimal is passed for any of the argument(s) in question,
then it should be passed as a machine single float rather than a
double:
(x, y<SF>, ...)
(x, y, 4<SF>, z)
(x, y, z, ...<SF>)
(You can also use <DF> to indicate double, but this is the
default so is never required.) In the variadic case, ...<SF>
applies to all the remaining arguments.
Note that <SF> is not a 'type' on the argument(s), in the sense
of implying type-checking or conversion for other values passed;
it merely says "if (d)decimals are passed for these argument(s),
pass them as single". See Calling External Functions in
REF * EXTERNAL for details of when to use <SF>.
(N.B. In previous versions of the system, the single argument name "N"
indicated a variadic function, i.e. (N) was used instead of (...). Since
this usage is still supported for backward compatibility, you cannot use
`N` for the name of the first argument.)
Some example declarations (of standard C library functions):
p_typespec
malloc(nbytes) :exptr,
atan2(x, y) :dfloat,
exit(status) :void, ;;; no result
printf(string, ...) :int, ;;; variadic
;
Note that (unlike structures and arrays), a function spec cannot appear
as an element of a structure or an array; it can only be used as the
direct argument to exacc or defexacc, or as an implicit type on an
external pointer field (that is, specifying a POINTER to a function --
implicit pointer types are dealt with in a later section).
--------------------------------------
8 Defining External Access Procedures
--------------------------------------
External data can be accessed (or functions called, etc) either with
in-line code generated by the syntax construct exacc, or with procedures
defined by defexacc. This section describes defexacc; exacc (which is
generally more convenient for calling functions), is dealt with in a
later section.
defexacc [syntax]
Used to construct new access/update procedures for external
data. It uses cons_access (see REF * KEYS) to create a procedure
or procedures appropriate to the <typespec> argument supplied,
and then assigns the procedure(s) to identifiers. (The
procedures constructed will accept as their input argument any
external pointer-class structure -- see REF * EXTERNAL_DATA.)
The construct has the form
defexacc <declaration> <name> <attributes> <typespec> ;
with the following parts:
<declaration>
Specifies the declaration for the identifier(s) to receive
the procedure(s), and is identical in all respects to the
declaration in a define header, including default
declarations when omitted. (Except that dlocal is NOT
allowed.)
<name>
An optional word. If supplied it determines the names of the
identifier(s) to receive the procedure(s) -- see below.
<attributes>
This is optional: if present, it consists of a square-
bracketed list of attribute names (words), optionally
separated by commas. Valid attributes are
@ See "Address Mode Accessing" below.
nc See "Notes on Efficiency" below
fast See "Notes on Efficiency" below
(For example, [@,nc,fast] .)
<typespec>
A <typespec> as described in elsewhere in this file. The
type of this determines what procedures are constructed.
Type Procedure(s)
---- ------------
Structure Access procedures for named structure fields
Array Subscriptor procedure for array elements
Function Apply procedure for function
Other Access procedure for non-compound type
The table above shows the mapping.
Note that whether or not the constructed procedure(s) have
updaters depends on whether <typespec> specifies a writeable
type (see Updating External Data: Non-Writeable Types below).
(An apply procedure for a function never has an updater.)
The identifier names declared by -defexacc are determined as follows:
¤ For a structure, the field access procedures are named after the
structure fields, with "<name>_" prefixing these if <name> is
supplied (no procedures are generated for unnamed fields).
¤ For an array, the subscriptor procedure is just called <name> if
that is supplied. Otherwise the <typespec> must be of the form
:<typename> or :<typename>[..], and the procedure is called
"exsub_<typename>".
¤ For a function, the apply procedure is just called <name> if that
is supplied. Otherwise, the procedure is called "exapp<nargs>" or
"exapp<nargs>_<typename>", where <nargs> is the number of
arguments or "N" for a variadic function, and where "_<typename>"
is present if <typespec> is of the form (..):<typename> (i.e.
specifying a result of <typename>).
¤ For the last case, the access procedure is just called <name> if
that is supplied. Otherwise the <typespec> must consist of just
:<typename>, and the procedure is called "exacc_<typename>".
In all cases, "fast_" is prefixed to the name(s) if the "fast" attribute
is present but not <name> (i.e. if you supply <name> this is assumed to
include any prefix you may or may not want).
8.1 Example
------------
With the definition of the "direct" structure as in the section above,
p_typespec
direct { dir_off :long,
dir_ino :long,
dir_reclen :short,
dir_namlen :short,
dir_name :byte[]
};
the following will generate access procedures dir_off, dir_ino,
dir_reclen, dir_namlen and dir_name:
defexacc :direct;
In this structure (which is a SunOS Unix directory entry), the field
"dir_namelen" gives the length in bytes of the "dir_name" field. Since
the latter is an array, the procedure dir_name will return an external
pointer to the field, the bytes of which could then be accessed with a
byte subscriptor procedure exsub_byte:
defexacc :byte[];
Thus if a_direct contains an external pointer-class record pointing to a
"direct" structure, then
exsub_byte(N, dir_name(a_direct))
would return the N-th byte of the structure's "dir_name" field. However,
since in this particular example the bytes in the "dir_name" field are
guaranteed to be null-terminated (i.e. end with ASCII 0), a simpler way
to access the whole name field would be to use the built-in procedure
exacc_ntstring, which given a pointer to a null-terminated sequence of
bytes extracts them as a Poplog string. Thus
exacc_ntstring(dir_name(a_direct))
would return the whole name as a string.
----------------------------------------------------
9 Implicit Access Procedures in External Type Specs
----------------------------------------------------
In the above example, it would be convenient if the "dir_name" field
could be specified so as to produce a Poplog string automatically when
accessed; this can be done by specifying exacc_ntstring as an 'implicit
access' procedure. The syntax for this is similiar to a conversion
procedure, but using "." instead of "#", i.e.
<typespec> --> <typespec> . <identifier name>
where the procedure is taken from <identifier name> in the same way as
for a conversion procedure. An implicit access procedure ACC_P and its
updater are called as
ACC_P(EXPTR) -> RESULT
RESULT -> ACC_P(EXPTR)
where EXPTR is an external pointer record and RESULT is whatever the
procedure accesses from it. (N.B. Unlike conversion procedures, implicit
access procedure updaters accord with 'normal' updater usage; this
includes the fact that in a sequence of them, only the last is called in
update mode -- see the next section.)
Using exacc_ntstring as an implicit access procedure, the definition
of the field "dir_name" could now be rewritten
dir_name :byte[].exacc_ntstring
after which
dir_name(a_direct)
would produce a string directly. (Better still, we can declare a general
field type for a null-terminated string and recast the field definition
to use that, e.g.
p_typespec ntstring :byte[].exacc_ntstring;
dir_name :ntstring
etc.) Alternatively, reverting back to accessing individual bytes, the
field could be declared
dir_name :byte[].exsub_byte
giving a field procedure
dir_name(N, a_direct)
that returns the N-th byte of the name.
It is important to note that access procedures may only be applied
on top of address-value fields, that is, (a) compound fields
(structures, arrays and functions), (b) "exptr" fields, or (c) other
access procedures producing a pointer. (It might be thought that in the
example exacc_ntstring could be specified as a conversion procedure
anyway, but this NOT the case: while conversion procedures may be
applied to "exptr" fields, they cannot be used with compound fields. The
reason concerns the semantics of updating the field -- see the section
on updating.)
Of course, conversion procedures can be layered on top of access
procedures. A typical example might be to turn the "ntstring" type into
one that produces a Poplog word:
define string_to_word()
consword()
enddefine;
define updaterof string_to_word()
word_string()
enddefine;
p_typespec ntword :ntstring#string_to_word;
and so on.
(Note that where an external <typespec> is given as the direct argument
to exacc or defexacc, it may consist of access/conversion procedures
only. Thus something like
defexacc foobaz .exacc_ntstring#string_to_word;
is valid, but the above <typespec> is not valid for a structure field or
array element, etc. This facility is mainly useful for a <typespec>
given to exload, see REF * EXTERNAL.)
--------------------------------------------------------
10 Implicit Type Access and Typing on External Pointers
--------------------------------------------------------
The idea of implicit access can be taken a stage further to include
'implicit type access' specification. Suppose we have an "exptr" field
(either a single one, or part of a structure), which we know contains a
pointer to say, an "int" value. We could use exacc_int as defined by
defexacc :int;
as an implicit access procedure on top of the "exptr" field, to obtain a
procedure which returns the "int" value indirectly through the pointer,
e.g.
defexacc exacc_int_indir :exptr.exacc_int;
However, the type-specification mechanism allows this to be expressed
more succinctly (and generating more efficient code) as
:exptr.:int
that is, we can use ':int' as an implicit access 'procedure'.
More generally, any <typespec> can be used in this way, which
syntactically gives
<typespec> --> <typespec> . <typespec>
For a non-compound type, its use for implicit access is simply
equivalent to using the procedure that defexacc would produce for it;
however, for compound types the situation is different.
The system is designed on the basis that an access to a compound
sub-field of a structure (or a compound element of an array) finishes by
returning the address of the compound item as a pointer (which pointer
will then be used by another access procedure appropriate for the type,
i.e. another field, subscriptor or apply procedure). defexacc and exacc
thus generate field, subscriptor and apply procedures/code for direct,
'top-level' structures, arrays and functions only; elsewhere, these just
mean mean 'return the address'. (Thus for example
defexacc :byte[];
generates a subscriptor, but the same type for a structure field
dir_name :byte[]
does not.) So, when used for implicit access, compound types merely
return an address -- which in fact, means they do nothing. To see this,
consider a type such as
:exptr.:ntstring
This 'expands out' to
:exptr.:byte[].exacc_ntstring
which is equivalent to just
:exptr.exacc_ntstring
i.e. the '.:byte[]' part doesn't do anything.
10.1 Pointer Typing
--------------------
In general therefore, an implicit compound type merely has the effect of
'typing' the pointer that precedes it. A special case of this is where
one appears at the end of a typespec, as in the following two examples:
;;; pointer to byte array
:exptr.:byte[]
;;; pointer to pointer to 1-arg function returning int
:exptr.:exptr.(ARG):int
Not only do these have the same results as ':exptr' and ':exptr.:exptr'
respectively, but the system in fact treats them as IDENTICAL to those
(i.e. both fields are just considered to be the final external pointer).
The important point in this respect is that it gives a sensible
interpretation to UPDATING these fields (namely of assigning a new byte
array or function address into them, etc).
In addition, the 'typing' property of implicit compound types is
used by the syntax construct exacc (described below), to enable one
exacc applied to the result of another producing an external pointer, to
determine the result pointer type (making it unnecessary to respecify
that type). There is a special convention associated with this: a
structure consisting of just one field with no field name, i.e.
{ <typespec> }
can be used to 'bracket' <typespec> for the purpose of typing a pointer.
For example,
:exptr.{:int}
types the pointer as pointing to "int". Note that in terms of the field
value there is nothing special about this (it being just a particular
case of an implicit compound type). However, when extracting the type of
a pointer, exacc recognises such 1-field 'bracket' structures and strips
off any number of levels of them; thus it recognises the above example
as a pointer to "int", not to a 1-field structure.
For consistency, this convention is also recognised by defexacc, in
that it too strips outer 'bracket' structures from its argument. Thus
defexacc {:int};
is the same as 'defexacc :int;', etc. (A bracket structure is also the
correct way to make a non-compound structure field or array element
produce the address of the data rather than its value; for example, with
defexacc { f1 :int, f2 {:int} };
the f2 procedure would return a pointer to "int".)
------------------------------------
11 In-Line Code for External Access
------------------------------------
As an alternative to constructing procedures with defexacc, the exacc
syntax form is provided for generating in-line code to access external
data and call functions. This saves the time overhead of calling
procedures (and can also save the space overhead of procedure records,
although since each piece of in-line code generally takes a little more
space than a procedure call, a procedure may be more space-efficient for
multiple uses of the same access).
exacc is also usually a more convenient form for calling external
functions; it is particularily designed for use with pointers to
external functions and data loaded with the exload syntax form (see
REF * EXTERNAL), since this has the option to automatically generate
typespec declarations under the names of the identifiers being loaded
(which therefore require no further <typespec> declaration when used
with exacc).
exacc [syntax]
Used to generate in-line access/update code for external data
(for which it uses the Poplog VM instruction sysFIELD, see
REF * VMCODE).
This construct has the general form
exacc <attributes> <typespec> <pointer expression> <access part>
where the parts are as follows:
<attributes>
This is optional: if present, it consists of a square-
bracketed list of attribute names as for defexacc (see
above).
<typespec>
An optional type 'cast' for <pointer expression>. This is
mandatory only if a <typespec> cannot be derived for
<pointer expression>; if supplied, it overrides any derived
type.
<pointer expression>
An expression whose run-time evaluation produces the
external pointer-class record to be used for the
access/update. This can have three forms:
(1) A simple identifier <name>, meaning the value of
that identifier. If <typespec> is not present, then
<name> must have a current declaration as a
typename, and the <typespec> assumed is :<name>.
(2) Another exacc construct enclosed in parentheses,
i.e. (exacc ...), meaning an external pointer
resulting from that access. If <typespec> is not
supplied, then the result pointer type of the
enclosed exacc must must be derivable (see below).
(3) Any other Pop11 expression enclosed in parentheses,
meaning the value of that expression. In this case,
<typespec> must be supplied.
<access part>
This part depends on the kind of access/update being
performed, as specified by <typespec> (and corresponds to
the type of procedure that would be generated for it by
defexacc):
Structure Field:
For <typespec> a structure, a dot followed by the name
of the field to be accessed, i.e.
. <fieldname>
E.g.
exacc struct.field
Array Element:
For <typespec> an array, a Pop11 expression for the
array subscript enclosed in square brackets, i.e.
[ <expression> ]
E.g.
exacc array[n+1]
Function:
For <typespec> a function, a Pop-11 expression sequence
for the function arguments enclosed in parentheses, i.e.
(ARG_1, ARG_2, ..., ARG_N)
E.g.
exacc func(1,2,3)
Other:
For <typespec> a non-compound type, nothing.
E.g.
exacc pointer
Note that an exacc construct can be used for updating, i.e.
appear after ` -> `; however, a compile-time mishap will result
in this case if <typespec> specifies a non-writeable type (see
'Updating External Data: Non-Writeable Types' below). (A
function call in update mode will always produce a mishap).
11.1 Examples
--------------
Before discussing form (2) for the pointer expression (where one exacc
is applied to the result of another), we illustrate forms (1) and (3).
Suppose (returning to the example in the section on defexacc) that
get_direct is a Pop procedure returning an external pointer-class record
pointing to a "direct" structure, defined as
p_typespec direct { dir_off :long,
dir_ino :long,
dir_reclen :short,
dir_namlen :short,
dir_name :byte[]
};
Then (using form (3)),
exacc :direct (get_direct()).dir_namlen
would access the "dir_namlen" field (a short integer). If instead the
procedure result were assigned to a variable a_direct first, then form
(1) can be used:
get_direct() -> a_direct;
exacc :direct a_direct.dir_namlen
However, if the name "a_direct" is itself declared as a typename,
l_typespec a_direct :direct;
then the type cast can be omitted:
get_direct() -> a_direct;
exacc a_direct.dir_namlen
etc.
Now suppose that instead of a Pop procedure we have an external
function readdir, again returning a pointer to a "direct" structure (it
takes an argument stream, but this isn't relevant for the discussion).
If the function is declared
p_typespec readdir(stream) :exptr;
then as before, the result could be assigned to a_direct first:
exacc readdir(stream) -> a_direct;
exacc a_direct.dir_namlen
On the other hand, form (2) could be used, applying one exacc to the
result of another. With a type cast this is
exacc :direct (exacc readdir(stream)).dir_namlen
as with the Pop procedure, but we would like to omit the cast and write
exacc (exacc readdir(stream)).dir_namlen
As things stand however, this will produce a compilation mishap --
because although the result of the function is known to be an external
pointer, it hasn't been declared as a pointer to a "direct" structure.
Redeclaring the function with a typed pointer result enables exacc to
derive this information:
p_typespec readdir(stream) :exptr.{:direct};
(note that in this case the 'bracket' structure around :direct isn't
strictly necessary, because :direct is already a compound type, but it
makes sense always to use it, since it is required for non-compound
types).
Finally, consider the "dir_name" field: as declared, this returns a
pointer to a byte array. The pointer could be assigned to a variable,
and bytes accessed from there, e.g.
l_typespec bptr :byte[];
exacc (exacc readdir(stream)).dir_name -> bptr;
exacc bptr[1] -> byte;
Alternatively, the pointer could be given directly to exacc_ntstring to
return a Pop string:
exacc_ntstring(exacc (exacc readdir(stream)).dir_name)
Note though, that exacc can derive the pointer type produced from a
compound field in a structure or array; thus a single byte could be
accessed directly with
exacc (exacc (exacc readdir(stream)).dir_name) [1]
(which is probably not very useful in this case, but illustrates the
point).
--------------------------
12 Address Mode Accessing
--------------------------
As described above, both exacc and defexacc can take the `@` attribute.
This is `address mode', and makes the result of the access be a pointer
to the data rather than the data itself.
In this case, the the (explicit or derived) <typespec> may only specify
a structure field, array element or simple type (i.e. not an external
function). The result is an external pointer pointing to the specified
component; since the address of the component is returned, only the base
type of <typespec> is relevant, i.e. any implicit access or conversion
procedures it contains are ignored. (The `@` attribute cannot be used in
update mode.)
For example, with the structure "direct" as defined in the last section,
exacc[@] :direct a_direct.dir_namlen
would return a pointer to the "dir_namlen" field, rather than its
integer value. For a compound component, address mode makes no
difference, since the access returns its address anyway; thus if the
field "dir_name" is defined as just
dir_name :byte[]
then the `@` in
exacc[@] :direct a_direct.dir_name
has no effect. On the other hand, with "dir_name" defined
dir_name :byte[].exacc_ntstring
the field is non-compound, and without `@` will produce a string; if it
is with `@`, then it will behave as before.
-----------------------------------------------
13 Updating External Data: Non-Writeable Types
-----------------------------------------------
This section concerns using exacc and procedures constructed by defexacc
in update mode.
To restrict the use of exacc in update mode (i.e. when the construct
follows ` -> `), and to allow control over the generation of updaters
for defexacc procedures, the symbol "!" (exclamation mark) can be used
anywhere in a <typespec> instead of ":". In general, "!" can be used to
flag types as `non-writeable'; an update mode exacc applied to a
non-writeable type then gives a compile-time mishap, and defexacc
procedures for non-writeable types have no updaters generated for them.
13.1 General Points
--------------------
Note first that `writeability' as applied to a compound type never
refers to the status of a POINTER to that type, but always to the
COMPONENTS of that type. An implicit compound type on an external
pointer (e.g. ':exptr.:int[]' etc), is ignored in the sense that the
field value is taken to be the preceding external pointer (as described
above in 'Pointer Typing'), and so such a <typespec> is NOT compound.
When a compound type does not follow an external pointer (i.e. as a
component of a structure or array), its field value is a pointer, which
is simply not updateable anyway. Thus
:exptr.{!int}
is writeable (i.e. the pointer can be updated even if the "int" it
points to can't), whereas the structure field
bytefield :byte[]
is non-writeable (i.e. the pointer value returned for the field can't be
updated, although the bytes it points to can).
Another important aspect of updating that needs to be understood
when using "!" is the following: as with Pop-11 procedure calls (e.g. as
in
x -> list.tl.tl.tl.hd
where only the last procedure hd has its updater run), only the last
type or access procedure in a <typespec> actually performs in update
mode; hence in general, this last type or access procedure is what
governs writeability. (Conversion procedures at the end of a <typespec>
have no effect on in this respect -- if a type is writeable, then all
conversion procedures on it must have updaters.)
13.2 Using "!"
---------------
Thus a <typespec> can be made non-writeable by making its last type or
access procedure 'non-writeable'. For a type, this means using "!" ; for
an access procedure, it means not giving the procedure an updater. For
example,
:exptr.!int
is non-writeable, and
:exptr.foo
is writeable if and only if foo has an updater.
However, to make it possible not to have to bother about the last
type or access procedure, a <typespec> STARTING with an explicit ! is
always considered non-writeable. Thus
!exptr.:int
!exptr.foo
are both non-writeable (in the second case, regardless of whether foo
has an updater or not).
Note that "!" always overrides ":". Thus, after
p_typespec nw_ntstring !ntstring;
types such as
:nw_ntstring
:exptr.:nw_ntstring
are non-writeable. (On the other hand, because of the rule about only
the last type being updated, a non-writeable pointer type like
p_typespec nw_exptr !exptr;
does NOT prevent
:nw_exptr.:ntstring
being writeable, etc.)
13.3 Compound Types
--------------------
As explained previously, writeability for a compound type always refers
to the components of that type.
Functions are a special case: a function can be thought of as having
one 'component', its result, which is NEVER writeable; thus a function
is always non-writeable (i.e. can't be called in update mode).
For structure fields and array elements, "!" overrides ":" as with
all other types. So in the structure
p_typespec timeval { tv_sec :long, tv_usec !long };
the field tv_sec is writeable, but not tv_usec; on the other hand, using
"!" on the typename,
!timeval
will make all fields non-writeable.
--------------------------
14 Pointer Values as Data
--------------------------
A further extension to the <typespec> syntax allows the pointer value of
an external pointer to be accessed (or updated) as data in its own
right. It has the form
<typespec> --> ^ <typename>
where this may only appear as the direct argument to exacc or defexacc
(or as an implicit type access on a pointer or "exval" field).
<typename> is restricted to being an integer <basetype> such as
'int' or N, etc, a single-float "sfloat", or "full" (or a renaming of
any of these). The effect of this form is to `back off' by one level of
pointer, and then perform the specified access on a pointer to the
pointer value. E.g. if ptr contains an external pointer, then
exacc ^uint ptr
would return the pointer value as an unsigned integer. (Note that
specifying a type smaller than a pointer is taken to mean the low-order
part of the value, e.g.
exacc ^byte ptr
would return the least-significant byte of the pointer value.)
----------------------------------------
15 External Structure Fields in Records
----------------------------------------
Although a Poplog record cannot contain structures declared with
x_typespec as single fields, such structures can be 'exploded' as
multiple fields in a record class. For example, with "timeval" declared
p_typespec timeval { tv_sec :long, tv_usec :long };
the form
defclass timeval :timeval;
creates a record with fields tv_sec and tv_usec (where tv_sec starts at
the pointer). Alternatively, something like
defclass foo { foo_1, foo_2, foo :timeval };
creates a record with 4 fields, where the names for the sub-structure
fields are got by prefixing "<outer fieldname>_" to the structure field
names (i.e. foo_tv_sec and foo_tv_usec in this case).
------------------------------
16 Summary of typespec Syntax
------------------------------
¤ named type
<typespec> --> : <typename>
--> ! <typename>
--> ^ <typename>
¤ structure
<typespec> --> { <fieldspec-1>, ..., <fieldspec-N> }
<fieldspec> --> <fieldname> <typespec>
¤ array
<typespec> --> <element-typespec> [ <integer> ]
--> <element-typespec> []
<element-typespec> --> :<typename>
--> !<typename>
--> { <fieldspec>, ... }
¤ function
<typespec> --> ( <arglist> ) <typespec>
--> ( <arglist> ) :void
--> ( <arglist> )
<arglist> --> <argspec>, ..., <argspec> (0 or more)
<argspec> --> <dummy name>
<argspec> --> <integer>
<argspec> --> ... (variadic)
<argspec> --> <argspec> <SF>
¤ implicit type access
<typespec> --> <typespec> . <typespec>
¤ implicit access procedure
<typespec> --> <typespec> . <identifier name>
¤ conversion procedure
<typespec> --> <typespec> # <identifier name>
N.B. While there are a number of potential ambiguities in this syntax,
they are not likely to represent a problem in practice. Although no
general provision is made for bracketing a <typespec> to resolve
ambiguities, this can always be done by defining it as a new typename.
The most serious potential problem is the <typespec> specifying a
function result, which can absorb something following that's not
intended to be part of it, e.g.
defexacc app_func (ARG):int;
defines an apply procedure for a function; if this is used in a
<typespec> to apply a function pointer, e.g.
:exptr.(ARG):int.app_func
then '.app_func' will incorrectly be taken as part of the function
result. However, since functions only type external pointers, they
should always be put in 'bracket' structures anyway (which resolves the
ambiguity):
:exptr.{(ARG):int}.app_func
-----------------------
17 Notes on Efficiency
-----------------------
¤ The "fast" attribute to exacc or defexacc means that the
code/procedure produced will not check its input pointer
argument to be an external pointer-class structure. In addition,
an array subscriptor will not check its subscript, and a
variadic function call will not check its number of arguments.
¤ Any <typespec> producing an external pointer result (e.g. an
"exptr" field, or an address-mode access, etc) will by default
construct a new external pointer record on each acccess. However
(so as to avoid creating unnecessary garbage), when the pointer
is being passed to a conversion procedure or external implicit
access procedure, a fixed record is used instead (i.e. fixed for
each exacc or access procedure defined by defexacc). This
optimisation should not cause problems, because (presumably) the
procedure receiving it is always going to return something
derived from the pointer, not the the pointer itself.
On the other hand, this behaviour can be forced with the "nc"
(`non-constructing') attribute to exacc or defexacc; when
supplied, an external pointer result will use a fixed record.
You must ensure that this pointer is no longer in use the next
time the exacc code or defexacc procedure is executed, otherwise
it will be overwritten.
You can also force a fixed record to be returned by simply
specifying identfn as a final conversion procedure or access
procedure. <typespec> syntax caters for this specially by
allowing a missing <identifier name> after "#" or "." to be
replaced with "identfn", e.g.
defexacc :exptr#;
(Note that this has no effect on the writeability of any
external type using it with "." ; moreover when used with "#"
the fact that identfn has no updater doesn't matter either.)
¤ In compiling code for conversion procedures and external
implicit access procedures, the system guarantees to optimise
out calls to both identfn itself and closures of identfn of no
arguments (identfn(%%)).
This is of particular relevance to using a conversion procedure
to type-check assignments into "full" fields (all other
<basetype>s are checked anyway); the access side of such a
procedure can simply be identfn(%%), resulting in no overhead on
accessing the field.
-----------------
18 Miscellaneous
-----------------
TYPESPEC [syntax]
This syntax construct takes a <typespec> (in procedure brackets
(...)) and plants a PUSH of the spec data structure associated
with it, suitable for use with field_spec_info etc.. For
example, if we have declared the typespec
p_typespec foo {a:full, b:int};
then we might use TYPESPEC thus:
field_spec_info(TYPESPEC(:foo)) =>
** [>-> full full] 64
TYPESPEC respects the scoping constraints as specified by
p_typespec (permanent scope) and l_typespec (lexical scope) etc.
SIZEOFTYPE [macro]
This macro produces the base type size of a <typespec>, that is
the amount of space it would occupy as a structure field or
array element etc. It has two forms, viz
SIZEOFTYPE( <typespec> )
SIZEOFTYPE( <typespec> , <unit-typespec> )
In the second form, the size of <typespec> is given in units of
the size of <unit-typespec>; in the first form, <unit-typespec>
defaults to :byte, i.e. the size in bytes. E.g.
SIZEOFTYPE(:timeval)
gives the size of a "timeval" structure in bytes, whereas
SIZEOFTYPE(:int,:1)
is the number of bits in an "int", etc.
If the size of <typespec> is not an exact multiple of
<unit-typespec> it is rounded up to the next multiple; thus
SIZEOFTYPE(:1)
equals 1 (byte).
Note that both <typespec> and <unit-typespec> must be sized
types, i.e. cannot be unsized structures or arrays, or
functions. (This macro uses field_spec_info, see REF * KEYS.)
FIELDOFFSET [macro]
This macro produces the offset of a field within a structure
<typespec>. Usage is
FIELDOFFSET( <typespec> , <fieldname> )
For example, where structure "foo" is defined
p_typespec foo {a:full, b:int};
then
FIELDOFFSET(:foo,b)
would produce 4.
Note that the value is returned in units appropriate to address
the given field; in all current Poplog implementations this is
bytes.
EXPTRINITSTR [macro]
A macro that creates and returns a fixed-address "exptr_mem"
structure big enough to hold a a given external type. Usage is
EXPTRINITSTR( <typespec> )
where <typespec> specifies the external type/structure. It is
simply equivalent to the code
initexptr_mem(SIZEOFTYPE(<typespec>))
See the description of "exptr_mem" structures in
REF * EXTERNAL_DATA, `Poplog Structures as External Data'. (Note
that since exptr_mems are fixed-address structures, it is never
necessary to use writeable on them.)
+-+ C.all/ref/defstruct
+-+ Copyright University of Sussex 1992. All rights reserved.