Records gives you a way to conveniently and efficiently store related information together. Each piece of information is called an attribute. Thus a record consists of record attributes. Each attribute has a type. For information about defining the type of an attribute, see the Variable/Attribute Types section of the documentation.
There is little difference between a record attribute and a local variable in a Command Implementation. In fact, each Command Implementation can be thought of as having a record containing all its local variables as attributes.
To define a new record type, use the command named "Record" (or "record"), followed by "Attribute" (shortcut "atr") commands, and end with the command named "EndRecord" (shortcut "erecord"). For example:
Record TPerson;
Attribute name, TText;
Attribute emailAddress, TText;
Attribute streetAddress, TText;
Attribute height, TUInt16;
Attribute weight, TUInt16;
Attribute age, TUInt8;
EndRecord;
Using shortcut names, it looks like this:
record TPerson;
atr name, TText;
atr emailAddress, TText;
atr streetAddress, TText;
atr height, uint16;
atr weight, uint16;
atr age, uint8;
erecord;
The "EndRecord" ("erecord") command can optionally have 1 parameter, the name of the new record definition ending there. This must be exactly the same name as was provided in the corresponding "Record" command (the compiler will report an error if the name at the beginning does not match the name at the end). This can be used to help catch mistakes and make the source code easier to read.
By convention, record definitions are usually named beginning with a capital letter "T" for Type, because the name will be used as a variable/attribute type. This helps distinguish the record definition name from variable names, and Command Implementation names, etc, thus avoiding conflicts and confusion.
The "Attribute" (shortcut "atr") command can optionally have a third parameter, the initial/default value of the attribute (must be a constant value). If no initial/default value is specified, then the initial/default value is zero or empty or null (depending on the type of attribute, with null having preference over zero if both are possible). The initial/default value can be an expression (an arithmetic calculation) but it must be possible to calculate the answer/result at compile-time (as opposed to runtime).
Records and tuples are the same thing, except that a record defined using the "Record" command is boxed, whereas a tuple defined using the "Tuple" command is unboxed. Boxed means an instance is stand-alone / free-standing / independent, whereas unboxed means an instance can only exist embedded inside another tuple or record instance. Boxed cannot be embedded, unboxed must be embedded.
Here is an example of defining a tuple (this is a tuple definition):
Tuple TRect;
Attribute left, TSInt32;
Attribute top, TSInt32;
Attribute right, TSInt32;
Attribute bottom, TSInt32;
EndTuple;
Using shortcut names, it looks like this:
tuple TRect;
atr left, sint32;
atr top, sint32;
atr right, sint32;
atr bottom, sint32;
etuple;
A tuple definition can be used inside a record or another tuple like this:
Record TSuperWidget;
Attribute bounds, TRect;
Attribute isVisible, TBoolean;
{...}
EndRecord;
Tuple TRectAndName;
Attribute r, TRect;
Attribute name, TText;
EndTuple;
However a record definition cannot be used inside a tuple or another record (except by reference). The following example is illegal:
Record TSuperWidget;
{...}
EndRecord;
Record TWidgetMaster;
Attribute widgey, TSuperWidget; {ILLEGAL}
{...}
EndRecord;
Tuple TDoubleWidget;
Attribute widg1, TSuperWidget; {ILLEGAL}
Attribute widg2, TSuperWidget; {ILLEGAL}
EndTuple;
Likewise a local variable in a Command Implementation can contain a tuple but not a record. A variable can contain a reference to a record instance, but not a record instance directly. Example:
Record TSuperWidget;
{...}
EndRecord;
Command Test;
Variable r, TRect; {ACCEPTABLE}
Variable widgey, TSuperWidget; {ILLEGAL}
Variable widgey2, RefN TSuperWidget, null; {ACCEPTABLE}
{...}
EndCommand;
A heap-allocated boxed record type (any type defined using the "Record" command) cannot contain a reference to an embedded/unboxed type such as a tuple type or simple integer type. Thus references to embedded/unboxed types can exist only as local variables or parameters (stack-allocated).
This restriction exists because it improves performance and reliability. It eliminates a problematic capability that was not really needed anyway. If it were possible for a heap-allocated record to contain a reference to a stack-allocated local variable, then the reference could be retained and used after the stack-allocated variable is destroyed, causing the program to crash.
Tuple TPoint;
{...}
EndTuple;
Record TPet;
{...}
EndRecord;
Record TPerson;
Attribute height, TUInt; {ACCEPTABLE}
Attribute weight, TUInt; {ACCEPTABLE}
Attribute id, Ref TUInt; {ILLEGAL}
Attribute pt, TPoint; {ACCEPTABLE}
Attribute pt2, Ref TPoint; {ILLEGAL}
Attribute name, TText; {ACCEPTABLE}
Attribute info, Ref TText; {ILLEGAL}
Attribute pet, Ref TPet; {ACCEPTABLE}
Attribute father, Ref TPerson; {ACCEPTABLE}
EndRecord;
A tuple type can contain a reference to an embedded/unboxed type, but if it does then the tuple type cannot be embedded in a heap-allocated boxed record type (the tuple type can only be used as a local variable or parameter allocated on the stack). In other words, a tuple type cannot be used to circumvent the prohibition of a record containing a reference to an embedded/unboxed type. Example:
Tuple TPoint;
Attribute x, TSInt;
Attribute y, TSInt;
EndTuple;
Tuple TNamedRect;
Attribute left, TSInt;
Attribute top, TSInt;
Attribute right, TSInt;
Attribute bottom, TSInt;
Attribute name, Ref TText; {ACCEPTABLE}
EndTuple;
Record TPerson;
Attribute height, TUInt;
Attribute weight, TUInt;
Attribute nr, TNamedRect; {ILLEGAL}
Attribute nr2, Ref TNamedRect; {ILLEGAL}
Attribute pt, TPoint; {ACCEPTABLE}
Attribute pt2, Ref TPoint; {ILLEGAL}
EndRecord;
Pointers do not have this restriction, but pointers are dangerous and are best avoided where possible.
It is important to understand the difference between a definition of a record (a record definition), and an instance of a record (a record instance).
The "record" and "tuple" commands create a definition, but NOT an instance. This means that they define HOW some data should be stored, but they do not actually create a place to store the data. After a definition is created, the definition can be used to create one or more instances, and then data can be stored into an instance. Definitions and instances are separate because it allows multiple instances to be created from the one same definition, and that is a very useful ability.
For example, you can create 1 record definition named "TPerson", and then create 3 instances of it. The first instance is for the person named "Tom", the second instance is for the person named "Dick", and the third instance is for the person named "Harry". All 3 instances have the same attributes, but different values stored in the attributes.
To create an instance of a tuple type, use the command named "Variable" (shortcut "var") within a Command Implementation, and specify the tuple type for the type of the new variable. In the following example, "inst" will be an instance of the tuple named "TPoint".
Tuple TPoint;
Attribute x, TSInt;
Attribute y, TSInt;
EndTuple;
Command Test;
Variable inst, TPoint;
EndCommand;
When creating a record or tuple instance, all the attributes in the new instance will be set to their initial/default value, as specified in the record/tuple definition (third parameter of the "Atttribute" command). If no initial/default value was specified for an attribute, then the initial/default value is zero or empty or null (with null being used in preference to zero or empty if both are possible).
The default values of some or all of the attributes can be overridden when an instance is created. To do this, supply the third parameter of the "Variable" command. The third parameter is the desired initial value of the variable. In the case of the variable being a tuple type, the third parameter must be supplied enclosed in round brackets, with the brackets containing a comma-separated list of values for the attributes of the tuple. For example:
Variable inst, TPoint, (45,80);
In the above example, "x" is set to 45, and "y" is set to 80. The values in the brackets are supplied in the same order as the attributes are defined in the tuple. If fewer values are supplied than the number of attributes, then the remaining attributes at the end are set to the default values. In the following example, "x" is set to 45, and "y" is set to 1.
Tuple TPoint;
Attribute x, TSInt, 1;
Attribute y, TSInt, 1;
EndTuple;
Command Test;
Variable inst, TPoint, (45);
EndCommand;
These values can also be expressions, but the "Variable" command requires that the expression be a constant value (meaning capable of being calculated at compile-time as opposed to runtime). Thus the expression cannot use another variable (except if that variable is actually a constant value). The following example is valid:
Variable pt, TPoint, (45+2, 80+2);
It is possible to supply the attribute values in a different order, and to omit any attribute (not only attributes at the end). To do this, the attributes must be specified by name. The comma-separated list within the round brackets is then a list of attribute names and values with assignment symbols ("=") between the names and values. For example:
Variable pt, TPoint, (y=45, x=80);
A record instance is not created in the same way as a tuple instance. A variable can directly contain a tuple instance. A variable cannot directly contain a record instance, but it can contain a reference to a record instance. Thus the following example is invalid:
Record TPerson;
Attribute height, TUInt;
Attribute weight, TUInt;
Attribute id, TUInt;
Attribute father, Ref TPerson;
EndRecord;
Command Test;
Variable inst, TPerson; {ILLEGAL}
EndCommand;
To create an instance of a record, use the command named "Create" (or "create") with a variable that is a nullable reference to a record. For example:
Command Test;
Variable person, Nullable Ref TPerson, null;
Create person;
EndCommand;
Or using shortcuts:
cmd Test;
var person, RefN TPerson;
create person;
ecmd;
The "Create" command does not require that the variable contain a null reference. If the variable contains a reference to an existing record instance, then the "Create" command removes that reference and replaces it with a reference to the newly created record instance. If the "Create" command fails, the variable is unchanged.
The "Create" command sets the values of the attributes in the record to the default values. Like tuples, the default values can be overridden. To do this, supply a second parameter to the "Create" command. This second parameter must be a list of values enclosed in round brackets, the same as for tuples (see description above). For example:
Create person, (height=177, weight=70+1, father=edward);
The specified attributes have their values set to the supplied values (overriding the default values), and the remaining unspecified attributes are set to the default values.
If a record contains an attribute that is an unnullable reference type, then a non-null reference value must be supplied for this attribute when the record is created using the "Create" command, otherwise the compiler will report an error.
If a record contains an attribute using the storage method "UnchangingSize", and if a value is not supplied for this attribute when the record is created, then the attribute is set to the default value, and its size cannot be changed after that. If there is no default value, the size becomes zero and cannot be changed.
In addition to the "Create" command, there is also a "Create" function. The "Create" function has the same parameters as the "Create" command, except that the first parameter is a type rather than a variable. The last 2 lines of the following example are equivalent:
Variable person, Nullable Ref TPerson, null; Create person; Set person, Create[TPerson];
The "Create" function can be used to create an instance of a type different than the type of the variable, but only if the type being created is compatible with the type of the variable. For example:
Variable person, Nullable Ref TPerson, null; Set person, Create[TSpecialPerson];
The "Create" function can also be used to make a variable that is an unnullable reference type. For example:
Variable p1, Nullable Ref TPerson, null; {LEGAL}
Variable p2, Ref TPerson, null; {ILLEGAL}
Variable p3, Ref TPerson; {ILLEGAL}
Variable p4, Ref TPerson, Create[TPerson]; {LEGAL}
Note that variables are set to their initial value only once (when the Command Activation Record is created), not every time the "Variable" command is encountered during iterations etc. Thus the "Create" function in the above example will be executed at most once per execution of the Command Implementation containing it.
It is not possible to create a stand-alone instance of a tuple (embedded) definition using the "Create" command or function. Instead you can create an instance of a record (boxed) definition that contains an attribute defined to use the tuple definition, and then the act of creating the record instance also causes the creation of an embedded tuple instance.
Although there is a command for creating a record instance, there is no command for destroying a record instance. A record instance is destroyed automatically when the last reference to it is removed (when no references to it exist). A reference can be removed by setting it to null. Reference variables in a Command Implementation are automatically removed when the command returns (finishes executing).
It is not possible to cause a Command Implementation of your choice to be executed when the "Create" command is invoked, nor when a record instance is destroyed. Instead you can simply make a Command Implementation that creates a record instance using the normal intrinsic "Create" command, then does some things to/with it, and then outputs the record reference. Then whenever you want to create a new instance, you invoke your Command Implementation instead of the intrinsic "Create" command. Thus you make a wrapper around the intrinsic "Create" command, rather than attempting to make the intrinsic "Create" command invoke your command.
The dot (".") symbol is used to access an attribute in (a component of) a record or tuple instance. Write the name of the variable that is a tuple type, followed by a dot, followed by the name of the attribute to access. There must be no space characters on either side of the dot. For example:
Tuple TRect;
Attribute left, TSInt;
Attribute top, TSInt;
Attribute right, TSInt;
Attribute bottom, TSInt;
EndTuple;
Command Test;
Variable bounds, TRect;
Variable width, TSInt;
Variable height, TSInt;
Set bounds.left, 10;
Set bounds.right, 110;
Set bounds.top, 40;
Set bounds.bottom, 350;
Set width, bounds.right - bounds.left;
Set height, bounds.bottom - bounds.top;
Set bounds.left, 300;
Set bounds.top, 300;
Set bounds.right, bounds.left + width;
Set bounds.bottom, bounds.top + height;
EndCommand;
In the case of a tuple in a tuple, 2 dots may be used. For example:
Tuple TPoint;
Attribute x, TSInt;
Attribute y, TSInt;
EndTuple;
Tuple TLine;
Attribute start, TPoint;
Attribute end, TPoint;
EndTuple;
Command Test;
Variable line, TLine;
Set line.start.x, 100;
Set line.start.y, 120;
Set line.end.x, 300;
Set line.end.y, 250;
EndCommand;
The dot symbol is used both in the case of a variable directly containing a tuple, and in the case of a variable containing a reference to a record or tuple. Here is an example using a parameter where the type of the parameter is a reference to a tuple:
Command Test;
Parameter inBounds, Ref ReadOnly TRect;
Variable width, TSInt;
Variable height, TSInt;
Set width, inBounds.right - inBounds.left;
Set height, inBounds.bottom - inBounds.top;
EndCommand;
The "Alias" (or "alias") command can be used to provide an alias (alternate name) for an attribute in a record or tuple definition.
In the following example, a tuple is defined containing 2 attributes named "x" and "y". And "h" is defined as an alternate name for attribute "x", and "v" is defined as an alternate name for attribute "y".
Tuple TPoint;
Attribute x, TSInt;
Attribute y, TSInt;
Alias h, x;
Alias v, y;
EndTuple;
An alias command cannot appear before the attribute it defines an alias for. The attribute must be defined before any aliases that use it.
The compiler is permitted to store record/tuple attribute values in any order it wants. For example, the first attribute in the record definition is not necessarily stored at the beginning of the memory allocated for an instance of the record. This allows the compiler to organize the storage in whatever way it determines is most efficient. It attempts to maximize speed and minimize wasted memory.
When a tuple definition uses another tuple definition, either can be specified first. The compiler achieves this by deferring evaluation (or full evaluation) of the contents (sub-commands) of the tuple definition until all tuple definitions have been found and their names registered. Other evaluations may also be deferred where necessary.
Here is an example of the order of evaluation:
cmd MakeNacho; {1}
var info, TWidgetInfo; {12}
set info.isVisible, true; {13}
ecmd; {2}
tuple TWidgetInfo; {3}
atr location, TPoint; {7}
atr name, TText; {8}
atr isVisible, bool; {9}
etuple; {4}
tuple TPoint; {5}
atr x, sint32; {10}
atr y, sint32; {11}
etuple; {6}
The compiler may also use "on demand" strategies for determining when to evaluate commands, or when to finish evaluating a definition.