{Begin Chapter The Record Package}
{Title The Record Package}
{Text


{note The record package was written by L. M. Masinter.}


{Tag RecordPackage}

{index *BEGIN* record package}


The advantages of "data abstraction" 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 encourages and facilitates this good programming practice by providing a uniform syntax for creating, accessing and storing data into many different types of data structures (arrays, list structures, association lists, etc.) as well as removing from the user the task of writing the various manipulation routines.  The user declares (once) the data structures used by his programs, and thereafter indicates the manipulations of the data in a data-structure-independent manner.  Using the declarations, the record package automatically computes the corresponding Interlisp expressions necessary to accomplish the indicated access/storage operations.  If the data structure is changed by modifying the declarations, the programs automatically adjust to the new conventions.


The user describes the format of a data structure (record) by making a "record declaration" (see {PageRef Tag RecordDeclarations}).  The record declaration is a description of the record, associating names with its various parts, or "fields".  For example, the record declaration {lisp (RECORD MSG (FROM TO . TEXT))} describes a data structure called {lisp MSG}, which contains three fields: {lisp FROM}, {lisp TO}, and {lisp TEXT}.  The user can reference these fields by name, to retrieve their values or to store new values into them, by using the {lisp FETCH} and {lisp REPLACE} operators ({PageRef (Record Operator) FETCH}).  The {lisp CREATE} operator ({PageRef (Record Operator) CREATE}) is used for creating new instances of a record, and {lisp TYPE?} ({PageRef (Record Operator) TYPE?}) is used for testing whether an object is an instance of a particular record.  (note: all record operators can be in either upper or lower case.)


Records may be implemented in a variety of different ways, as determined by the first element ("record type") of the record declaration.  {lisp RECORD} (used to specify elements and tails of a list structure) is just one of several record types currently implemented.  The user can specify a property list format by using the record type {lisp PROPRECORD}, or that fields are to be associated with parts of a data structure via a specified hash array by using the record type {lisp HASHLINK}, or that an entirely new data type be allocated (as described on {PageRef Tag UserDataTypes}) by using the record-type {lisp DATATYPE}.


The record package is implemented through the DWIM/CLISP facilities, so it contains features such as spelling correction on field names, record types, etc.  Record operations are translated using all CLISP declarations in effect (standard/fast/undoable); it is also possible to declare local record declarations that override global ones (see {PageRef Tag CLISPDeclarations}).


The file package includes a {filecom RECORDS}{index RECORDS FileCom} file package command for dumping record declarations ({PageRef FileCom RECORDS}), and {fn FILES?} and {fn CLEANUP} will inform the user about records that need to be dumped.



{Begin SubSec FETCH and REPLACE}
{Title FETCH and REPLACE}
{Text

{index *PRIMARY* FETCH (Record Operator)}
{index *PRIMARY* REPLACE (Record Operator)}

The fields of a record are accessed and changed with the {lisp FETCH} and {lisp REPLACE} operators.  If the record {lisp MSG} has the record declaration {lisp (RECORD MSG (FROM TO . TEXT))}, and {lisp X} is a {lisp MSG} data structure, {lisp (fetch FROM of X)} will return the value of the {lisp FROM} field of {lisp X}, and {lisp (replace FROM of X with Y)} will replace this field with the value of {lisp Y}.  In general, the value of a {lisp REPLACE} operation is the same as the value stored into the field.

{note exceptions: via ACCESSFNS, users specify functional forms to do fetching and replacing --- it is up to the user to make sure that replace form returns the value stored in the field}


Note that the form {lisp (fetch FROM of X)} implicitly states that {lisp X} is an instance of the record {lisp MSG}, or at least it should to be treated as such for this particular operation.  In other words, the interpretation of {lisp (fetch FROM of X)} never depends on the value of {lisp X}.  Therefore, if {lisp X} is not a {lisp MSG} record, this may produce incorrect results.  The {lisp TYPE?} record operation ({PageRef (Record Operator) TYPE?}) may be used to test the types of objects.


If there is another record declaration, {lisp (RECORD REPLY (TEXT . RESPONSE))}, then {lisp (fetch TEXT of X)} is ambiguous, because {lisp X} could be either a {lisp MSG} or a {lisp REPLY} record.  In this case, an error will occur, {lisp AMBIGUOUS RECORD FIELD}.{index AMBIGUOUS RECORD FIELD Error}  To clarify this, {lisp FETCH} and {lisp REPLACE} can take a list for their "field" argument:  {lisp (fetch (MSG TEXT) of X)} will fetch the {lisp TEXT} field of an {lisp MSG} record.

Note that if a field has an {it identical} interpretation in two declarations, e.g. if the field {lisp TEXT} occurred in the same location within the declarations of {lisp MSG} and {lisp REPLY}, then {lisp (fetch TEXT of X)} would {it not} be considered ambiguous.


Another complication can occur if the fields of a record are themselves records.  The fields of a record can be further broken down into sub-fields by a "subdeclaration" within the record declaration (see {PageRef Tag RecordSubDeclarations}).  For example,

{lispcode
(RECORD NODE (POSITION . LABEL) (RECORD POSITION (XLOC . YLOC)))}

permits the user to access the {lisp POSITION} field with {lisp (fetch POSITION of X)}, or its subfield {lisp XLOC} with {lisp (fetch XLOC of X)}.


The user may also elaborate a field by declaring that field name in a {it separate} record declaration (as opposed to an embedded subdeclaration).  For instance, the {lisp TEXT} field in the {lisp MSG} and {lisp REPLY} records above may be subdivided with the seperate record declaration {lisp (RECORD TEXT (HEADER . TXT))}.  Fields of subfields (to any level of nested subfields) are accessed by specifying the "data path" as a list of record/field names, where there is some path from each record to the next in the list.  For instance, {lisp (fetch (MSG TEXT HEADER) of X)} indicates that {lisp X} is to be treated as a {lisp MSG} record, its {lisp TEXT} field should be accessed, and {it its} {lisp HEADER} field should be accessed.  Only as much of the data path as is necessary to disambiguate it needs to be specified.  In this case, {lisp (fetch (MSG HEADER) of X)} is sufficient.  The record package interprets a data path by performing a tree search among all current record declarations for a path from each name to the next, considering first local declarations (if any) and then global ones.  The central point of separate declarations is that the (sub)record is {it not} tied to another record (as with embedded declarations), and therefore can be used in many different contexts.  If a data-path rather than a single field is ambiguous, (e.g., if there were yet another declaration {lisp (RECORD TO (NAME . HEADER))} and the user specified {lisp (fetch (MSG HEADER) of X)}), the error {lisp AMBIGUOUS DATA PATH}{index AMBIGUOUS DATA PATH Error} is generated.


{lisp FETCH} and {lisp REPLACE} forms are translated using the CLISP declarations in effect.  {lisp FFETCH}{index FFETCH (Record Operator)} and {lisp FREPLACE}{index FREPLACE (Record Operator)} are versions which insure fast CLISP declarations will be in effect, {lisp /REPLACE}{index /REPLACE (Record Operator)} insures undoable declarations.
 

}{End SubSec FETCH and REPLACE}




{Begin SubSec CREATE}
{Title CREATE}
{Text

{index *PRIMARY* CREATE (Record Operator)}

Record operations can be applied to arbitrary structures, i.e., the user can explicitely creating a data structure (using {fn CONS}, etc), and then manipulate it with {lisp FETCH} and {lisp REPLACE}.  However, to be consistant with the idea of data abstraction, new data should be created using the same declarations that define its data paths.  This can be done with an expression of the form:

{lispcode (CREATE {arg RECORD-NAME} . {arg ASSIGNMENTS})}

A {lisp CREATE} expression translates into an appropriate Interlisp form using {fn CONS}, {fn LIST}, {fn PUTHASH}, {fn ARRAY}, etc., that creates the new datum with the various fields initialized to the appropriate values.  {arg ASSIGNMENTS} is optional and may contain expressions of the following form:


{Begin LabeledList ASSIGNMENTS}

{Label {arg FIELD-NAME} ← {arg FORM}}
{Item
Specifies initial value for {arg FIELD-NAME}.
}


{Label {lisp USING {arg FORM}}}
{Item
Specifies that for all fields not explicitly given a value, the value of the corresponding field in {arg FORM} is to be used.{index USING (Record Package)}
}


{Label {lisp COPYING {arg FORM}}}
{Item
Similar to {lisp USING} except the corresponding values are copied (with {fn COPYALL}).{index COPYING (Record Package)}
}


{Label {lisp REUSING {arg FORM}}}
{Item
{index REUSING (Record Package)}Similar to {lisp USING}, except that wherever possible, the corresponding {it structure} in {arg FORM} is used.
}


{Label {lisp SMASHING {arg FORM}}}
{Item
A new instance of the record is not created at all; rather, the value of {arg FORM} is used and smashed.{index SMASHING (Record Package)}
}

{End LabeledList ASSIGNMENTS}


The record package goes to great pains to insure that the order of evaluation in the translation is the same as that given in the original {lisp CREATE} expression if the side effects of one expression might affect the evaluation of another.  For example, given the declaration {lisp (RECORD CONS (CAR . CDR))}, the expression {lisp (CREATE CONS CDR←X CAR←Y)} will translate to {lisp (CONS Y X)}, but {lisp (CREATE CONS CDR←(FOO) CAR←(FIE))} will translate to {lisp ((LAMBDA ($$1) (CONS (PROGN (SETQ $$1 (FOO)) (FIE)) $$1)))} because {lisp FOO} might set some variables used by {lisp FIE}.




Note that {lisp (CREATE {arg RECORD} REUSING {arg FORM} ...)} does not itself do any destructive operations on the value of {arg FORM}.
The distinction between {lisp USING} and {lisp REUSING} is that {lisp (CREATE {arg RECORD} REUSING {arg FORM} ...)} will incorporate as much as possible of the old data structure into the new one being created, while {lisp (CREATE {arg RECORD} USING {arg FORM} ...)} will create a completely new data structure, with only the contents of the fields re-used.  For example, {lisp CREATE REUSING} a {lisp PROPRECORD} just {fn CONS}es the new property names and values onto the list, while {lisp CREATE USING} copies the top level of the list.  Another example of this distinction occurs when a field is elaborated by a subdeclaration: {lisp USING} will create a new instance of the sub-record, while {lisp REUSING} will use the old contents of the field (unless some field of the subdeclaration is assigned in the {lisp CREATE} expression.)


If the value of a field is neither explicitly specified, nor implicitly specified via {lisp USING}, {lisp COPYING} or {lisp REUSING}, the default value in the
declaration is used, if any, otherwise {lisp NIL}.  (Note: For {lisp BETWEEN} fields in {lisp DATATYPE} records, {arg N{sub 1}} is used; for other non-pointer fields zero is used.)  For example, following {lisp (RECORD A (B C D) D ← 3)},

{lispcode (CREATE A B←T)  ==>  (LIST T NIL 3)}

{lispcode (CREATE A B←T USING X)  ==>  (LIST T (CADR X) (CADDR X))}

{lispcode (CREATE A B←T COPYING X))  ==>  [LIST T (COPYALL (CADR X)) (COPYALL (CADDR X]}

{lispcode (CREATE A B←T REUSING X)  ==>  (CONS T (CDR X))}


{note better examples!!!!}


{Begin Note}
Date: 19 NOV 1978 1418-PST
From: MASINTER

fixed RECORD so that if a field has both a sub-declaration and a default value, as in (RECORD FOO (A . B) (RECORD B (C . D)) B ← NIL), that a CREATE expression will use the default value for B (i.e. B=NIL) unless one of the fields of the sub-declaration is given; in the above example, that means that (create FOO) returns (LIST NIL), but (create FOO C ← 3) returns (LIST NIL 3).
{End Note}


{Begin Note}
Document CCREATE?

Date: 15 JUL 1976 1637-PDT
From: MASINTER

The way CCREATE works now is that the thing is evaluated and the value treated as if it were a CREATE form; that is, the only difference between CREATE and CCREATE is that CCREATE does an EVAL before doing the subst that CREATE does.

When the CCREATE form is evaluated, the variables FIELDS.IN.CREATE, USINGTYPE, and USINGEXPR may be interrogated.
{End Note}


}{End SubSec CREATE}





{Begin SubSec TYPE?}
{Title TYPE?}
{Text

{index *PRIMARY* TYPE? (Record Operator)}

The record package allows the user to test if a given datum "looks like" an instance of a record.  This can be done via an expression of the form

{lispcode (TYPE? {arg RECORD-NAME} {arg FORM})}

{lisp TYPE?} is mainly intended for records with a record type of {lisp DATATYPE} or {lisp TYPERECORD}.  For {lisp DATATYPE}s, the {lisp TYPE?} check is exact; i.e. the {lisp TYPE?} expression will return non-{lisp NIL} only if the value of {arg FORM} is an instance of the record named by {arg RECORD-NAME}.  For {lisp TYPERECORD}s, the {lisp TYPE?} expression will check that the value of {arg FORM} is a list beginning with {arg RECORD-NAME}.  For {lisp ARRAYRECORD}s, it checks that the value is an array of the correct size.  For {lisp PROPRECORD}s and {lisp ASSOCRECORD}s, a {lisp TYPE?} expression will make sure that the value of {arg FORM} is a property/association list with property names among the field-names of the declaration.


Attempting to execute a {lisp TYPE?} expression for a record of type {lisp ACCESSFNS}, {lisp HASHLINK} or {lisp RECORD} will cause an error, {lisp TYPE? NOT IMPLEMENTED FOR THIS RECORD}.{index TYPE? NOT IMPLEMENTED FOR THIS RECORD Error}  The user can (re)define the interpretation of {lisp TYPE?} expressions for a particular declaration by inclusion of an expression of the form {lisp (TYPE? {arg COM})} in the record declaration (see {PageRef Tag RecordDeclarationTYPE?}).

}{End SubSec TYPE?}




{Begin SubSec WITH}
{Title WITH}
{Text

{index *PRIMARY* WITH (Record Operator)}


Often it is necessary to manipulate the values of the fields of a particular record.  The {lisp WITH} construct can be used to talk about the fields of a record as if they were variables within a lexical scope:

{lispcode (WITH {arg RECORD-NAME} {arg RECORD-INSTANCE} {arg FORM{sub 1}} {ellipsis} {arg FORM{sub N}})}

{arg RECORD-NAME} is the name of a record, and {arg RECORD-INSTANCE} is an expression which evaluates to an instance of that record.  The expressions {arg FORM{sub 1}} {ellipsis} {arg FORM{sub N}} are evaluated so that references to variables which are field-names of {arg RECORD-NAME} are implemented via {lisp fetch} and {fn SETQ}s of those variables are implemented via {lisp replace}. 

For example, given

{lispcode
(RECORD RECN (FLD1 FLD2))
(SETQ INST (CREATE RECN FLD1 ← 10 FLD2 ← 20))}

Then the construct

{lispcode
(with RECN INST (SETQ FLD2 (PLUS FLD1 FLD2]}

is equivalent to

{lispcode
(replace FLD2 of INST with (PLUS (fetch FLD1 of INST) (fetch FLD2 of INST]}

Note that the substitution is lexical: this operates by actually doing a substitution
inside the forms.


}{End SubSec WITH}





{Begin SubSec Record Declarations}
{Title Record Declarations}
{Text


{Tag RecordDeclarations}


{index *BEGIN* record declarations}
{index *PRIMARY* record declarations}


A record is defined by evaluating a record declaration,{foot
Local record declarations are defined by including an expression of this form in the CLISP declaration for that function, rather than evaluating the expression itself (see {PageRef Tag CLISPLocalDeclarations}).
}{comment endfootnote}
which is an expression of the form:


{lispcode
({arg RECORD-TYPE} {arg RECORD-NAME} {arg FIELDS} . {arg RECORD-TAIL})}



{arg RECORD-TYPE}{index record-type} specifies the "type" of data being described by the record declaration, and thereby implicitly specifies how the corresponding access/storage operations are performed.  {arg RECORD-TYPE} currently is either {lisp RECORD}, {lisp TYPERECORD}, {lisp ARRAYRECORD}, {lisp ATOMRECORD}, {lisp ASSOCRECORD}, {lisp PROPRECORD}, {lisp DATATYPE}, {lisp HASHLINK}, {lisp ARRAYBLOCK} or {lisp ACCESSFNS}.  {lisp RECORD} and {lisp TYPERECORD} are used to describe list structures, {lisp DATATYPE} to describe user data-types, {lisp ARRAYRECORD} to describe arrays, {lisp ATOMRECORD} to describe (the property list of) litatoms, {lisp PROPRECORD} to describe lists in property list format, and {lisp ASSOCRECORD} to describe association list format.
{lisp HASHLINK} can be used with any type of data: it simply specifies the data path to be a hash-link.  {lisp ACCESSFNS} is also type-less; the user specifies the data-paths in the record declaration itself, as described below.


{arg RECORD-NAME} is a litatom used to identify the record declaration for creating instances of the record via {lisp CREATE}, testing via {lisp TYPE?}, and dumping to files via the {filecom RECORDS} file package command ({PageRef FileCom RECORDS}).  {lisp DATATYPE} and {lisp TYPERECORD} declarations also use {arg RECORD-NAME} to identify the data structure (as described below).

{Begin Note}
Why would anyone ever want to do this??

For some top-level declarations, {arg RECORD-NAME} is optional, e.g., {lisp (RECORD (ID (FROM TO) . TEXT))} is acceptable.  However, if {arg RECORD-NAME} is omitted, the user cannot specify the record by name, e.g., in {lisp CREATE} expressions, or when using the {filecom RECORDS} file package command.
{End Note}


{arg FIELDS} describes the structure of the record.  Its exact interpretation varies with {arg RECORD-TYPE}:




{Def {Type (Record Type)} {Name RECORD}
{Text
{arg FIELDS} is a list structure whose non-{lisp NIL} literal atoms are taken as field-names to be associated with the corresponding elements and tails of a list structure.  For example, with the record declaration {lisp (RECORD MSG (FROM TO . TEXT))}, {lisp (fetch FROM of X)} translates as {lisp (CAR X)}.  

{lisp NIL} can be used as a place marker to fill an unnamed field, e.g., {lisp (A NIL B)} describes a three element list, with {lisp B} corresponding to the third element.  A number may be used to indicate a sequence of {lisp NIL}s, e.g. {lisp (A 4 B)} is interpreted as {lisp (A NIL NIL NIL NIL B)}.
}}



{Def {Type (Record Type)} {Name TYPERECORD}
{Text
Similar to {lisp RECORD}, except that {arg RECORD-NAME} is also used as an indicator in {lisp CAR} of the datum to signify what "type" of record it is.  This type-field is used by the record package in the translation of {lisp TYPE?}{index TYPE? (Record Operator)} expressions.  {lisp CREATE} will insert an extra field containing {arg RECORD-NAME} at the beginning of the structure, and the translation of the access and storage functions will take this extra field into account.  For example, for {lisp (TYPERECORD MSG (FROM TO . TEXT))}, {lisp (fetch FROM of X)} translates as {lisp (CADR X)}, not {lisp (CAR X)}.
}}



{Def {Type (Record Type)} {Name ASSOCRECORD}
{Text
{arg FIELDS} is a list of literal atoms.  The fields are stored in association-list format:

{lispcode
(({arg FIELDNAME{sub 1}} . {arg VALUE{sub 1}}) ({arg FIELDNAME{sub 2}} . {arg VALUE{sub 2}}) {ellipsis})}

Accessing is performed with {fn ASSOC} (or {fn FASSOC}, depending on current {lisp CLISP} declarations), storing with {fn PUTASSOC}.
}}



{Def {Type (Record Type)} {Name PROPRECORD}
{Text
{arg FIELDS} is a list of literal atoms.  The fields are stored in "property list" format:

{lispcode
({arg FIELDNAME{sub 1}} {arg VALUE{sub 1}} {arg FIELDNAME{sub 2}} {arg VALUE{sub 2}} {ellipsis})}

Accessing is performed with {fn LISTGET}, storing with {fn LISTPUT}.
}}



Both {lisp ASSOCRECORD} and {lisp PROPRECORD} are useful for defining data structures in which it is often the case that many of the fields are {lisp NIL}.  A {lisp CREATE} for these record types only stores those fields which are non-{lisp NIL}.  Note, however, that with the record declaration {lisp (PROPRECORD FIE (H I J))} the expression {lisp (CREATE FIE)} would still construct {lisp (H NIL)}, since a later operation of {lisp (replace J of X with Y)} could not possibly change the instance of the record if it were {lisp NIL}.



{Def {Type (Record Type)} {Name ARRAYRECORD}
{Text
{arg FIELDS} is a list of field-names that are associated with the corresponding elements of an array.  {lisp NIL} can be used as a place marker for an unnamed field (element).  Positive integers can be used as abbreviation for the corresponding number of {lisp NIL}s.  For example, {lisp (ARRAYRECORD (ORG  DEST NIL ID 3 TEXT))} describes an eight element array, with {lisp ORG} corresponding to the first element, {lisp ID} to the fourth, and {lisp TEXT} to the eighth.

Note that {lisp ARRAYRECORD} only creates arrays of pointers.  Other kinds of arrays must be implemented by the user with {lisp ACCESSFNS}.
}}



{Def {Type (Record Type)} {Name HASHLINK}
{Text
{arg FIELDS} is either an atom {arg FIELD-NAME}, or a list {lisp ({arg FIELD-NAME} {arg HARRAYNAME} {arg HARRAYSIZE})}.  {arg HARRAYNAME} indicates the hash-array to be used; if not given, {var SYSHASHARRAY} is used.  {arg HARRAYSIZE} is used for initializing the hash array:  if {arg HARRAYNAME} has not been initialized at the time of the declaration, it will be set to {lisp (LIST (HARRAY (OR {arg HARRAYSIZE} 100)))}.  {lisp HASHLINK}s are useful as subdeclarations to other records to add additional fields to already existing data-structures.  For example, suppose that {lisp FOO} is a record declared with {lisp (RECORD FOO (A B C))}.  To add an aditional field {lisp BAR}, without modifying the already-existing data strutures, redeclare {lisp FOO} with:

{lispcode (RECORD FOO (A B C) (HASHLINK FOO (BAR BARHARRAY)))}

Now, {lisp (fetch BAR of X)} will translate into {lisp (GETHASH X BARHARRAY)}, hashing off the existing {it list} {lisp X}.


{note HASHLINK may also be called HASHRECORD (HASHLINK defined as USERRECORDTYPE)}

{note CREATE is meaningless with a HASHLINK record, right?  doc??}
}}



{Def {Type (Record Type)} {Name ATOMRECORD}
{Text
{arg FIELDS} is a list of property names, e.g., {lisp (ATOMRECORD (EXPR CODE MACRO BLKLIBRARYDEF))}.  Accessing is performed with {fn GETPROP}, storing with {fn PUTPROP}.  As with {lisp ACCESSFNS}, {lisp CREATE} is not initially defined for {lisp ATOMRECORD} records.
}}


{Def {Type (Record Type)} {Name DATATYPE}
{Text
{Tag RecordDeclarationDATATYPE}Specifies that a new user data type with type name {arg RECORD-NAME} be allocated via {fn DECLAREDATATYPE} ({PageRef Fn DECLAREDATATYPE}).  Unlike other record-types, the records of a {lisp DATATYPE} declaration are represented with a completely new Interlisp type, and not in terms of other existing types.

{arg FIELDS} is a list of field specifications, where each specification is either a list {lisp ({arg FIELDNAME {arg FIELDTYPE})}, or an atom {arg FIELDNAME}.  If {arg FIELDTYPE} is omitted, it defaults to {lisp POINTER}.
Options for {arg FIELDTYPE} are:

{Begin LabeledList Other options for FIELDTYPE}

{Label {lisp POINTER}{index POINTER (record field type)}}
{Item
Field contains a pointer to any arbitrary Interlisp object.
}

{Label {lisp BITS {arg N}}{index BITS (record field type)}}
{Item
Field contains an {arg N}-bit unsigned integer.
}

{Label {lisp INTEGER} or {lisp FIXP}{index INTEGER (record field type)}{index FIXP (record field type)}}
{Item
Field contains a full word signed integer (the size is implementation-dependent).
}

{Label {lisp FLOATING} or {lisp FLOATP}{index FLOATING (record field type)}{index FLOATP (record field type)}}
{Item
Field contains a full word floating point number.
}

{Label {lisp FLAG}{index FLAG (record field type)}}
{Item
Field is a one bit field that "contains" {lisp T} or {lisp NIL}.
}

{End LabeledList Other options for FIELDTYPE}


For example, the declaration

{lispcode
(DATATYPE FOO
   ((FLG BITS 12)
    TEXT
    HEAD
    (DATE BITS 18)
    (PRIO FLOATP)
    (READ? FLAG)))}

would define a data type {lisp FOO} with two pointer fields, a full word floating point number, and fields for a 12 and 18 bit unsigned integers, and a flag (one bit).  Fields are allocated in such a way as to optimize the storage used and not necessarily in the order specified. Generally, {lisp DATATYPE}s are much more storage compact than the corresponding {lisp RECORD} structure would be; in addition, access is faster.

Other field types are allowed in some Interlisp implementations. For example, Interlisp-D includes the following field types:

{Begin LabeledList Interlisp-D options for FIELDTYPE}

{Label {lisp BYTE}{index BYTE (record field type)}}
{Item
Equvialent to {lisp BITS 8}.
}
{Label {lisp WORD}{index WORD (record field type)}}
{Item
Equvialent to {lisp BITS 16}.
}
{Label {lisp SIGNEDWORD}{index SIGNEDWORD (record field type)}}
{Item
A 16-bit signed integer.
}
{Label {lisp XPOINTER}{index XPOINTER (record field type)}}
{Item
This kind of field should be used with caution. It signals that the {lisp DATATYPE} contains a pointer field, but that field should not be reference counted by the Interlisp-D garbage collector. {lisp XPOINTER} fields are useful for implementing back-pointers in structures that would be circular and not otherwise collected by the reference-counting garbage collector.
}

{End LabeledList Interlisp-D options for FIELDTYPE}

Since the user data type must be set up at {it run}-time, the {filecom RECORDS}{index RECORDS FileCom} file package command will dump a {fn DECLAREDATATYPE} expression as well as the {lisp DATATYPE} declaration itself.  The {filecom INITRECORDS}{index INITRECORDS FileCom} file package command ({PageRef FileCom INITRECORDS}) will dump only the {fn DECLAREDATATYPE} expression.


Note: {lisp DATATYPE} declarations should be used with caution within local declarations, since a new and different data type is allocated for each one with a different name.



{Begin Note} should document somewhere (\SIGNED bits width) which returns a signed integer from an unsigned field, and (\UNSIGNED n width) which (using LOGAND) turns a signed integer into a width-bit field.
{End Note}

{note Need to document in DATATYPE record (or somewhere, with crossrefs.) the size information for FIXP vs. WORD vs. POINTER etc. so that users like me can make intelligent choices about how to reduce their storage usage.}
}}



{Def {Type (Record Type)} {Name ARRAYBLOCK}
{Text
(Not implemented in Interlisp-D)  Similar to a {lisp DATATYPE} declaration, except that the objects it creates and manipulates are arrays.  As with {lisp DATATYPE}'s, the actual order of the fields of the {lisp ARRAYBLOCK} may be shuffled around in order to satisfy garbage collector constraints.

For example,

{lispcode
(ARRAYBLOCK FOO
   ((F1 INTEGER)
    (F2 FLOATING)
    (F3 POINTER)
    (F4 BETWEEN -30 -2)
    (F5 BITS 12)
    (F6 FLAG)))}
}}



{Def {Type (Record Type)} {Name ACCESSFNS}
{Text
{arg FIELDS} is a list of elements of the form {lisp ({arg FIELD-NAME} {arg ACCESSDEF} {arg SETDEF})}, i.e. for each fieldname, the user specifies how it is to be accessed and set.  {arg ACCESSDEF} should be a function of one argument, the datum, and will be used for accessing.  {arg SETDEF} should be a function of two arguments, the datum and the new value, and will be used for storing.  {arg SETDEF} may be omitted, in which case, no storing operations are allowed.  {arg ACCESSDEF} and/or {arg SETDEF} may also be a {lisp LAMBDA} expression or a form written in terms of variables {lisp DATUM} and (in {arg SETDEF}) {lisp NEWVALUE}.{index DATUM Var}{index NEWVALUE Var}  For example, given the declaration

{lispcode
[ACCESSFNS ((FIRSTCHAR (NTHCHAR DATUM 1)
                       (RPLSTRING DATUM 1 NEWVALUE))
            (RESTCHARS (SUBSTRING DATUM 2]}

{lisp (replace FIRSTCHAR of X with Y)} would translate to {lisp (RPLSTRING X 1 Y)}.  Since no {arg SETDEF} is given for the {lisp RESTCHARS} field, attempting to perform {lisp (replace RESTCHARS of X with Y)} would generate an error, {lisp REPLACE UNDEFINED FOR FIELD}.{index REPLACE UNDEFINED FOR FIELD Error}  Note that {lisp ACCESSFNS} do not have a {lisp CREATE} definition.  However, the user may supply one in the defaults and/or subdeclarations of the declaration, as described below.  Attempting to {lisp CREATE} an {lisp ACCESSFNS} record without specifying a create definition will cause an error {lisp CREATE NOT DEFINED FOR THIS RECORD}.{index CREATE NOT DEFINED FOR THIS RECORD Error}

{arg ACCESSDEF} and {arg SETDEF} can also be a property list which specify {lisp FAST}, {lisp STANDARD} and {lisp UNDOABLE} versions of the {lisp ACCESSFNS} forms, e.g.

{lispcode
[ACCESSFNS LITATOM ((DEF (STANDARD GETD FAST FGETD)
                         (STANDARD PUTD UNDOABLE /PUTD]}

means if {lisp FAST} declaration is in effect, use {lisp FGETD} for fetching, if {lisp UNDOABLE}, use {lisp /PUTD} for saving.


{note ACCESSFNS can also be ACCESSFN (ACCESSFN defined as USERRECORDTYPE)}
}}



The {lisp ACCESSFNS} facility allows the use of data-structures not specified by one of the built-in record types.  For example, one possible representation of a data-structure is to store the fields in {it parallel} arrays, especially if the number of instances required is known, and they do not need to be garbage collected.  Thus, to implement a data structure called {lisp LINK} with two fields {lisp FROM} and {lisp TO}, one would have two arrays {lisp FROMARRAY} and {lisp TOARRAY}.  The representation of an "instance" of the record would be an integer which is used to index into the arrays. This can be accomplished with the declaration:

{lispcode
[ACCESSFNS LINK
   ((FROM (ELT FROMARRAY DATUM)
          (SETA FROMARRAY DATUM NEWVALUE))
    (TO (ELT TOARRAY DATUM)
        (SETA TOARRAY DATUM NEWVALUE)))
   (CREATE (PROG1 (SETQ LINKCNT (ADD1 LINKCNT))
                  (SETA FROMARRAY LINKCNT FROM)
                  (SETA TOARRAY LINKCNT TO)))
   (INIT (PROGN (SETQ FROMARRAY (ARRAY 100))
                (SETQ FROMARRAY (ARRAY 100))]}

To {lisp CREATE} a new {lisp LINK}, a counter is incremented and the new elements stored (although the {lisp CREATE} form given the declaration should actually include a test for overflow).






{arg RECORD-TAIL} is optional.  It may contain expressions of the form:


{Begin LabeledList RECORD-TAIL may contain expressions of the form}

{Label {lisp {arg FIELD-NAME} ← {arg FORM}}{index ← (in record declarations)}}
{Item
Allows the user to specify within the record declaration the default value
to be stored in {arg FIELD-NAME} by a {lisp CREATE}{index CREATE (Record Operator)} (if no value is given within the {lisp CREATE} expression itself).
Note that {arg FORM} is evaluated at {lisp CREATE} time, not when the declaration is made.
}

{Label {lisp (CREATE {arg FORM})}}
{Item
Defines the manner in which {lisp CREATE}{index CREATE (Record Operator)} of this record should be performed.  This provides a way of specifying how
{lisp ACCESSFNS}{index ACCESSFNS (Record Type)} should be created or overriding the usual definition of {lisp CREATE}.  If {arg FORM} contains the field-names of the declaration as variables, the forms given in the {lisp CREATE} operation will be substituted in.  If the word {lisp DATUM}{index DATUM Var} appears in the create form, the {it original} {lisp CREATE} definition is inserted.  This effectively allows the user to "advise" the create.

Note:  {lisp (CREATE {arg FORM})} may also be specified as "{lisp {arg RECORD-NAME} ← {arg FORM}}", e.g. {lisp C ← (CONS A D)}.{index ← (in record declarations)}
}

{Label {lisp (INIT {arg FORM})}{index INIT (in record declarations)}}
{Item
Specifies that {arg FORM} should be evaluated when the record is declared.  {arg FORM} will also be dumped by the {filecom INITRECORDS} file package command ({PageRef FileCom INITRECORDS}).

For example, see the example of an {lisp ACCESSFNS} record declaration above.  In this example, {lisp FROMARRAY} and {lisp TOARRAY} are initialized with an {lisp INIT} form.
} 


{Label {lisp (TYPE? {arg FORM})}{index TYPE? (in record declarations)}{Tag RecordDeclarationTYPE?}}
{Item
Defines the manner in which {lisp TYPE?} expressions are to be translated.  {arg FORM} may either be an expression in terms of {lisp DATUM}{index DATUM var} or a function of one argument.

Note:  {lisp (TYPE? {arg FORM})} may also be specified as "{lisp {arg RECORD-NAME} @ {arg FORM}}", e.g. {lisp C @ LISTP}.
}


{Label {lisp (SUBRECORD {arg NAME} . {arg DEFAULTS})}}
{Item
{arg NAME} must be a field that appears in the current declaration and the name of another record.  This says that, for the purposes of translating {lisp CREATE} expressions, substitute the top-level declaration of {arg NAME} for the {lisp SUBRECORD} form, adding on any defaults specified.

For example:  Given {lisp (RECORD B (E F G))}, {lisp (RECORD A (B C D) (SUBRECORD B))} would be treated like {lisp (RECORD A (B C D) (RECORD B (E F G)))} for the purposes of translating {lisp CREATE} expressions.
}


{Label a subdeclaration (i.e., a record declaration.){index subdeclarations (Record Package)}{Tag RecordSubDeclarations}}
{Item
The {arg RECORD-NAME} of a subdeclaration must be either the {arg RECORD-NAME} of its immediately superior declaration or one of the superior's field-names.  Instead of identifying the declaration as with top level declarations, the record-name of a subdeclaration identifies the parent field or record that is being described by the subdeclaration.  Subdeclarations can be nested to an arbitrary depth.

Giving a subdeclaration {lisp (RECORD {arg NAME{sub 1}} {arg NAME{sub 2}})} is a simple way of defining a {it synonym} for the field {arg NAME{sub 1}}.

Note that, in a few cases, it makes sense for a given field to have more than one subdeclaration. For example, in

{lispcode
(RECORD (A . B) (PROPRECORD B (FOO FIE FUM)) (HASHLINK B C))}

{lisp B} is elaborated by both a {lisp PROPRECORD} and a {lisp HASHLINK}.{note what does this mean????}  Similarly,

{lispcode
(RECORD (A B) (RECORD A (C D)) (RECORD A (FOO FIE)))}

is also acceptable, and essentially "overlays" {lisp (FOO FIE)} and {lisp (C D)}, i.e. {lisp (fetch FOO of X)} and {lisp (fetch C of X)} would be equivalent.  In such cases, the {it first} subdeclaration is the one used by {lisp CREATE}.
}


{End LabeledList RECORD-TAIL may contain expressions of the form}


{index *END* record declarations}




{Begin Note}   ?????

Date: 27 JUN 1977 1441-PDT
From: MASINTER

add footnote to record package documentation about using WITH as a field name.

Date: 24 FEB 1979 1734-PST
From: MASINTER

Record declarations can contain a form (DECL ...) in their tail which will be ignored.
{End Note}

}{End SubSec Record Declarations}




{Begin SubSec Defining New Record Types}
{Title Defining New Record Types}
{Text

In addition to the built-in record types, users can declare their own record types by performing the following steps:


(1)  Add the new record-type to the value of {var CLISPRECORDTYPES};{index CLISPRECORDTYPES Var}.


(2)  Perform {lisp (MOVD 'RECORD {arg RECORD-TYPE})}, i.e. give the record-type the same definition as that of the function {lisp RECORD};


(3)  Put the name of a function which will return the translation on the property list of {arg RECORD-TYPE}, as the value of the property {prop USERRECORDTYPE}{index USERRECORDTYPE Prop}.  Whenever a record declaration of type {arg RECORD-TYPE} is encountered, this function will be passed the record declaration as its argument, and should return a {it new} record declaration which the record package will then use in its place.


}{End SubSec Defining New Record Types}





{Begin SubSec Record Manipulation Functions}
{Title Record Manipulation Functions}
{Text

{index changing record declarations}


The user may edit (or delete) global record declarations with the function:


{FnDef {FnName EDITREC} {FnArgs NAME COM{sub 1} {ellipsis} COM{sub N}}
{Type NLAMBDA NOSPREAD}
{Text
Nospread nlambda function similar to {fn EDITF} or {fn EDITV}.  {fn EDITREC} calls the editor on a copy of all declarations in which {arg NAME} is the record-name or a field name.  On exit, it redeclares those that have changed and undeclares any that have been deleted.  If {arg NAME} is {lisp NIL}, {it all} declarations are edited.


{arg COM{sub 1}} {ellipsis} {arg COM{sub N}} are (optional) edit commands.
}}



When the user redeclares a global record, the translations of all expressions involving that record or any of its fields are automatically deleted from {var CLISPARRAY}, and thus will be recomputed using the new information.  If the user changes a {it local} record declaration, or changes some other CLISP declaration, e.g., {lisp STANDARD} to {lisp FAST}, and wishes the new information to affect record expressions already translated, he must make sure the corresponding translations are removed, usually either by {fn CLISPIFY}ing or applying the {lisp !DW} edit macro.



{FnDef {FnName RECLOOK} {FnArgs RECORDNAME {anonarg} {anonarg} {anonarg} {anonarg}}
{Text
Returns the entire declaration for the record named {arg RECORDNAME}; {lisp NIL} if no record declaration with name {arg RECORDNAME}.  Note that the record package maintains internal state about current record declarations, so performing destructive operations (e.g. {fn NCONC}) on the value of {fn RECLOOK} may leave the record package in an inconsistent state.  To change a record declaration, use {fn EDITREC}.
}}



{FnDef {FnName FIELDLOOK} {FnArgs FIELDNAME}
{Text
Returns the list of declarations in which {arg FIELDNAME} is the name of a field.
}}



{FnDef {FnName RECORDFIELDNAMES} {FnArgs RECORDNAME}
{Text
Returns the list of fields declared in record {arg RECORDNAME}.  {arg RECORDNAME} may either be a name or an entire declaration.
}}



{FnDef {FnName RECORDACCESS} {FnArgs FIELD DATUM DEC TYPE NEWVALUE}
{Text
{arg TYPE} is one of {lisp FETCH}, {lisp REPLACE}, {lisp FFETCH}, {lisp FREPLACE}, {lisp /REPLACE} or their lowercase equivalents.  {arg TYPE}={lisp NIL} means {lisp FETCH}.  If {arg TYPE} corresponds to a fetch operation, i.e. is {lisp FETCH}, or {lisp FFETCH}, {fn RECORDACCESS} performs {lisp ({arg TYPE} {arg FIELD} OF {arg DATUM})}.  If {arg TYPE} corresponds to a replace, {fn RECORDACCESS} performs {lisp ({arg TYPE} {arg FIELD} OF {arg DATUM} WITH {arg NEWVALUE})}.  {arg DEC} is an optional declaration; if given, {arg FIELD} is interpreted as a field name of that declaration.

Note that {fn RECORDACCESS} is relatively inefficient, although it is better than constructing the equivalent form and performing an {fn EVAL}.
}}

{FnDef {FnName RECORDACCESSFORM} {FnArgs FIELD DATUM TYPE NEWVALUE}
{Text

Returns the form that would be compiled as a result of a record access.  {arg TYPE} is one of {lisp FETCH}, {lisp REPLACE}, {lisp FFETCH}, {lisp FREPLACE}, {lisp /REPLACE} or their lowercase equivalents.  {arg TYPE}={lisp NIL} means {lisp FETCH}.  
}}




}{End SubSec Record Manipulation Functions}




{Begin SubSec Changetran}
{Title Changetran}
{Text

{note Changetran was designed by R. M. Kaplan.}

{index *BEGIN* Changetran}

A very common programming construction consists of assigning a new value to some datum that is a function of the current value of that datum.
Some examples of such read-modify-write sequences include:


{Begin Table Some examples}

{column}  {column}

{First {lisp (SETQ X (IPLUS X 1))}}	{Next Incrementing a counter}

{First {lisp (SETQ X (CONS Y X))}}	{Next Pushing an item on the front of a list}

{First {lisp (PROG1 (CAR X) (SETQ X (CDR X)))}}
						{Next Popping an item off a list}

{End Table Some examples}



It is easier to express such computations when the datum in question is a simple variable as above than when it is an element of some larger data structure.  For example, if the datum to be modified was {lisp (CAR X)}, the above examples would be:

{lispcode (CAR (RPLACA X (IPLUS (CAR X) 1)))}

{lispcode (CAR (RPLACA X (CONS Y (CAR X)))}

{lispcode (PROG1 (CAAR X) (RPLACA X (CDAR X)))}

and if the datum was an element in an array, {lisp (ELT A N)}, the examples would be:

{lispcode (SETA A N (IPLUS (ELT A N) 1)))}

{lispcode (SETA A N (CONS Y (ELT A N))))}

{lispcode (PROG1 (CAR (ELT A N)) (SETA A N (CDR (ELT A N))))}


The difficulty in expressing (and reading) modification idioms is in part due to the well-known assymmetry of setting versus accessing operations on structures:  {fn RPLACA} is used to smash what {fn CAR} would return, {fn SETA} corresponds to {fn ELT}, and so on.

The "Changetran" facility is designed to provide a more satisfactory notation in which to express certain common (but user-extensible) structure modification operations.{index structure modification (in Changetran) Term}  Changetran defines a set of CLISP words that encode the kind of modification that is to take place, e.g. pushing on a list, adding to a number, etc.  More important, the expression that indicates the datum whose value is to be modified needs to be stated only once.  Thus, the "change word" {lisp ADD} is used to increase the value of a datum by the sum of a set of numbers.  Its arguments are an expression denoting the datum, and a set of items to be added to its current value.  The datum expression must be a variable or an accessing expression (envolving {lisp fetch}, {lisp CAR}, {lisp LAST}, {fn ELT}, etc) that can be translated to the appropriate setting expression.


{note above needs better defn.  at least, give more complete list of accessing functions that can be in DATUM form.}


For example, {lisp (ADD (CADDR X) (FOO))} is equivalent to:

{lispcode
(CAR (RPLACA (CDDR X)
             (PLUS (FOO) (CADDR X)))}

If the datum expression is a complicated form involving subsidiary function calls, such as {lisp (ELT (FOO X) (FIE Y)))}, Changetran goes to some lengths to make sure that those subsidiary functions are evaluated only once (it binds local variables to save the results), even though they logically appear in both the setting and accessing parts of the translation.  Thus, in thinking about both efficiency and possible side effects, the user can rely on the fact that the forms will be evaluated only as often as they appear in the expression. 


For {lisp ADD} and all other changewords, the lower-case version ({lisp add}, etc.) may also be specified.  Like other CLISP words, change words are translated using all CLISP declarations in effect (see {PageRef Tag CLISPdeclarations}).


The following is a list of those change words recognized by Changetran.  Except for {lisp POP}, the value of all built-in changeword forms is defined to be the new value of the datum.


{Def {Type (Change Word)}
{Name ADD} {Args DATUM ITEM{sub 1} ITEM{sub 2} {ellipsis}}
{Text
Adds the specified items to the current value of the datum, stores the result back in the datum location.  The translation will use {fn IPLUS}, {fn PLUS}, or {fn FPLUS} according to the CLISP declarations in effect.
}}


{Def {Type (Change Word)}
{Name PUSH} {Args DATUM ITEM{sub 1} ITEM{sub 2} {ellipsis}}
{Text
{fn CONS}es the items onto the front of the current value of the datum, and stores the result back in the datum location.  For example, {lisp (PUSH X A B)} would translate as {lisp (SETQ X (CONS A (CONS B X)))}.
}}


{Def {Type (Change Word)}
{Name PUSHNEW} {Args DATUM ITEM}
{Text
Like {lisp PUSH} (with only one item) except that the item is not added if it is already {fn FMEMB} of the datum's value.

Note that, whereas {lisp (CAR (PUSH X 'FOO))} will always be {lisp FOO},
{lisp (CAR (PUSHNEW X 'FOO))} might be something else if {lisp FOO} already existed in the middle of the list.
}}


{Def {Type (Change Word)}
{Name PUSHLIST} {Args DATUM ITEM{sub 1} ITEM{sub 2} {ellipsis}}
{Text
Similar to {lisp PUSH}, except that the items are {fn APPEND}ed in front of the current value of the datum.  For example, {lisp (PUSHLIST X A B)} would translate as {lisp (SETQ X (APPEND A B X))}.
}}


{Def {Type (Change Word)}
{Name POP} {Args DATUM}
{Text
Returns {fn CAR} of the current value of the datum after storing its {fn CDR} into the datum.  The current value is computed only once even though it is referenced twice.  Note that this is the only built-in changeword for which the value of the form is not the new value of the datum.
}}


{Def {Type (Change Word)}
{Name SWAP} {Args DATUM{sub 1} DATUM{sub 2}}
{Text
Sets {arg DATUM{sub 1}} to {arg DATUM{sub 2}} and vice versa.

{note returns??}
}}


{Def {Type (Change Word)}
{Name CHANGE} {Args DATUM FORM}
{Text
This is the most flexible of all change words, since it enables the user to provide an arbitrary form describing what the new value should be, but it still highlights the fact that structure modification is to occur, and still enables the datum expression to appear only once.  {lisp CHANGE} sets {arg DATUM} to the value of {arg FORM*}, where {arg FORM*} is constructed from {arg FORM} by substituting the datum expression for every occurrence of the litatom {lisp DATUM}.{index DATUM (in Changetran)}  For example, {lisp (CHANGE (CAR X) (ITIMES DATUM 5))} translates as {lisp (CAR (RPLACA X (ITIMES (CAR X) 5)))}.

{lisp CHANGE} is useful for expressing modifications that are not built-in and are not sufficiently common to justify defining a user-changeword.  As for other changeword expressions, the user need not repeat the datum-expression and need not worry about multiple evaluation of the accessing form. 
}}




It is possible for the user to define new change words.  To define a change word, say {lisp sub}, that subtracts items from the current value of the datum, the user must put the property {prop CLISPWORD},{index CLISPWORD Prop} value {lisp (CHANGETRAN . sub)} on both the upper and lower-case versions of {lisp sub}:

{lispcode
(PUTPROP 'SUB 'CLISPWORD '(CHANGETRAN . sub))
(PUTPROP 'sub 'CLISPWORD '(CHANGETRAN . sub))}

Then, the user must put (on the {it lower}-case version of {lisp sub} only) the property {prop CHANGEWORD},{index CHANGEWORD Prop} with value {arg FN}.  {arg FN} is a function that will be applied to a single argument, the whole {lisp sub} form, and must return a form that Changetran can translate into an appropriate expression.  This form should be a list structure with the atom {lisp DATUM} used whenever the user wants an accessing expression for the current value of the datum to appear.  The form {lisp (DATUM← {arg FORM})} (note that {lisp DATUM←} is a single atom) should occur once in the expression; this specifies that an appropriate storing expression into the datum should occur at that point.  For example, {lisp sub} could be defined with:

{lispcode
(PUTPROP 'sub 'CHANGEWORD
         '(LAMBDA (FORM)
            (LIST 'DATUM←
                  (LIST 'IDIFFERENCE
                        'DATUM
                        (CONS 'IPLUS (CDDR FORM))))))}

If the expression {lisp (sub (CAR X) A B)} were encountered, the arguments to {lisp SUB} would first be dwimified, and then the {prop CHANGEWORD} function would be passed the list {lisp (sub (CAR X) A B)}, and return {lisp (DATUM← (IDIFFERENCE DATUM (IPLUS A B)))}, which Changetran would convert to {lisp (CAR (RPLACA X (IDIFFERENCE (CAR X) (IPLUS A B))))}.

Note:  The {lisp sub} changeword as defined above will always use {fn IDIFFERENCE} and {fn IPLUS}; {lisp add} uses the correct addition operation depending on the current CLISP declarations.



{index *END* Changetran}

}{End SubSec Changetran}



{index *END* record package}



{Begin Note}
Date: 24 FEB 1979 1734-PST
From: MASINTER

I declared RECORDWORDS.
{End Note}




{Begin Note}
Date:  5 NOV 1974 2339-PST
From: MASINTER
Subject: RECORD USE
To:   KAPLAN

If RECORDUSE is both set and defined, then whenever the record package makes a translation, it will call (RECORDUSE functionname recordname fieldnames).  Functionname is the name of the function containing the reference; either recordname or fieldnames are non-nil. On CREATE's and TYPE?'s, its recordname; on FETCH's and REPLACE's it's fieldnames (which is a list of field names).

In addition, whenever a record is redeclared, the record package function RECREDECLARE[redeclarelst,recname] is called (redeclarelst is the list of field names). You can advise this function to, say, unsavedef all the functions which use either that record name or any of those fields.
{End Note}





{Begin SubSec User Defined Data Types}
{Title User Defined Data Types}
{Text

{Tag UserDataTypes}


Note: The most convenient way to define new user data types is via {lisp DATATYPE} record declarations (see {PageRef Tag RecordDeclarationDATATYPE}).

In addition to built-in data-types such as atoms, lists, arrays, etc., Interlisp provides a way of defining completely {it new} classes of objects, with a fixed number of fields determined by the definition of the data type.  Facilities are provided for declaring the name and {it type} of the fields for a given class, creating objects of a given class, accessing and replacing the contents of each of the fields of such an object, as well as interrogating such objects.

In order to define a new class of objects, the user must supply a name for the new data type and specifications for each of its fields.  Each field may contain either a pointer (i.e., any arbitrary Interlisp datum), an integer, a floating point number, or an {arg N}-bit integer.  This is done via the function {fn DECLAREDATATYPE}:


{FnDef {FnName DECLAREDATATYPE} {FnArgs TYPENAME FIELDSPECS}
{Text
{arg TYPENAME} is a literal atom, which specifies the name of the data type.  {arg FIELDSPECS} is a list of "field specifications".  Each field specification may be one of the following:

{note still true?}

{Begin labeledlist DECLAREDATATYPE field specification}

{Name {lisp POINTER}{index POINTER (as a field specification)}}
{Text
Field may contain any Interlisp datum.
}

{Name {lisp FIXP}{index FIXP (as a field specification)}}
{Text 
Field contains an integer.
}

{Name {lisp FLOATP}{index FLOATP (as a field specification)}}
{Text 
Field contains a floating point number.
}

{Name {lisp (BITS {arg N})}{index BITS (as a field specification)}}
{Text 
Field contains a non-negative integer less than 2{super {arg N}}.
}

{End labeledlist DECLAREDATATYPE field specification}


{fn DECLAREDATATYPE} returns a list of "field descriptors", one for each element of {arg FIELDSPECS}.  A field descriptor contains information about where within the datum the field is actually stored.

If {arg TYPENAME} is already declared a datatype, it is re-declared.  If {arg FIELDSPECS} is {lisp NIL}, {arg TYPENAME} is "undeclared".
}}


{FnDef {FnName FETCHFIELD} {FnArgs DESCRIPTOR DATUM}
{Text
Returns the contents of the field described by {arg DESCRIPTOR} from {arg DATUM}.  {arg DESCRIPTOR} must be a "field descriptor" as returned by {fn DECLAREDATATYPE} or {fn GETDESCRIPTORS}.  If {arg DATUM} is not an instance of the datatype of which {arg DESCRIPTOR} is a descriptor, causes error {lisp DATUM OF INCORRECT TYPE}{index DATUM OF INCORRECT TYPE Error}.

In Interlisp-10, if {arg DESCRIPTOR} is quoted, {fn FETCHFIELD} compiles open.  This capability is used by the record package.
}}


{FnDef {FnName REPLACEFIELD} {FnArgs DESCRIPTOR DATUM NEWVALUE}
{Text
Store {arg NEWVALUE} into the field of {arg DATUM} described by {arg DESCRIPTOR}.  {arg DESCRIPTOR} must be a field descriptor as returned by {fn DECLAREDATATYPE}.  If {arg DATUM} is not an instance of the datatype of which {arg DESCRIPTOR} is a descriptor, causes error {lisp DATUM OF INCORRECT TYPE}.  Value is {arg NEWVALUE}.
}}


{FnDef {FnName NCREATE} {FnArgs TYPENAME FROM}
{Text
Creates and returns a new instance of datatype {arg TYPENAME}.

If {arg FROM} is also a datum of datatype {arg TYPENAME}, the fields of the new object are initialized to the values of the corresponding fields in {arg FROM}.

{fn NCREATE} will not work for built-in datatypes, such as {lisp ARRAYP}, {lisp STRINGP}, etc.  If {arg TYPENAME} is not the type name of a previously declared {it user} data type, generates an error, {lisp ILLEGAL DATA TYPE}.{index ILLEGAL DATA TYPE Error}


{note In Interlisp-D, NCREATE will work for Some built-in datatypes - jonl}
}}


{FnDef {FnName GETFIELDSPECS} {FnArgs TYPENAME}
{Text
Returns a list which is {fn EQUAL} to the {arg FIELDSPECS} argument given to {fn DECLAREDATATYPE} for {arg TYPENAME}; if {arg TYPENAME} is not a currently declared data-type, returns {lisp NIL}.
}}


{FnDef {FnName GETDESCRIPTORS} {FnArgs TYPENAME}
{Text
Returns a list of field descriptors, {fn EQUAL} to the {it value} of {fn DECLAREDATATYPE} for {arg TYPENAME}.  If {arg TYPENAME} is not an atom, {lisp (TYPENAME {arg TYPENAME})} is used.
}}


{FnDef {FnName USERDATATYPES} {FnArgs}
{Text
Returns list of names of currently declared user data types.
}}



Note that the user can define how user data types are to be printed via {fn DEFPRINT} ({PageRef Fn DEFPRINT}), how they are to be evaluated by the interpreter via {fn DEFEVAL} ({PageRef Fn DEFEVAL}), and how they are to be compiled by the compiler via {var COMPILETYPELST} ({PageRef Var COMPILETYPELST}).



{note In Interlisp-10, user datatypes are allocated starting with type number 31.  The minimum amount of space that can be assigned to any one data-type is one page, thus effectively limiting the number of possible data types.  The fields are rearranged internally so that pointer fields come first; the remaining numeric fields, if any, are packed so as to optimize the space used.}



The {lisp DATATYPE} facility in Interlisp-D is an extension of that found in Interlisp-10.  Interlisp-D also accepts {lisp BYTE}, {lisp WORD}, and {lisp SIGNEDWORD} as datatype field descriptors equivalent to {lisp BITS 8}, {lisp BITS 16}, and {lisp BETWEEN} -2{super 15} and 2{super 15}-1 respectively.  Interlisp-D will not move fields around in a user declaration if they pack into words and pointers as specified.  {lisp POINTER} fields take 24 bits and must be 32-bit right-justified.


{note from the mail archives: (Larry, 9/9/82)
Interlisp-D does not currently support datatypes larger than 256 words (128 pointers).  Bigger data structures must be allocated out of array space.  There is a bug in the current release so that NO ERROR MESSAGE is generated when declaring such a beast --- rather the system happily creates one, which eventually clobbers other pages in the working environment.
(will be changed, but limit still exists --BVM)

still true?  documentP?
(currently max size is 512 bytes --lmm)
}

{Note
Interlisp-10 : limited # of datatypes to 64
Interlisp-D :  limit currently 256, extension to 2↑15 planned   ---lmm
}


{note in Interlisp-Vax, minimum amount of storage per datatype is 64K bytes.  Nax number of types is about 3600}


}{End SubSec User Defined Data Types}





}{End Chapter The Record Package}