Heading:
Mesa 6.0 Compiler Update
Page Numbers: Yes X: 527 Y: 10.5" First Page: 15
Sequences
A sequence in Mesa is an indexable collection of objects, all of which have the same type. In this respect, a sequence resembles an array; however, you need not specify the length of the sequence when its type is declared, only when an instance of that type is created. Mesa 6 provides sequence-containing types for applications in which the size of a dynamically created array cannot be computed statically. Note, however, that only a subset of a more general design for sequences has been implemented. The contexts in which sequence types may appear are somewhat restricted, as are the available operations on them. We believe that the subset provides enough functionality to accomodate most uses of sequences, but you will encounter a number of annoying and sometimes inconvenient restrictions that you must take note of in your Mesa 6 programming.
One can view a sequence type as a union of some number of array types, just as the variant part of a variant record type can be viewed as a union of some (enumerated) collection of record types. Mesa adopts this view, particularly with respect to the declaration of sequence-containing types, with the following consequences:
A sequence type can be used only to declare a field of a record. At most one such field may appear within a record, and it must occur last.
A sequence-containing object has a tag field that specifies the length of that particular object and thus the set of valid indices for its elements.
To access the elements of a sequence, you use ordinary indexing operations; no discrimination is required. In this sense, all sequences are overlaid, but simple bounds checking is sufficient to validate each access.
Uses of sequence-containing variables must follow a more restrictive discipline than is currently enforced for variant records. The (maximum) length of a sequence is fixed when the object containing that sequence is created, and it cannot subsequently be changed. In addition, Mesa 6 imposes the following restrictions on the uses of sequences:
You cannot embed a sequence-containing record within another data structure. You must allocate such records dynamically and reference them through pointers. (The NEW operation has been extended to make allocation convenient.)
You cannot derive a new type from a sequence-containing type by fixing the (maximum) length; i.e., there is no analog of a discriminated variant record type.
There are no constructors for sequence-valued components of records, nor are such components initialized automatically.
The following sections describe sequences in more detail.
Defining Sequence Types
You may use sequence types only to declare fields of records. A record may have at most one such field, and that field must be declared as the final component of the record:
Syntax
VariantPart ::=. . .
|PackingOption SEQUENCE SeqTag OF TypeSpecification
SeqTag ::=identifier : Access BoundsType
|COMPUTED BoundsType
BoundsType ::=IndexType
TypeSpecification ::=. . .
|TypeIdentifier [ Expression ]
The TypeSpecification in VariantPart establishes the type of the sequence elements. The BoundsType appearing in the SeqTag determines the type of the indices used to select from those elements. It is also the type of a tag value that is associated with each particular sequence object to encode the length of that object. For any such object, all valid indices are smaller than the value of the tag. If T is the BoundsType, the sequence type is effectively a union of array types with the index types
T[FIRST[T] .. FIRST[T]), T[FIRST[T] .. SUCC[FIRST[T]]), ... T[FIRST[T] .. LAST[T])
and a sequence with tag value v has index type T[FIRST[T]..v). Note that the smallest interval in this union is empty.
If you use the first form of SeqTag, the value of the tag is stored with the sequence and is available for subscript checking. In the form using COMPUTED, no such value is stored, and no bounds checking is possible.
Examples:
StackRep: TYPE = RECORD [
top: INTEGER1,
item: SEQUENCE size: [0..LAST[INTEGER]] OF T]
Number: TYPE = RECORD [
sign: {plus, minus},
magnitude: SELECT kind: * FROM
short => [val: [0..1000)],
long => [val: LONG CARDINAL],
extended => [val: SEQUENCE length: CARDINAL OF CARDINAL]
ENDCASE]
WordSeq: TYPE = RECORD [SEQUENCE COMPUTED CARDINAL OF Word]
The final example illustrates the recommended method for imposing an indexable structure on raw storage.
If S is a type containing a sequence field, and n is an expression with a type conforming to CARDINAL, both S and S[n] are TypeSpecifications. They denote different types, however, and the valid uses of those types are different, as described below.
MACHINE DEPENDENT Sequences
You may declare a field with a sequence type within a MACHINE DEPENDENT record. Such a field must come last, both in the declaration and in the layout of the record, and the total length of a record with a zero-component sequence part must be a multiple of the word length. If you explicitly specify bit positions, the size of the sequence field also must describe a zero-length sequence; i.e., it must account for just the space occupied by the tag field (if any).
Examples:
Node: TYPE = MACHINE DEPENDENT RECORD [
info (0: 0..7): CHARACTER,
sons (0: 8..15): SEQUENCE nSons (0: 8..15): [0..256) OF POINTER TO Node]
CharSeq: TYPE = MACHINE DEPENDENT RECORD [
length (0): CARDINAL,
char (1): PACKED SEQUENCE COMPUTED CARDINAL OF CHARACTER]
Allocating Sequences
If S designates a record type with a final component that is a sequence, S[n] is a type specification describing a record with a sequence part containing exactly n elements. The expression n must have a type conforming to CARDINAL. Its value need not be a compile-time constant; however, you can use specifications of this form only to allocate sequence-containing objects (as arguments of NEW) or to inquire about the size of such objects (as arguments of SIZE). In particular, you cannot use S[n] to define or construct a new type or to declare a variable.
The value of the expression SIZE[S[n]] has type CARDINAL and is the number of words required to store an object of type S having n components in its sequence part.
The value of the expression z.NEW[S[n]] has type POINTER TO S (or LONG POINTER TO S, depending upon the type of the zone z). The effect of its evaluation is to allocate SIZE[S[n]] words of storage from the zone z and to initialize that storage as follows:
Any fields in the common part of the record receive their default values.
The sequence tag field receives the value SUCCn[FIRST[T]], where T is the type of that field.
The elements of the sequence part have undefined values.
To supply initial values for the fields in the common part, you may use a constructor for type S in the call of NEW. There are currently no constructors for sequence parts, however, and you must void the corresponding field. In any case, you must explicitly program any required initialization of the elements of the sequence part. In Mesa 6, this is true even if the element type has non-NULL default value.
Examples:
ps: POINTER TO StackRepz.NEW[StackRep[100]];-- s.top = 1
pn: POINTER TO Node ← z.NEW[Node[degree[c]] ← [info: c, sons: NULL]]
pxn: POINTER TO extended Numberz.NEW[extended Number[2*k]]
Note that n specifies the maximum number of elements in the sequence part and must conform to CARDINAL no matter what BoundsType Ti appears in the SeqTag. The value assigned to the tag field is SUCCn[FIRST[Ti]]. A bounds fault occurs if this is not a valid value of type Ti, i.e., if n > cardinality(Ti), and you have requested bounds checking.
If FIRST[Ti] = 0, SUCCn[FIRST[Ti]] is just n, i.e., the interpretation of the tag is most intuitive if Ti is a zero-origin subrange. Usually you will specify a BoundsType (e.g., CARDINAL) with a range that comfortably exceeds the maximum expected sequence length. If, however, some maximum length N is important to you, you should consider using [0..N] as the BoundsType; then the value of the tag field in a sequence of length n (n < N) is just n and the valid indices are in the interval [0..n).
Operations on Sequences
You can use a sequence-containing type S only as the argument of the type constructor POINTER TO. Note that the type of z.NEW[S[n]] is POINTER TO S (not POINTER TO S[n]). If the type of an object is S, the operations defined upon that object are
ordinary access to fields in the common part
readonly access to the tag field (if not COMPUTED)
indexing of the sequence field
constructing a descriptor for the components of the sequence field (if not COMPUTED).
There are no other operations upon either type S or the sequence type embedded within S. In particular, you cannot assign or compare sequences or sequence-containing records (except by explicitly programming operations on the components).
Indexing: You may use indexing to select elements of the sequence-containing field of a record by using ordinary subscript notation, e.g., s.seq[i]. The type of the indexing expression i must conform to the BoundsType appearing in the declaration of the sequence field and must be less than the value of the tag, as described above. The result designates a variable with the type of the sequence elements. A bounds fault occurs if the index is out of range, the sequence is not COMPUTED, and you have requested bounds checking.
By convention, the indexing operation upon sequences extends to records containing sequence-valued fields. Thus you need not supply the field name in the indexing operation. Note too that both indexing and field selection provide automatic dereferencing.
Examples:
ps↑.item[ps.top] ps.item[ps.top] ps[ps.top] -- all equivalent
Descriptors: You may apply the DESCRIPTOR operator to the sequence field of a record; the result is a descriptor for the elements of that field. The resulting value has a descriptor type with index and component types and PACKED attribute equal to the corresponding attributes of the sequence type. By extension, DESCRIPTOR may be applied to a sequence-containing record to obtain a descriptor for the sequence part. The DESCRIPTOR operator does not automatically dereference its argument.
You cannot use the single-argument form of the DESCRIPTOR operator if the sequence is COMPUTED. The multiple-argument form remains available for constructing such descriptor values explicitly (and without type checking).
In any new programming, you should consider the following style recommendation: use sequence-containing types for allocation of arrays with dynamically computed size; use array descriptor types only for parameter passing.
Examples:
DESCRIPTOR[pn↑] DESCRIPTOR[pn.sons] -- equivalent
String Bodies and TEXT
The type StringBody provided by previous versions of Mesa illustrates the intended properties and uses of sequences. For compatibility reasons, it has not been redefined as a sequence; the declarations of the types STRING and StringBody remain as follows:
STRING: TYPE = POINTER TO StringBody;
StringBody: TYPE = MACHINE DEPENDENT RECORD [
length (0): CARDINAL ← 0,
maxlength (1): --READONLY-- CARDINAL,
text (2): PACKED ARRAY [0..0) OF CHARACTER]
The operations upon sequence-containing types have, however, been extended to StringBody so that its operational behavior is similar. In these extensions, the common part of the record consists of the field length, maxlength serves as the tag, and text is the collection of indexable components (packed characters). Thus z.NEW[StringBody[n]] creates a StringBody with maxlength = n and returns a STRING; if s is a STRING, s[i] is an indexing operation upon the text of s, DESCRIPTOR[s↑] creates a DESCRIPTOR FOR PACKED ARRAY OF CHARACTER, etc.
There are two anomalies arising from the actual declaration of StringBody: s.text[i] never uses bounds checking, and DESCRIPTOR[s.text] produces a descriptor for an array of length 0. Use s[i] and DESCRIPTOR[s↑] instead.
Type TEXT
The type TEXT, which describes a structure similar to a StringBody as a true sequence, is predeclared in Mesa 6. Its components length and maxLength are declared to have a type compatible with either signed or unsigned numbers (but with only half the range of INTEGER or CARDINAL).
TEXT: TYPE = MACHINE DEPENDENT RECORD [
length (0): [0..LAST[INTEGER]] ← 0,
text (1): PACKED SEQUENCE maxLength (1): [0..LAST[INTEGER]] OF CHARACTER]
Exported Types
An exported type is a type designated by an identifier that is declared in an interface and subsequently bound to some concrete type supplied by a module exporting that interface. This is analogous to the current treatment of procedures in interfaces, where the implementations of procedures (i.e., the procedure bodies) do not appear in the interface but are defined separately. The advantages are twofold:
The internal structure of the type is guaranteed to be invisible to clients of the interface.
There are no compilation dependencies between the definition of the concrete type and the interface module. The definition of that type can be changed and/or recompiled at any time (perhaps subject to a size constraint; see below) without requiring recompilation of either the interface or any client of the interface.
The uses of an exported type are the same as those of any other type, e.g, to construct other types. The value provided by the interface is constant but has no accessible internal structure. In Mesa 6, there are two other important differences between exported procedures and exported types.
The first is a restriction necessary to ensure type safety across module boundaries. Different exporters of an interface can supply different implementations of any particular procedure in that interface. In Mesa 6, this is not true for exported types; all exporters of a particular type within a configuration must supply the same concrete type, which is called the standard implementation of that exported type. Because of this restriction, clients can safely interassign values with exported type T, no matter how obtained. In addition, any exporter of T may convert a value of type T to a value of the concrete type it uses to represent T and conversely.
The second difference is that it is not necessary to import an interface to access an exported type defined within it or to distinguish among values of such a type coming from different imported instances. This is another consequence of the fact that, in Mesa 6, all interfaces must reference the standard implementation of the exported type.
Interface Modules
An exported type is declared in an interface (DEFINITIONS) module using one of the following two forms:
T: TYPE;
T: TYPE [ Expression ];
The first of these introduces a type T, no properties of which are known in the interface or to any client of the interface. In particular, the size of T is not known; this is adequate (and desirable) if the interface and clients deal only with values of type POINTER TO T.
The second form specifies the size of the values used in the representation of the type. The value of Expression, which must denote a compile-time constant with an unsigned integer value, gives this size in units of machine words. Supplying the size of an exported type is a shorthand for exporting a set of fundamental operations (creation, ← , =, and #) upon that type. In Mesa 6, the eventual concrete type must supply the standard implementations of these operations, which are defined as follows:
creationallocate the specified number of words, with no initialization
copy an uninterpreted bit string
=, #compare uninterpreted bit strings
Note that a type with non-NULL default value does not have the standard creation operation. Such types cannot be exported with known size. You should therefore consider writing your interfaces in terms of POINTER TO T, where T is a completely opaque exported type and not subject to these restrictions.
Client Modules
A client has no knowledge of the type T beyond those properties specified in the interface. If the size is not specified there, no operations on T are permitted. If the size is available from the interface, SIZE[T] is legal; also declaration of variables (including record fields and array components) and the operations ←, =, # are defined for type T.
Implementation Modules
An implementor exports a type T to some interface Defs by declaring the type with the required identifier, the PUBLIC attribute, and a value that is the concrete type; e.g., in
T: PUBLIC TYPE = S;
S specifies the concrete type. If the size of T appears in the interface, the definition of T in the exporter must specify a type with that size and with the standard fundamental operations (the compiler checks this).
Within an exporter, Defs.T and T conform freely and are assignment compatible. Otherwise, Defs.T is treated opaquely there and is not equivalent to T (except for the purpose of checking exports). You should therefore attempt to write an exporting module entirely in terms of concrete types. Consider the following example:
Interface Module (Defs):
T: TYPE;
H: TYPE = POINTER TO T;
R: TYPE = RECORD [f: H, ...];
Proc1: PROC [h: H];
Proc2: PROC [r: POINTER TO R];
...
Exporting Module:
T: PUBLIC TYPE = RECORD [v: ...];
P: TYPE = POINTER TO T;
Proc1: PUBLIC PROC [h: P] = {... h.v ...};
Proc2: PUBLIC PROC [r: POINTER TO Defs.R] = {
q: P = r.f;
...
q.v ...};
...
If the type of h were Defs.H in the implementation of Proc1, the reference to h.v would be illegal. By defining a type such as P and using it within the exporter instead of H, you can avoid most such problems. (Note that Proc1 is still exported with the proper type.) This strategy of creating concrete types in one-to-one correspondence to interface types involving T fails for record types such as R (because of the uniqueness rule for record constructors). In this example, you must use Defs.R to define the type of r in the implementation of Proc2, but a reference to r.f.v is illegal. In such cases, a LOOPHOLE-free implementation may require redundant assignments, such as the one to q. Alternatively, you should consider making the record type another exported type, and defining its concrete type within the exporter also.
Binding
For each interface containing some exported type T, all exporters of that interface must provide equivalent concrete types for T (the binder and loader check this). In Mesa 6, the concrete types must in fact be identical; if two modules export T, they must obtain the same concrete definition of T, e.g., from another shared interface module (typically, a private one).
Control Variables
You can now declare the control variable of a loop as part of the FOR clause attached to that loop. Such an identifier cannot be accessed outside the loop and cannot be updated except by the FOR clause in which it is declared.
Syntax
Iteration::=FOR identifier Direction IN LoopRange
|FOR identifier : TypeExpression Direction IN LoopRange
Assignation::=FOR identifierExpression , Expression
|FOR identifier : TypeExpressionExpression , Expression
The forms of Iteration and Assignation with ": TypeExpression" declare a new control variable. That variable cannot be explicitly updated (except by the FOR clause itself). Its scope is the entire LoopStmt introduced by the Iteration or Assignation including any LoopExitsClause. Note, however, that the value of a control variable used in an Iteration is undefined in the FinishedExit.
Extended NIL
In Mesa 6, null values are available for all address-containing types. An address-containing type is one constructed using POINTER, DESCRIPTOR, PROCEDURE, PROGRAM, SIGNAL, ERROR, PROCESS, PORT, ZONE or a LONG or subrange form of one of the preceding. The built-in type STRING is address-containing. A relative pointer or relative descriptor type is not considered to be address-containing in Mesa 6.
Null values are denoted as follows:
If T designates any address-containing type, NIL[T] denotes the corresponding null value.
Whenever T is implied by context, NIL abbreviates NIL[T].
If T is not implied by context, NIL means NIL[POINTER TO UNSPECIFIED] and thus continues to match any POINTER or LONG POINTER type.
A fault will occur if you attempt to dereference a null value and have requested NIL checking; a fault will occur unconditionally if you attempt to transfer control through a null value.
Reject Statement
Within a catch phrase, you can use the statement REJECT to explicitly reject a signal, i.e., to terminate execution of that catch phrase and propagate the signal to the enclosing one. (Note that each catch phrase is currently terminated by an implicit REJECT.)
Process Extensions
Aborting a process now raises the predeclared signal ABORTED. The predeclared types MONITORLOCK and CONDITION are now defined with default initialization. The only client-visible field is timeout in CONDITION; its default initial value is 100 ticks.
Restrictions on Assignment
The assignment operations defined upon certain types have been restricted so that variables of those types can be initialized (either explicitly or by default) when they are created but cannot subsequently be updated. A variable is considered to be created at its point of declaration or, for dynamically allocated objects, by the corresponding NEW operation.
In Mesa 6, the following types have restricted assignment operations:
MONITORLOCK
CONDITION
any type constructed using PORT
any type constructed using SEQUENCE
any type constructed using ARRAY in which the component type has a restricted assignment operation.
any type constructed using RECORD in which one of the field types has a restricted assignment operation.
Note that the restrictions upon assignment for a type do not impose restrictions upon assignment to component types. Thus selective updating of fields of a variable may be possible even when the entire variable cannot be updated; e.g., the timeout field of a CONDITION variable can be updated by ordinary assignment. Also, you may apply the operator @ to obtain the address of the entire variable in such a situation.
Operational Changes
User Interface
The standard Mesa 6 Compiler reads commands only from the executive’s command line; it no longer supports interactive input. During compilation, the display and keyboard are disabled. The cursor provides a limited amount of feedback; it moves down the screen to indicate progress through a sequence of commands and to the right as errors are detected. At the end of compilation, the message "Type Key" is displayed in a flashing cursor if there are errors and you have requested the compiler to pause. Typing Shift-Swat aborts the Executive’s current command sequence; Ctrl-Swat invokes the Mesa Debugger; any other character causes normal exit from the compiler.
A summary of compilation commands is written on the file Compiler.log (formerly, Mesa.typescript).
Command Line Arguments
The Mesa 6 Compiler allows you to control the association between modules and file names at the time you invoke the compiler. The compiler accepts a series of commands, each of which has the form
outputFile ← inputFile[id1: file1, ..., idn: filen]/switches
Only inputFile is mandatory; it names the file containing the source text of the module to be compiled, and its default extension is .mesa. Any warning or error messages are written on the file outputRoot.errlog, where outputRoot is the string obtained by deleting any extension from outputFile, if given, otherwise from inputFile. If there are no errors or warnings, any existing error log with the same name is deleted at the end of the compilation.
If a list of keyword arguments appears between brackets, each item establishes a correspondence between the name idi of an included module, as it appears in the DIRECTORY of the source program, and a file with name filei; the default extension for such file names is .bcd. (If the name of an included module is not mentioned on the command line, its file name is computed from information in the DIRECTORY statement; see above).
The optional switches are a sequence of zero or more letters. Each letter is interpreted as a separate switch designator, and each may optionally be preceded by - or ~ to invert the sense of the switch.
If outputFile (and ) are omitted, the object code and symbol tables are written on the file inputRoot.bcd, where inputRoot is inputFile with any extension deleted. Otherwise code and symbols are written on outputFile, for which a default extension of .bcd is supplied. If the compiler detects any errors, the output file is not written and any existing file with the same name is deleted.
The compiler accepts a sequence of one or more commands from the executive’s command line (through the file Com.cm). Commands are separated by semicolons, but you may omit a semicolon between any two successive identifiers (file names or switches), or between a ] and an identifier (but not between an identifier and a /). Note that any required semicolon in an Alto Executive command must be quoted.
You can set global switches by a command with an empty file name. In the form /switches, each letter designates a different switch. Unless a command to change the global switch settings comes first in the sequence of commands, you must separate it from the preceding command by an explicit semicolon. Note that the form switch/c is no longer available for setting global switches.
Switches
The following compilation options have been added:
SwitchOption Controlled
fimplementation of floating-point operations
ltreatment of long pointers
ywarning on runtime calls
If the f switch is set, the compiler generates byte code instructions for floating-point operations (these require microcode support); otherwise, it generates calls through the system dispatch vector (SD) to software routines implementing such operations. If the a and l switches are both set, the compiler generates code using an variant of the Alto Mesa instruction set that implements long pointer accesses to a virtual memory larger than 64K (code generated using the l switch cannot be executed on an Alto, even if long pointers are not used). If you specify -a, the l switch is ignored. The y switch indicates that a warning message should be issued whenever the compiler generates code to invoke a runtime procedure (including some "instructions" which are actually implemented in software).
The default settings for these switches are f, -l and -y.
Distribution:
Mesa Users
Mesa Group
SD Support