perm filename FW[1,LMM] blob sn#113651 filedate 1974-07-27 generic text, type C, neo UTF8
C00001 00001
C00003 00002	.KEEP 15
C00008 00003	Note that what the record declaration is really
C00011 00004	The user may also elaborate a field by declaring that field name
C00014 00005	~3RECORD~1 (used to specify elements and tails of a list structure)
C00016 00006	The record package also provides for some types of records a 
C00018 00007	.KEEP 12
C00021 00008	{INDENT 0,3}2. ↓_recordα-name_↓ is a literal atom used to identify
C00024 00009	{INDEXX(|RECORD (record package)|)}{INDENT 10,10}For ~3RECORD~1,
C00030 00010	{INDENT 10,10}{INDEXX(|ATOMRECORD (record package)|)}For ~3ATOMRECORD~1,
C00033 00011	{INDENT 10,10}{INDEXX(|HASHRECORD (record package)|)}For
C00040 00012	{INDENT 0,0}↓_CREATE_↓{INDEXX(|BEGIN CREATE (record package)|)}
C00046 00013	.KEEP 15
C00050 00014	.begin nofill
C00057 ENDMK
.KEEP 15;
{INDENT 0,0;}↓_23.11__The_Record_Package_↓
{INDEXX(|BEGIN record package (in clisp)|)}{XNOTE; SEND FOOT ⊂
{L!53:}{INDENT 0,4;}{!NOTE!} \The record package was written by L. M. Masinter.
.SKIP 2;
.SKIP 3;
The advantages of "data-less" or data-structure-independent
programming have long been known: more readable code, fewer bugs, the
ability to change the data structure without having to make major
modifications to the program, etc. The record package in CLISP both
encourages and facilitates this good programming practice by
providing a uniform syntax for accessing and storing data into many
different types of data structures, e.g. those employing arrays, list
structures, atom property lists, hash links, etc., or any combination
thereof, as well as removing from the user the task of writing the
various access and storage routines themselves. The user declares
(once) the data structure(s) used by his programs, and thereafter
indicates the manipulations of the data in a
data-structure-independent manner. The record package automatically
computes from the declaration(s) the corresponding INTERLISP
expressions necessary to accomplish the indicated access/storage
operations. The user can change his data structure simply by changing
the corresponding declaration(s), and his program automatically
(re)adjusts itself to the new conventions.
.SKIP 3;
The user informs the record package about the format of his
data structure by making a record declaration.
A record declaration defines a ↓_record_↓, i.e. a data structure.
(Note that the record itself is an abstraction
that exists only in the user's head.) The record declaration
is essentially a template which describes the record,
associating names with its various parts or ~2fields~1.
For example, the record declaration
~3(RECORDα MSGα (IDα (FROMα TO)α .α TEXT))~1
describes a data structure called ~3MSG~1, which contains four
fields: ~3ID, FROM, TO,~1 and ~3TEXT.~1 The user can then reference
these fields by name, either to retrieve their contents, or to store
new data into them, by using the : operator followed by the field
For example, for the above record declaration,
~3X:FROM~1 would be equivalent (and translate) to (~3CAADRα X)~1,
and ~3Y:TO←Z~1 to ~3(RPLACAα (CDADRα Y)α Z)~1.{XNOTE; SEND FOOT ⊂
{INDENT 0,4;}{!NOTE!} \or ~3/RPLACA~1 or ~3FRPLACA~1, depending on
the CLISP declaration in effect.
.SKIP 2;
The fields of a record can be further broken down into subfields by additional
declarations within the record, e.g.
.SKIP 1;
would permit the user to refer to ~3TEXT~1, or to its subfields
~3HEADER~1 and ~3TXT~1.
.SKIP 3;
Note that what the record declaration is really
doing is specifying the
~2dataα-paths~1{INDEXX(|data-paths (in records in clisp)|)}
of the structure, and thereby specifying how the
corresponding access/storage operations are to be carried out.
For example,
says the ~3HEADER~1 of a ~3MSG~1 is to be found as the first element
of its ~3TEXT~1, which is the second tail of the ~3MSG~1 itself.
Hence, ~3X:HEADER←string~1 is achieved by performing
~3(RPLACAα (CDDRα X)α string)~1.
.SKIP 3;
Note also that when the user writes ~3X:HEADER~1, he is
implicitly saying the ~3X~1 is an instance of the
record ~3MSG~1, or at least is to be treated as
such for this particular operation. In other words,
the interpretation of ~3X:FORM~1
~2↓_never depends on the value of X_↓~1.
The record package (currently) does not provide any facility which
uses ~2run-time~1 checks to determine data paths,
nor is there any
error checking other than that provided by INTERLISP itself.
For example, if ~3X~1 happened to be an array, ~3X:HEADER~1
would still compute ~3(CAADRα X)~1.{XNOTE; SEND FOOT ⊂
{INDENT 0,4;}{!NOTE!} \However, it is possible
to make the interpretation of ~3X:HEADER~1 differ from
that of ~3Y:HEADER~1 (regardless of the values of
~3X~1 and ~3Y~1), by using local record
declarations, as described on {PAGEREF L!41}.
Note that this distinction depends on a ~2translationα-~1time check, not run-time.
.SKIP 2;
.SKIP 3;
The user may also elaborate a field by declaring that field name
in a ~2separate~1 record declaration (as opposed to an embedded
declaration). For example, te two declarations
~3(RECORDα (MSGα (FROMα TO)α . TEXT))~1 and
~3(RECORDα TEXTα (HEADERα .α TXT))~1 subdivides ~3TEXT~1 into two
subfields. In this case, the record package interprets  ~3X:HEADER~1 to
mean that X is an instance of TEXT and translates to ~3(CARα X);
however the user may specify
to achieve the interpretation "X is a MSG, retrieve
its HEADER" with translation ~3(CARα (CDDRα X))~1. In general,
the user may specify a chain of such
data paths X:R.F1.F2.F3; only as much of the path as is necessary
to disambiguate the access path need be specified. 
.SKIP 3;
~3←(RECORD Q (T1 S1 S3))
←(RECORD S3 (FOO3 S2 S4))
(fetch S4 of X)
  (CADR X)
(fetch (S1 S2) of X)
(fetch (Q S1 S2) of X)
(fetch (Q S4) of X)
Ambiguous record field S2 appears in:
(RECORD S3 (FOO3 S2 S4))
 in  (fetch S2 of X)
~3RECORD~1 (used to specify elements and tails of a list structure)
is just one of several recordα-types currently implemented.
For example, the user can specify 'optional' fields, i.e. property list format,
by using the record type ~3PROPRECORD~1, or fields to be associated
with parts of the data structure via hash links, by using the recordα-type
~3HASHRECORD~1, or that a new data type be used to store multiple fields, by using
the record-type ~3DATATYPE~1,
 or even specify the access definitions in the
record declaration himself, by using the recordα-type ~3ACCESSFN~1.
These are described in detail below.
.SKIP 3;
The record package also provides a facility for ~2creating~1
new data structures using a record declaration as a guide or template.
Initial values for the various fields
can be specified in the ~3CREATE~1 expression,{INDEXX(|CREATE (record package)|)}
or defaulted to values specified in the record declaration itself.
Alternatively, ~3CREATE~1 can be instructed to
use an existing datum as a model, i.e. to
obtain the field values for the new datum from the corresponding
fields of the existing datum, or even to actually re-use the
structure of the existing datum itself.
.SKIP 3;
The record package also provides for some types of records a 
facility for testing a data structure to determine its type,
via a ~3TYPE?~1 expression.
This facility is primarily intended for ~3DATATYPE~1 and ~3TYPERECORD~1
records, although the user may declare how ~3TYPE?~1 expressions
are to be interpreted for other record types.
.SKIP 3;
As with all DWIM/CLISP facilities, the record package contains
many doα-whatα-Iα-mean features, spelling correction
on field names, record types, etc.
In addition, the record package includes
a ~3RECORDS~1 ↓_prettydef_↓ macro{INDEXX(|RECORDS (prettydef macro)|)}
for dumping record declarations, as well as the appropriate
modifications to the file package (Section 14), so that
↓_files?_↓ and ↓_cleanup_↓ will inform the user about
records that need to be dumped.
.SKIP 4;
.KEEP 12;
↓_Record_Declarations_↓{INDEXX(|BEGIN record declarations (in clisp)|)}
.SKIP 3;
{INDENT 0,0;}A record declaration is an expression of the form
.SKIP 1;
(recordα-typeα record-nameα fieldsα .α α{defaults and/or subfieldsα})
.SKIP 1;
This expression is evaluated to effect the corresponding
declaration.{XNOTE; SEND FOOT ⊂
{INDENT 0,4;}{!NOTE!} \Local record
declarations are performed by including an expression of this form
in the CLISP declaration for that function
({PAGEREF L!41}), rather than evaluating the expression itself.
.SKIP 2;
.SKIP 3;
{INDENT 0,3;}1. ↓_recordα-type_↓ specifies the "type" of data being
described by the record declaration, and thereby implicitly specifies
the data paths, i.e. how the corresponding access/storage operations
are performed.
↓_recordα-type_↓ currently is either ~3RECORD, TYPERECORD,
~3RECORD~1 and ~3TYPERECORD~1 are used to describe list structures,
~3DATATYPE~1 to describe user data types,
~3ARRAYRECORD~1 to describe arrays, ~3ATOMRECORD~1 to describe 
(the property list of) atoms, and ~3PROPRECORD~1 to describe lists
that use property list format. ~3HASHRECORD~1 can be used with any
type of data: since it simply specifies the
data path to be a hashα-link. ~3ACCESSFN~1 is also type-less;
the user specifies the dataα-path(s) in the record declaration
itself, as described below.
.SKIP 3;
{INDENT 0,3;}2. ↓_recordα-name_↓ is a literal atom used to identify
the record declaration for dumping to files via the ~3RECORDS~1
↓_prettydef_↓ macro,{INDEXX(|RECORDS (prettydef macro)|)} and for
creating instances of the record via ~3CREATE~1.
{INDEXX(|CREATE (record package)|)}
For most top-level declarations, ↓_recordα-name_↓
is optional, e.g. ~3(RECORDα (IDα (FROMα TO)α .α TEXT))~1 is
perfectly acceptable.{XNOTE; SEND FOOT ⊂
{INDENT 0,4;}{!NOTE!} \If ↓_recordα-name_↓ is omitted, it simply means that
the user cannot specify the record by name, e.g. when calling
~3CREATE~1, or when using the ~3RECORDS~1 ↓_prettydef_↓ command.
.SKIP 2;
.SKIP 3;
{INDEXX(|TYPERECORD (record  package)|)}
↓_recordα-name_↓ is obligatory and is used as an
indicator in ~3CAR~1 of the datum to signify what "type" of record it is.
~3CREATE~1 will insert an extra field containing ↓_recordα-name_↓ at
the beginning of the structure, and the translation of the access and
storage functions will take this extra field into account.{XNOTE;SEND FOOT ⊂
{INDENT 0,4;}{!NOTE!} \Note: this type-field is used by the record package
in the translation of TYPE? expressions.
.SKIP 2;
.SKIP 3;
{INDENT 3,3;}For subfield declarations, ↓_recordα-name_↓ is also
obligatory, and specifies the parent field that is being elaborated,
as described below.
.SKIP 3;
{INDENT 0,3;}3. ↓_fields_↓ describes the structure of the record. Its
exact interpretation varies with the ↓_recordα-type_↓:
.SKIP 3;
{INDEXX(|RECORD (record package)|)}{INDENT 10,10;}For ~3RECORD~1,
↓_fields_↓ is a list whose non-~3NIL~1 literal atoms are taken as
field-names to be associated with the corresponding elements and
tails of a list structure. ~3NIL~1 can be used as a place marker to
fill an unnamed field, e.g. ~3(Aα NILα B)~1 describes a three element
list, with ~3B~1 corresponding to the third element.
.SKIP 3;
{INDEXX(|TYPERECORD (record package)|)}{INDENT 10,10;}For
~3TYPERECORD~1, ↓_fields_↓ has the same meaning as for ~3RECORD~1.
However, since ~3CAR~1 of the datum contains an indicator signifying
its "type," the translation of the access/storage functions differ
from those of ~3RECORD~1. For example, for
X:FROM~1 translates as ~3(CAADDRα X)~1, not
~3(CAADRα X)~1.
.SKIP 3;
{INDEXX(|DATATYPE (record package)|)}{INDENT 10,10;}For ~3DATATYPE~1,
the implementation allows the user to
specify data structures which are more compact and which can be
accessed faster than if list structures were used. When the user
declares a ~3DATATYPE~1 for the first time, the record package
informs the garbage collector of the number of pointer fields and
unboxed fields in the data-structure. The system allocates storage
space and a type number for that data type. Subsequently, allocation
and garbage collection of user defined data types is the same as for
any other data type in INTERLISP. However,  it provides a significant
increase in the data definition capabilities of LISP.
The scheme used provides the ability to define fixed length data
types composed of fixed length components.  Each component may be a
pointer, an integer,  a floating point number,  or some number of
For ~3DATATYPE~1 record  declarations, ↓_fields_↓ is  a list of  the fields
contained in the data type. Each field is of the form:
↓_field-name_↓ or     \\the field is a pointer (i.e. you can store
(↓_field-name_↓ POINTER)\\any arbitrary LISP datum in this field)

(↓_field-name_↓α BITS n)\\the field is an n-bit unsigned integer.

(↓_field-name_↓α BIT)  \\equivalent to ...BITS 1)

(↓_field-name_↓ BYTE)  \\equivalent to ...BITS 7)

(↓_field-name_↓ INTEGER)\\the field is a full word signed integer.

(↓field-name_↓ FLOATING)\\the field is a floating point number.

(↓_field-name_↓ HALFWORD)\\ the field is a half word signed integer.
HALF=HALFWORD are all allowable synonyms.
.SKIP 2;
.SKIP 3;
For example, a declaration
would define a data type MESSAGE which occupied two words of storage with
one pointer field.
~3DATATYPE~1s differ from other types of records in that the garbage
collector must be made aware of them explicitly,  separate pages of
the LISP address space must be allocated for data of this type;
further, as in TYPERECORD's, there must be a unique identifier (atom)
which is common among for all declarations which are intended to reference
the same data types.
Thus, the ↓_name_↓ portion of a ~3DATATYPE~1 declaration may be
(↓_recordname_↓α ↓_datatypename_↓) where ↓_recordname_↓ is the name
used in ~3CREATE~1 and ~3TYPE?~1 expressions as well as for
identification in ~3RECORDS~1 prettycoms; while ↓_datatypename_↓ must
be a unique identifier which is common to local and global
declarations which expect to access datum of this type. Normally,
↓_name_↓ is both the ↓_recordname_↓ and the ↓_datatypename_↓.
{INDENT 10,10;}{INDEXX(|ATOMRECORD (record package)|)}For ~3ATOMRECORD~1,
↓_fields_↓ is a list of property names, e.g.
Accessing will be performed with ↓_getp_↓, storing with ↓_put_↓.
.SKIP 3;
{INDENT 10,10;}{INDEXX(|PROPRECORD (record package)|)}For
~3PROPRECORD~1, ↓_fields_↓ is also a list of property names.
Accessing is performed with ↓_get_↓, storing with ↓_putl_↓.
SEND FOOT ⊂ {INDENT 0,4;}{!NOTE!} \A new function (part of the record
package), similar to ↓_put_↓, which takes a list as its first
argument, searches the list looking for an occurrence of the given
property name (its second argument). If found, it replaces the next
element with the new property value (its third argument), otherwise
adds the property name and property value to the list.
.SKIP 2;
For example,
.SKIP 1;
could be used to describe an entry on the history list
(see Section 22).{XNOTE; SEND FOOT ⊂
{INDENT 0,4}{!NOTE!} \Note that ~3(ATOMRECORDα (FOOα FIE)))~1
is equivalent to ~3(RECORD (VALUE . PROPS)
the difference being in the translations.
In the first case, ~3X:FIE~1 translates as ~3(GETPα Xα (QUOTEα FIE)),~1,
in the second case, as ~3(GETα (CDRα X)α (QUOTEα FIE))~1.
Note also that in the first case, if ~3X~1
is not a literal atom, INTERLISP
(i.e. ↓_getp_↓) will generate an error.
.SKIP 2;
.SKIP 3;
{INDENT 10,10;}{INDEXX(|HASHRECORD (record package)|)}For
~3HASHRECORD~1 (or ~3HASHLINK~1), ↓_fields_↓ is usually just
↓_fieldα-name_↓, i.e. an atom, and is the name by which the
corresponding hashα-value is referred to. For example,
for ~3(RECORDα (Aα Bα .α C)α (HASHRECORDα Bα FOO)),
X:FOO~1 translates as ~3(GETHASHα (CADRα X)).~1
If ↓_fieldα-name_↓ is a list,
it is interpreted as (fieldα-nameα arraynameα arraysize).
In this case, ↓_arrayname_↓ indicates the hashα-array to be used.
For example, ~3(HASHRECORDα (CLISPα CLISPARRAY)) would permit
the user to obtain the CLISP translation of ~3X~1 by simply writing ~3X:CLISP~1.
↓_arraysize_↓ is used for initializing the hash array:
if ↓_arrayname_↓ has not been initialized
at the time of the declaration, it will be set to
~3(HARRAYα (ORα arraysizeα 100))~1.
.SKIP 3;
{INDENT 10,10;}For ~3ARRAYRECORD~1{INDEXX(|ARRAYRECORD (record package)|)},
↓_fields_↓ is a list of fieldα-names that are associated
with the corresponding elements of the array.
~3NIL~1 can be used as a place marker for an unnamed
field (element). Positive integers can be used
as abbreviation for the corresponding number of
~3NIL~1s. For example,
describes an eight element array, with ~3ORG~1 corresponding to the
first element,  ~3ID~1 to the fourth, and ~3TEXT~1 to the eighth.
.SKIP 3;
{INDENT 10,10;}For ~3ACCESSFN~1{INDEXX(|ACCESSFN (record package)|)}
(or ~3ACCESSFNS~1), ↓_fields_↓ is a list of the form (fieldα-nameα
accessdefinitionα setdefinition), or a list of elements of this form.
↓_accessdefinition_↓ is a function of one argument, the datum, and
will be used for accessing. ↓_setdefinition_↓ is a function of two
arguments, the datum and the new value,  and is used for
storing.{XNOTE; SEND FOOT ⊂ {INDENT 0,4;}{!NOTE!} \Currently, an
error is generated if ~3CREATE~1 is called with a record declaration
containing an ~3ACCESSFNS~1 record-type.
.SKIP 2;
For example, ~3(HASHRECORDα FOO)~1 and
~3(ACCESSFNα (FOOα GETHASHα PUTHASH))~1 are equivalent:
in both cases, ~3X:FOO~1 translates as ~3(GETHASHα FOO)~1.
Similarly, ~3(ACCESSFNα (DEFα GETDα PUTD))~1 would
permit defining functions by writing fn:~3DEF~1←definition.{XNOTE; SEND FOOT ⊂
(DEFINEα (LISTα (LISTα FNα DEF]~1 would be preferable to using ↓_putd_↓.
.SKIP 2;
.SKIP 3;
.KEEP 7;
{INDENT 0,3;}4. α{defaults and/or subfieldsα} is optional. It may contain
expressions of the form:
.SKIP 3;
{INDENT 10,10;}(1) fieldα-nameα ←α form - specifies the default value
for ↓_fieldα-name_↓. Used by ~3CREATE~1.
.SKIP 3;
(2) ~3DEFAULT~1α ←α form - specifies default value for every field
not given a specific default via (1).
.SKIP 3;
(3) a subfield declaration - i.e. a record declaration of any of the
above types. For subfield declarations, ↓_recordα-name_↓ is
obligatory. Instead of identifying the declaration as with the case
of top level declarations, ↓_recordα-name_↓ identifies the parent
field or record that is being described by the subfield declaration.
It must be either the recordα-name of the immediately superior
declaration, or one of its fieldα-names (or else an error is
.SKIP 3;
Subfields can be nested to an arbitrary depth.
.SKIP 3;
Note that in some cases, it makes sense for a given field to have more than
one subfield declaration. For example, in
~3B~1 is elaborated by both a ~3PROPRECORD~1 and ~3HASHRECORD~1.
Similarly, ~3(RECORDα (Aα B) (RECORDα Aα (Cα D))
(RECORDα Aα (FOOα FIE)))~1 is also acceptable,
and essentially "overlays" ~3(FOOα FIE)~1 and ~3(Cα D)~1, i.e.
~3X:FOO~1 and ~3X:C~1 would be equivalent. In such cases, the
~2first~1 subfield declaration is the one used by ~3CREATE~1, e.g.
.SKIP 1;
~3(RECORDα Xα (Aα B)α α 
(RECORDα Aα (Cα D))α α (RECORDα Aα (FOOα FIEα FUM))α )~1
will cause ~3(CREATEα X)~1 to construct
~3((NILα NIL)α NIL)~1, not ~3((NILα NILα NIL)α NIL)~1,
as would be the case if the subfield declaration
~3(RECORDα Aα (Cα D))~1 were removed.
{INDEXX(|END record declarations (in clisp)|)}
.SKIP 4;
{INDENT 0,0}↓_CREATE_↓{INDEXX(|BEGIN CREATE (record package)|)}
.SKIP 3;
Record operations can be applied to arbitrary structures,
i.e. structures created directly by user programs can be
manipulated in a dataα-independent manner using record declarations.
However, to be completely dataα-independent,
new data should be created using the same
declarations that define its data paths. This can be done by means of
an expression of the form
~3(CREATEα recordα-nameα .α α{assignmentsα})~1.{XNOTE; SEND FOOT ⊂
{INDENT 0,4;}{!NOTE!} \~3CREATE~1 is
not defined as a function. Instead, DWIM calls
the appropriate function in the record package giving
it the entire ~3CREATE~1 expression as an argument.
The translation of the ~3CREATE~1 expression, i.e. the
INTERLISP form which is evaluated to construct the datum,
is then stored elsewhere, as with iterative statements and pattern matches.
.SKIP 2;
α{assignmentsα} is optional and may contain expressions of the following form:
.SKIP 3;
{INDENT 5,34;}(1) fieldα-nameα ←α form∂(35)specifies initial value
for ↓_fieldα-name_↓.
.SKIP 3;
{INDENT 5,34;}(2) ~3USING~1{INDEXX(|USING (record package)|)}α
form∂(35)specifies that for all fields not given a value by (1), the
value of the corresponding field in ↓_form_↓ is to be used.
.SKIP 3;
(3) ~3COPYING~1{INDEXX(|COPYING (record package)|)} form∂(35)like
~3USING~1 except the corresponding values are copied (↓_copy_↓).
.SKIP 3;
(4) ~3REUSING~1{INDEXX(|REUSING (record package)|)} form∂(35)like
~3USING~1, except that wherever possible, the corresponding
~2structure~1 in ↓_form_↓ is used (similar to operation of
↓_subpair_↓ and ↓_sublis_↓).
.SKIP 3;
{INDENT 0,0;}For example, following ~3(RECORDα FOOα (Aα Bα C))~1,
.SKIP 1;
~3(CREATEα FOOα A←Tα USINGα X)~1 translates as ~3(LIST T (CADR X) (CADDR X)),~1
.SKIP 1;
as ~3(LIST T (COPY (CADR X)) (COPY (CADDR X))),~1 and
.SKIP 1;
~3(CREATEα FOOα A←Tα REUSINGα X)~1 as ~3(CONS T (CDR X)).~1
.SKIP 3;
{INDENT 0,0;}A ~3CREATE~1 expression translates into an appropriate
INTERLISP form using ↓_cons_↓, ↓_list_↓, ↓_put_↓, ↓_putl_↓,
↓_puthash_↓, ↓_seta_↓, etc., that creates the new datum with the
various fields initialized to the appropriate values.
If values are neither explicitly specified, nor implicitly specified
via ~3USING~1 or ~3COPYING~1, the DEFAULT value in the
declaration is used, if any,{XNOTE; SEND FOOT ⊂
{INDENT 0,4;}{!NOTE!} \For ~3RECORD~1 and ~3TYPERECORD~1 declarations
with non-~3NIL~1 defaults, all elements and named tails will be
initialized; unnamed tails will ~2not~1 be initialized. For example,
~3(RECORDα FOOα (Aα NILα B)α DEFAULT←T) will
cause ~3(CREATEα FOO)~1 to construct
~3(Tα Tα T)~1 not ~3(Tα Tα Tα .α T)~1. Of course,
~3(RECORDα FOOα (Aα Bα .α C)α DEFAULT←T)~1
will cause ~3(CREATEα FOO)~1 to construct
~3(Tα Tα .α T)~1 as expected.
.SKIP 2;
otherwise ~3NIL~1.{XNOTE; SEND FOOT ⊂
{INDENT 0,4;}{!NOTE!} \For ~3PROPRECORD~1, initialization
is only performed where necessary. For example,
~3(RECORDα FOOα (Aα B)α (PROPRECORDα (Cα Dα E)))~1 would cause
~3(CREATEα FOO)~1 to construct ~3(NILα NIL)~1,
not ~3(NILα (Cα NILα Dα NILα Eα NIL))~1.
however, will construct ~3(NILα (Cα Tα Dα Tα Eα T))~1.
.SKIP 2;
{INDEXX(|END CREATE (record package)|)}
.SKIP 4;
.KEEP 15;
.SKIP 1 << for break >>
.SKIP 2;
{INDENT 0,0;}Record operations are implemented by replacing
expressions of the form ~3X:FOO~1 by
{INDEXX(|FETCH (use in records in clisp)|)}
~3(FETCHα FOOα OFα X)~1, and ~3X:FOO←Y~1 by
{INDEXX(|REPLACE (use in records in clisp)|)}~3(REPLACEα FOOα OFα Xα
recognizes expressions input in this form.
.SKIP 2;
and then storing the translation elsewhere, usually in a hash array,
as described on {PAGEREF L!5}. Translations of ~3CREATE~1 expressions
are also stored elswhere.
.SKIP 3;
The translation of each record operation is computed using
information retrieved from the property list of the field name, under
Thus, (global) field names must be unique, i.e. cannot be the same as
the name of any other field in any other record. {L!42:}Records can
also be declared local to a particular function by using a CLISP
declaration, as described on {PAGEREF L!41}. Local record
declarations override global ones, and a local record can have a
field name the same as that of a local record of another function, or
the same as a field name of a global record.
.SKIP 1 << for break >>
.KEEP 5; SKIP 2;
{INDENT 0,0;}For both global and local records, the translation is
computed using all CLISP declarations in effect as described on
{PAGEREF L!14}, e.g. if the declaration ~3UNDOABLE~1 in in effect,
~3/RPLACA, /RPLACD, /PUTHASH~1, etc. will be used.
.SKIP 1 << for break >>
.KEEP 5; SKIP 2;
{INDENT 0,0;}When the user redeclares a global record, the
translations of all expressions involving that record are
automatically deleted,{XNOTE; SEND FOOT ⊂ {INDENT 0,4;}{!NOTE!} \from
↓_clisparray_↓. If the user is not using this method for storing
translations, i.e. is instead using the CLISP%_ method
({PAGEREF L!60}),
those expressions already translated will remain as they are.
(There is no practical way to locate them.)
.SKIP 2;
and thus will be recomputed using the new information. If the user
changes a ~2local~* record declaration, or changes some other CLISP
declaration, e.g. ~3STANDARD~1 to ~3FAST~1, and wishes the new
information to affect record expressions already translated, he must
make sure the corresponding translations are removed, usually either
by CLISPIFYING or changing the expression by editing it.
{INDEXX(|END record package (in clisp)|)}
.begin nofill


change footnote for proprecords about smashing into field

retranslation by !DW

The  allocation of  space  within a  datum  of a  given  type is  not
necessarily  in   the  order  that  the  fields   are  given  in  the
declaration: viz.

BIT 12]

here, the actual representation of a MESSAGE would be:

 bit:	0		  17 18  21 22    33 34 35
	|	header	    |length|  date  |un- |   word 1
	|		    |	   |	    |used|
	|	count	    |	   unused	 |    word 2
	|		    |			 |


Since a DATATYPE need be allocated even when no translating
information is necessary, the RECORDS prettymacro prints out a
that IS copied to the .COM file upon compilation.  This is so files
with DATATYPE declarations can be compiled and loaded and run without
redeclaring the records.

DEFTYPE[nwrds; nptrs]

 Defines a new data type such that each object of that type has nwrds
words and nptrs pointers within those nwrds.  If there are no
available types, an attempt is made to find a data type which has;
(1) the proper number of pointers,  (2) at least the needed number of
words,  and (3) has been marked as "not in use" via TYPESTATUS.  The
data type number selected is returned as the value of DEFTYPE.


 Returns the number of pointers in an object whose type is typen.


 Returns the number of words in a object whose type is typen.


 Allocates an object of type typen,  clears its contents to zero and
returns a pointer to it.

USERCONS[typen; p1; p2; ...  ; pn]

 Similar to NALLOC but fills in the pointers within the object with
the arguments p1, p2, ...  ,  pn.  If too many arguments are given,
the extra ones are ignored.  If too few arguments are given, NILs are

TYPESTATUS[typen; status]

 Sets the status of data type typen to status and returns the
previous status.  If status is NIL,  the status of typen is left
unchaged.  The meaning of the status is as follows:

Status = 0 - the data type has never been used.

	= 1 - the data type is currently in use.  The status is set
to 1 by DEFTYPE when it selects a type.

	= 2 - the type was previously in use,  but is now available
for selection by DEFTYPE if no other types are available.

Actually,  a status value of 1 is the only value DEFTYPE checks for.
It is advisable to set the status to 2, instead of 0, when freeing a
data type,  simply to distinguish between "not in use" and "has never
been in use".

DEFEVAL[typen; fn]

	Defines how the data type typen is to be evaluated. When EVAL
tries to evaluate an item of type typen,  it will call fn with the
item as its argument.  The result returned by fn is then the result
of the evaluation.  If typen is not a valid type number,  or if the
type number is that of lists, litatoms,  or numbers,  then DEFEVAL
simply returns NIL.  If fn is T or EVAL,  the evaluation is reset to
the default where the items of the given type simply evaluate to
themselves.  If fn is NIL,  the evaluation function is not changed,
and the current one, for that type, is returned.

For compatibility with the compiler,  there is the variable
COMPILETYPELST.  COMPILETYPELST is an a-list of type-number, function
pairs.  When an attempt is made to compile code for an item whose
type number is on COMPILETYPELST,  the item is passed to the
corresponding function.  The result from the function,  as with
macros, is then compiled in place of the original item.  Like macros,
the function may call compiler functions itself,  and return

For example,  (24 .  (LAMBDA(X) <'PRINT (KWOTE X)>)) will cause
unquoted strings to be compiled as a call to PRINT.  The DEFEVAL
equivalent is DEFEVAL(24 PRINT).