MAINSAIL Language Manual, Chapter 10

previous   next   top   complete contents   complete index   framed top   this page unframed


10. Records

A record is a data structure of which the components (called fields) may be of differing data types and are accessed by field names. The names and data types of the fields in a record are normally described by a CLASS (see Chapter 9), and a POINTER, ADDRESS, or inplace record variable of the appropriate CLASS is used to access the record's fields.

For example, a record with three fields, a STRING named str, an INTEGER named base, and another INTEGER named val, may be imagined as three adjacent boxes, the first holding the value of the field str, the second holding the value of base, and the third holding the value of val. If str = "hello", base = 53, and val = 28, then the record may be pictured as:

Figure 10–1. A Record with Three Fields
+-----------------+
|     "hello"     | STR
+-----------------+
|       53        | BASE
+-----------------+
|       28        | VAL
+-----------------+

Records are allocated and accessed in two different ways, each using its own syntax:

  1. An inplace record is allocated automatically when the object or stack frame that contains it is allocated. An inplace record's fields are accessed using a record variable.

  2. A dynamic record must be allocated explicitly with a call to the system PROCEDURE new. A dynamic record's fields are accessed using a POINTER variable.

Figure 10–2 shows how a POINTER variable p pointing at a dynamic record is illustrated in diagrams; Figure 10–3 shows an inplace record variable r containing the same fields is illustrated.

Figure 10–2. A POINTER p to a Dynamic Record
            +-----------------+
p --------->|     "hello"     | STR
            +-----------------+
            |       53        | BASE
            +-----------------+
            |       28        | VAL
            +-----------------+

Figure 10–3. An Inplace Record r
  +-----------------+
r |     "hello"     | STR
  +-----------------+
  |       53        | BASE
  +-----------------+
  |       28        | VAL
  +-----------------+

Dynamic and inplace records are similar in many ways; they differ mainly in the ways in which they are created and destroyed. In MAINSAIL documentation, the term “record” can mean either an inplace record or a dynamic record, unless the context makes it clear that only one kind of record is intended.

A field of a record is accessed by means of a field variable (as described in Section 9.5), which is a POINTER, ADDRESS, or inplace record followed by a period and the field name. If p points to a the dynamic record described in Figure 10–2, then p.str, p.base, and p.val have the values:

p.str = "hello"
p.base = 53
p.val = 28

10.1. Inplace Records

An inplace record is allocated automatically as a local variable or as a component of some other object; this is the same way in which values of simple data types, such as INTEGER, are allocated. It is not possible to call the system PROCEDURE new to allocate an inplace record.

In contrast with a dynamic record, an inplace record is not accessed through a POINTER, but through an inplace record variable. An inplace record variable's type is declared with the keyword $RECORD followed by the name of the record's CLASS in parentheses, as in:

$RECORD(myClassmyRec;

Assignment of one inplace record to another copies each field of the record from the source to the destination.

Two simple record variables (that are not $REFERENCE parameters; see Section 7.5.4) cannot be aliases for each other; each has its own copy of each field of the record, and a change to a field of one of the records is not reflected in a field of another simple inplace record variable. This is also true of simple variables of types like INTEGER; a change to a simple INTEGER variable i does not change the value of another simple INTEGER variable j. Inplace record variables are said to have “value semantics” because their values are independent of one another; this contrasts with the “pointer semantics” of POINTERs used to access dynamic records.

An inplace record is never disposed explicitly, and never garbage collected; it goes away automatically when the object of which it is a component is reclaimed or when the PROCEDURE in which it is a local variable returns.

10.2. Dynamic Records

A dynamic record is never allocated automatically; it is allocated by a call to a system PROCEDURE that allocates records. The system PROCEDURE that allocates records is usually the system PROCEDURE new, although $newRecords and $createRecord also allocate records. Each of these system PROCEDUREs requires a CLASS, CLASS descriptor, or POINTER to another record of the CLASS to be allocated as an argument, and returns a POINTER to the newly allocated record(s) of the specified CLASS.

In contrast with an inplace record, a dynamic record is not accessed through an inplace record variable, but through a POINTER. The CLASS (or a prefix of the CLASS) that a POINTER is allowed to point to is usually included in the POINTER declaration, as in:

POINTER(myClassmyPtr;

Assignment of one POINTER to another makes the destination POINTER point to the same object as the source POINTER.

Two POINTER variables may be aliases for the same dynamic record; that is, they may both point to the same record, and a change to any of the record's fields using one POINTER is reflected when the same field is accessed through the other POINTER. POINTER variables are said to have “pointer semantics”, in contrast to the “value semantics” of inplace record variables.

A dynamic record may be reclaimed by passing a POINTER to it to the system PROCEDURE dispose, or it may be reclaimed automatically by the garbage collector if it becomes inaccessible.

10.3. Inplace Record Declaration

An inplace record r is declared as follows, where c is a CLASS:

$RECORD(cr;

The declaration of c must have already been completed; c cannot be a forward (not yet declared) CLASS or a CLASS in the process of being declared. An exception is made if r is a $REFERENCE parameter (see Section 7.5.4), or is a parameter of a FORWARD PROCEDURE p or a PROCEDURE p being declared as a field (of a CLASS or MODULE); in the latter case, c must have been declared by the time the body for p is declared.

It is an error if c is a CLASS that contains PROCEDURE fields.

An inplace record of size 0 (i.e., of a CLASS with no data fields) is not allowed as a local or OWN variable, as a non-$REFERENCE parameter, or as an ARRAY element or PROCEDURE type.

Any variable, parameter, or field can be of an inplace record type. Any ARRAY may have inplace record elements. Any PROCEDURE may return an inplace record type.

10.4. Declaration of POINTERs to Dynamic Records

A dynamic record is not declared directly; a POINTER may be declared to point to a record CLASS, however.

A POINTER p to records of a CLASS c is declared as follows:

POINTER(cp;

The declaration of c need not have been completed; c can be a forward (not yet declared) CLASS or a CLASS in the process of being declared. However, c must be declared before p is used to access any fields of c.

The prohibition against the use of a CLASS with no data fields that applies to inplace records does not apply to POINTERs or dynamic records; any POINTER may be declared to point to a CLASS with no data fields, and it is legal to allocate a dynamic record of size 0.

Any variable, parameter, or field can be of a POINTER type. Any ARRAY may have POINTER elements. Any PROCEDURE may return a POINTER type.

10.5. Inplace Record Allocation

Inplace record allocation occurs implicitly. If the inplace record is a PROCEDURE local variable (a PROCEDURE parameter is considered a local variable), it is allocated automatically on entry to the PROCEDURE. Otherwise, the inplace record must be a component of some other object; it is allocated when the enclosing object is allocated.

The contents of an inplace record are cleared (all fields are set to Zero) when an inplace record is allocated, even when the inplace record is a local variable.

As an example, consider the following code:

CLASS c (INTEGER iSTRING s);
CLASS d (LONG REAL rr$RECORD(cr);

PROCEDURE proc ($RECORD(cr1);
BEGIN
$RECORD(cr2;
POINTER(dp;

p := new(d);
END;

r1 and r2 are allocated and cleared on entry to proc; i.e., at the start of proc, r1.i and r1.i are zero, and r1.s and r2.s are the empty STRING.

The inplace record field r is a component of the dynamic record of CLASS d to which p is made to point by p := new(d). When the dynamic record is allocated, its field r is allocated and cleared.

10.6. Dynamic Record Allocation

A dynamic record is most commonly allocated by calling the system PROCEDURE new with the CLASS of the record to be allocated as an argument; see Section 42.1. A program may call new any number of times, allocating as many dynamic records as it needs. The dynamic records are usually made accessible by setting POINTER components of other objects to point to the records, so that the program can keep track of the dynamic records it allocates.

The PROCEDUREs $createRecord (Section 32.72) and $newRecords (Section 42.6) can also be used to allocate records in the appropriate circumstances, but they are generally not as useful as new.

Newly allocated dynamic records are cleared (all fields are set to Zero).

As an example, consider the following code:

CLASS c (INTEGER iSTRING s);
CLASS(cd (REAL r);

PROCEDURE proc (POINTER(cp1);
BEGIN
POINTER(cp2,p3,p4;

p2 := new(c);
p3 := new(d);
p4 := p1;
END;

On entry to proc, p1 has been initialized from its argument; if the program is correct, then p1 is either NULLPOINTER (pointing at no object) or pointing to an object of CLASS c or of a CLASS derived from c. p2, p3, and p4 are initialized to NULLPOINTER on entry.

The statement p2 := new(c) allocates a dynamic c record, clears it (so that p2.i is zero and p2.s the empty STRING), and sets p2 to point to it.

The statement p3 := new(d) allocates a dynamic d record, clears it (so that p3.i and p3.r are zero and p3.s the empty STRING), and sets p3 to point to it. It is allowed that p3 be declared to point to a c object but actually point to a d object; since d is derived from c, every d object is a c object.

The statement p4 := p1 makes p4 point at the same record as p1 (or sets p4 to NULLPOINTER if p1 is NULLPOINTER). p1 and p4 can then be considered aliases for the same object.

10.7. Inplace Record Operations

Operations on inplace records include field access, assignment, and parameter passing.

10.7.1. Field Access

A field f of a record r is accessed as r.f; see Section 9.5.

As a more complex example, given the declarations:

CLASS c (POINTER(dp);
CLASS d ($RECORD(cr);
$RECORD(crc;

the expression rc.p.r.p would be valid and would refer to the POINTER field p of the inplace record field r of the object pointed to by rc's POINTER field p.

10.7.1.1. Fields of Record Expressions
A field of an inplace record is considered to be a variable only if the record itself is a variable, and not the result of an expression. For example, if rec1 and rec2 are inplace records, (IF bo THEN rec1 EL rec2).f is not considered to be a variable, and thus cannot appear on the left-hand side of an assignment or be passed as a MODIFIES or PRODUCES argument. A field of a dynamic record (accessed by means of a POINTER) is always considered to be a variable, regardless of whether or not the POINTER that specifies the record is a variable. Thus, if rec1 and rec2 were POINTERs in the expression above, then the expression would be able to appear on the left-hand side of an assignment.

10.7.2. Assignment

Inplace record assignment copies record fields from one record to another.

Records can be assigned (with the assignment operator :=) if they are assignment compatible. Two records are assignment compatible if and only if they are of the same CLASS and that CLASS is not unsized (see Section 12.14). For example, let r be a $RECORD(c). r can be assigned or passed to a $RECORD(c); this copies all fields of c.

The assignment can be part of an assignment expression:

$RECORD(cx,y,z;
z := y := x;

10.7.2.1. Explicit Classification and Inplace Records
To assign or pass r to a $RECORD(d), where d is a prefix of c, use r:d, which copies only the fields of r that belong to d. It is a compiletime error if d is not a prefix of c.

For example, if r1 is a record of d and r2 is a record of c, and d is a prefix of c, then r1 := r2:d assigns the values of r2's d fields to the corresponding fields of r2, and ignores the fields in r2 that are from c but not from d.

10.7.3. Inplace Records as PROCEDURE Parameters

As with other types of PROCEDURE arguments and parameters, a USES inplace record argument effectively assigns its value to the corresponding parameter, a PRODUCES inplace record parameter effectively assigns its value (at the time the PROCEDURE returns) to the corresponding argument, and a MODIFIES argument/parameter pair assigns in both directions. Assignment of inplace records means copying fields, as described above.

An omitted OPTIONAL USES or MODIFIES $RECORD(c) parameter is passed a $RECORD(c) with all fields cleared. An omitted OPTIONAL MODIFIES or PRODUCES record is discarded upon return. An explicit default value (e.g., using OPTIONAL(v)) is not allowed for record parameters.

The parameter qualifier $REFERENCE is allowed for inplace records, although it is not allowed for simple data types. A $REFERENCE parameter is effectively an alias for its argument; see Section 7.5.4.

10.7.4. Miscellaneous Operations

An IF expression can have a record result:

CLASS c (INTEGER i,j);
$RECORD(cx,y,z;
    . . .
write(logFile,(IF procTrue THEN z EL x).i,eol,
    (
IF procFalse THEN y EL z).j,eol);

clear(r), where r is an inplace record, clears all fields of r.

$expr(inplace record expression) and compiletime calls to PROCEDUREs with record results are not currently supported.

INIT is not currently allowed for an ARRAY of inplace records.

10.7.4.1. Inplace Records and the Foreign Language Interface

Currently, inplace records can be passed to or from a foreign language only as $REFERENCE parameters (see Section 7.5.4).

10.8. Dynamic Record Operations

10.8.1. Field Access

A field f of a dynamic record referenced by a POINTER p is accessed as p.f; see Section 9.5.

As a more complex example, given the declarations:

CLASS c (POINTER(dp);
CLASS d ($RECORD(cr);
POINTER(cpc;

the expression pc.p.r.p would be valid and would refer to the POINTER field p of the inplace record field r of the object pointed to by the POINTER field p of the object pointed to by pc.

10.8.1.1. Explicit Classification and Dynamic Record Fields
A POINTER p can be temporarily treated as a POINTER to a CLASS c different from the CLASS (if any) to which p was declared to point, using the syntax p:c. Such an explicitly classified POINTER can be used to access a field f of c (assuming that CLASS of the object p points to really is c or a CLASS derived from c) with the syntax p:c.f.

Section 9.6 describes explicit classification in detail.

10.8.2. Assignment

POINTER assignment does not copy record fields; it just makes the destination POINTER point to the same object as the source POINTER. The declared CLASSes of the POINTERs must be assignment compatible, as described in Section 4.9.

After a (non-NULLPOINTER) POINTER assignment, the two POINTERs in question are effectively aliases of the same object. For example, given:

p.i := 3; q := pq.i .+ 1;

p.i is 4.

A POINTER assignment can be part of an assignment expression:

POINTER(cx,y,z;
z := y := x;

10.8.2.1. Copying Fields from One Dynamic Record to Another
POINTER assignment does not copy record fields. To get an effect with dynamic records similar to that of assigning inplace records to one another, use $ref (see Section 7.5.4.5):

POINTER(cp,q;
Assume q is not NULLPOINTER:
p := q;                 # p and q point at the same object
                        # 
now
p := new(c);
$ref(p) := $ref(q);     # p and q point at different
                        # 
objects nowbut p's c fields
                        # 
are a copy of q's

10.8.3. Dynamic Records as PROCEDURE Parameters

As with POINTER assignment, a POINTER passed as a PROCEDURE parameter copies only the POINTER value, and does not copy record fields.

10.8.4. Miscellaneous Operations

clear(p), where p is a POINTER, clears all fields of the dynamic object to which p points.

10.9. Deallocating Inplace Records

Inplace records are deallocated automatically when the PROCEDURE frame or object that contains them goes away. Inplace records cannot become garbage.

10.10. Deallocating Dynamic Records

The system PROCEDURE dispose takes a POINTER to a dynamic object and immediately reclaims the memory that object occupies. No check is made that there are no other POINTERs currently pointing to the object. If there are such POINTERs, they become dangling; using them to access data has undefined effects.

The use of dispose is therefore inherently unsafe, although judicious use of dispose can improve performance in many programs by reducing garbage collection time. A facility called POINTER rigging (see Chapter 6 of the MAINDEBUG User's Guide) can sometimes make it easier to track dangling POINTER bugs. SUPERCHECK platforms (see Chapter 18) may also catch dangling POINTER problems.

It is never necessary to dispose records explicitly, since a record that becomes inaccessible (no longer pointed to by any accessible POINTER) is automatically collected by the MAINSAIL garbage collector; the only reason to call dispose is to improve program performance.

10.11. When to Use Inplace Records and When to Use Dynamic Records

Inplace objects are most useful for records of relatively small CLASSes that represent a particular value, where a copy of the value is as valid as the original value. A classic example of inplace record usage would be for complex numbers, e.g.:

CLASS complex (LONG REAL realPart,imaginaryPart);

An inplace $RECORD(complex) represents a single complex value, and assignment and parameter passing assign that value to another variable without accidentally introducing aliases to the same object.

Dynamic records are useful when the number of records in a data structure is not known beforehand, as in a list of arbitrary size, or where all uses of a particular record value must be through POINTERs to the same record, as in MAINSAIL file records (because a duplicate of a file record is not valid). The bulk of the data allocated by most programs consists of dynamic objects, because the ability to link dynamic objects through POINTERs to produce data structures of arbitrary topology allows great flexibility.

10.12. How to Make a PROCEDURE Able to Take Both Inplace Records and POINTERs to Dynamic Records as Arguments

Sometimes it is useful to use the same CLASS to classify both inplace and dynamic records.

If you want to create a PROCEDURE to which you can pass either an inplace record of a CLASS c or a POINTER to c, declare the parameter in question as a $REFERENCE $RECORD(c). Pass an argument declared as POINTER(c) p to this parameter as $ref(p). See Section 7.5.4 for more details on $REFERENCE parameters and $ref.

10.13. The Layout of Fields within a Record

The order of the fields of a MAINSAIL record is subject to change, and code that depends upon this order should be avoided. Generally speaking, it is not possible to write code depending on the layout of a MAINSAIL CLASS without using low-level ADDRESS arithmetic or PROCEDUREs that inquire at runtime about the fields of a CLASS.

CLASSes representing foreign language data structures to be passed through the Foreign Language Interface should be declared with the $ALIGN directive. Such CLASSes are guaranteed to be laid out as the foreign language expects, not as described below.

At present, consecutively declared fields of an unaligned CLASS are stored at increasing memory locations within each record of that CLASS. On all existing 32-bit platforms, each field occupies exactly the number of storage units given by size(typeCode), where typeCode is the INTEGER type code for the field's data type; no padding or packing is done. For example, if a CLASS is declared as:

CLASS xyz (
    
INTEGER i;
    
POINTER(xyzp;
    
LONG INTEGER li;
);

and, on the (32-bit) machine where the record is stored:

size(integerCode) = 2
size(pointerCode) = 4
size(longIntegerCode) = 4

then:

On all existing 64-bit platforms, padding is sometimes inserted between fields, as required by the platform's alignment rule; see Table 9–3. For example, on HPPA64, the above record would be laid out so that:

10.13.1. The Layout of Fields within a Record of a Prefixed CLASS

At present, fields contributed directly by a prefixed CLASS immediately follow those fields contributed by the prefix CLASS in memory. There is no padding (except that required by the platform's alignment rule, as listed in Table 9–3) or extra fields between fields of a prefix CLASS and those of its prefixed CLASS in the prefixed CLASS record.

For example, given the declarations:

CLASS c1 (INTEGER iSTRING s);
CLASS(c1c2 (BITS bREAL r);

a record of c2 contains the fields i, s, b, and r at progressively higher addresses in memory.


previous   next   top   complete contents   complete index   framed top   this page unframed

MAINSAIL Language Manual, Chapter 10