The following is an exhaustive list of nodes (syntax constructs) in MPL, including their descriptions.
| Syntax | Name | Description |
|---|---|---|
[ |
Block | Begins collection of nodes for a Block object. |
] |
BlockEnd | Ends Block collection, creates the resulting Block object, and pushes it onto the data stack. |
# |
Comment | Begins a comment that continues until the next line feed or end of file. |
{ |
Dict | Begins a nested scope whose created locals become items of the resulting Struct object. |
} |
DictEnd | Ends the Dict scope, constructs the resulting Dict, List, or Tuple object, and pushes it onto the data stack. |
NUMBERi8 |
Int8 | Pushes an Int8 object (an 8-bit integer). |
NUMBERi16 |
Int16 | Pushes an Int16 object (a 16-bit integer). |
NUMBER[i32] |
Int32 | Pushes an Int32 object (a 32-bit integer). |
NUMBERi64 |
Int64 | Pushes an Int64 object (a 64-bit integer). |
NUMBERix |
Intx | Pushes an Intx object (a pointer-width integer). |
NAME: |
Label | Pushes a name onto the name stack for Variable to consume. |
NAME |
Name | Mentions one selected builtin, local, or visible field by source-text name: executes a builtin, calls a callable object, or otherwise pushes an immutable Ref, copied object, or new object. |
.NAME |
NameMember | Pops a Dict object or a Ref to Dict, selects the last matching field, calls it if callable, or otherwise pushes an immutable Ref, copied object, or new object. |
@NAME |
NameRead | Reads one selected local or visible field by source-text name without calling it, pushing a Ref, copied object, or new object. |
.@NAME |
NameReadMember | Pops a Dict object or a Ref to Dict, selects the last matching field, reads it without calling, and pushes a Ref, copied object, or new object. |
!NAME |
NameWrite | Writes one selected local or visible field by source-text name. |
.!NAME |
NameWriteMember | Pops a mutable Ref to Dict and writes one selected field by source-text name. |
NUMBERn8 |
Nat8 | Pushes a Nat8 object (an 8-bit natural). |
NUMBERn16 |
Nat16 | Pushes a Nat16 object (a 16-bit natural). |
NUMBERn32 |
Nat32 | Pushes a Nat32 object (a 32-bit natural). |
NUMBERn64 |
Nat64 | Pushes a Nat64 object (a 64-bit natural). |
NUMBERnx |
Natx | Pushes a Natx object (a pointer-width natural). |
REALr32 |
Real32 | Pushes a Real32 object (a 32-bit real). |
REAL[r64] |
Real64 | Pushes a Real64 object (a 64-bit real). |
"TEXT" / «TEXT» |
Text | Pushes a Text object. |
( |
Tuple | Begins a nested scope whose remaining stack items become unnamed items of the resulting Struct object. |
) |
TupleEnd | Ends the Tuple scope, constructs the resulting List or Tuple object, and pushes it onto the data stack. |
; |
Variable | Pops a name from the name stack, pops an object from the data stack, and creates a local. |
Block & BlockEnd ([...])
The Block node begins collecting nodes. The matching BlockEnd node ends that collection and creates a new Block object. A Block is a Meta object whose schema includes the enclosed AST nodes; see Block and Code for source-position identity and processing. Blocks can be nested, allowing structured logic to be grouped within multiple levels of [ ... ] constructs, and each matching pair produces its own Block object.
Blocks are not compiled at the control-flow location where they appear (see Block and Code). Using Block and BlockEnd only creates a Block object. Later, that object is handled in one of the following ways:
- Call Block when it is mentioned by Name or NameMember, used through special fields such as
ASSIGN,CALL,DIE, andINIT, passed to block-calling builtins such ascall,if,ucall, oruif, or used as the outer block ofexportFunction. Runtime and compile-time behavior depends on the caller: for example,ifmay compile one or both branches, whileucallanduifinline the logic. - Interpret as predicate when it is used as a
PREfield. The Block is interpreted at compile time and must produce a knownCond; it is not compiled as a runtime function call. - Compile and store Code when it is assigned to a Code target of the corresponding schema. The Block is compiled immediately and the resulting Code object is stored, but it is not called.
- Store or move the Block object itself in other contexts, such as writing it to a Block target of the same schema. The Block is treated as a Meta object and is not called.
- A managed stack item captured by a Block remains owned by its original scope; the captured view is not destroyed separately.
Example
{} () {} [
namedBlock: ["-- named block --" printCompilerMessage];
namedBlock
{
blockField: ["-- named member block --" printCompilerMessage];
}.blockField
runner: {
CALL: ["-- special CALL field --" printCompilerMessage];
};
runner
choice: ["-- fallback --" printCompilerMessage];
choice: {
CALL: ["-- matched --" printCompilerMessage];
PRE: ["-- PRE --" printCompilerMessage FALSE];
};
choice
codeLocal: {} () {} codeRef;
["-- block compiled for code --" printCompilerMessage] !codeLocal # a non-NIL Code
["-- call builtin --" printCompilerMessage] call
TRUE ["-- if builtin --" printCompilerMessage] [] if
["-- ucall builtin --" printCompilerMessage] ucall
TRUE ["-- uif builtin --" printCompilerMessage] [] uif
] "main" exportFunction
Expected Output During Compilation
-- named block --
-- named member block --
-- special CALL field --
-- PRE --
-- fallback --
-- block compiled for code --
-- call builtin --
-- if builtin --
-- ucall builtin --
-- uif builtin --
Comment (#)
The Comment node begins a comment that extends from # to the next line feed or to the end of the file. The comment text does not affect program logic and is not parsed as MPL syntax.
A comment may occupy a whole line or follow other nodes on the same line. Because the comment text is not parsed, unmatched delimiters or other syntax characters inside it have no effect.
Example
{} () {} [
"-- comment ignored --" printCompilerMessage # ] } ) " « everything here is ignored
] "main" exportFunction
Expected Output During Compilation
-- comment ignored --
Dict & DictEnd ({...})
The Dict node begins a nested scope in which locals can be created (see Structs). The matching DictEnd node ends that scope, constructs the resulting Struct object from those created locals, and pushes it onto the data stack. The inner locals do not remain available by their local names after the scope ends.
DictEnd collects created locals only. Any other stack items left in the nested scope remain on the data stack below the pushed Struct object.
Locals created between { and } become items of the resulting Struct object. Locals with non-empty names become named fields. Locals with empty names still participate in the result. If all created locals have empty names and none of the non-Meta locals is static, the final object is a List or Tuple whose items are unnamed. Locals are created with NAME: … ; or with the builtin def; only that builtin can create a local with the empty name (1 "" followed by def). The resulting named fields or unnamed items keep the created locals’ schemas and storage properties. If several created fields have the same name, they all remain in the object; later lookup rules decide which one is selected.
See Structs for static-field rules: at DictEnd, a local made static with the builtin virtual becomes a static field whose value is packed into the resulting object’s schema instead of occupying runtime field storage, so it must be known at compile time.
Depending on the created locals, DictEnd does one of the following:
- Create a Dict when at least one local has a non-empty name or at least one non-Meta local is static.
- Create a List otherwise when at least one local was created and all their schemas are identical.
- Create a Tuple otherwise, including the empty case.
See Structs for reserved field behavior and reverse destruction order: DictEnd creates the resulting object without calling callable fields, and a field can rely on fields declared before it during its own DIE because destruction is in reverse declaration order.
Dict constructs can be nested. Nested { ... } pairs create nested Struct objects.
Example
{} () {} [
"-- empty --" printCompilerMessage
{} printStack _:;
"-- named and nested --" printCompilerMessage
{
name: "Alice";
stats: {
score: 100;
level: 2;
};
} printStack _:;
"-- fields created by def --" printCompilerMessage
{ 100 "score" def 2 "level" def } printStack _:;
"-- duplicate field names --" printCompilerMessage
{ x: 1; x: 2; } printStack _:;
"-- unnamed fields --" printCompilerMessage
{ 1 "" def FALSE "" def } printStack _:;
] "main" exportFunction
Expected Output During Compilation
-- empty --
()
-- named and nested --
{
name: "Alice";
stats: {
score: 100;
level: 2;
};
}
-- fields created by def --
{
score: 100;
level: 2;
}
-- duplicate field names --
{
x: 1;
x: 2;
}
-- unnamed fields --
(1 FALSE)
Int8, Int16, Int32, Int64, and Intx
The Int8, Int16, Int32, Int64, and Intx nodes push integer objects onto the data stack. Integer literals may be negative, zero, or positive. A literal is invalid if its value does not fit the selected schema. For Intx, this check uses pointer-width limits. Integer values use two's complement representation.
Decimal
A decimal integer literal is written as digits, optionally preceded by -. If a suffix is present, it must immediately follow the digits. Decimal integer literals cannot have leading zeroes except for 0 itself. The following suffix forms are available:
- No suffix: Int32, from
-2147483648to2147483647 i8: Int8, from-128i8to127i8i16: Int16, from-32768i16to32767i16i32: Int32, from-2147483648i32to2147483647i32i64: Int64, from-9223372036854775808i64to9223372036854775807i64ix: Intx, with pointer-width limits
Hexadecimal
A hexadecimal integer literal begins with exactly 0x, may be preceded by -, and may use digits 0 to 9 and uppercase letters A to F. If a suffix is present, it must immediately follow the hexadecimal digits. The following suffix forms are available:
- No suffix: Int32, from
-0x80000000to0x7FFFFFFF i8: Int8, from-0x80i8to0x7Fi8i16: Int16, from-0x8000i16to0x7FFFi16i32: Int32, from-0x80000000i32to0x7FFFFFFFi32i64: Int64, from-0x8000000000000000i64to0x7FFFFFFFFFFFFFFFi64ix: Intx, with pointer-width limits
Literal syntax is case-sensitive: use exactly 0x for hexadecimal notation, lowercase suffixes such as i8 and ix, and uppercase hexadecimal letters A to F.
When formatted, Int32 objects are shown without the optional i32 suffix.
The example below uses only fixed-width forms. Intx is pointer-width, so its exact limits are not shown there.
Example
{} () {} [
"-- decimal examples --" printCompilerMessage
-128i8 printStack _:;
32767i16 printStack _:;
2147483647 printStack _:;
9223372036854775807i64 printStack _:;
"-- hexadecimal examples --" printCompilerMessage
0x7Fi8 printStack _:;
-0x8000i16 printStack _:;
0x7FFFFFFF printStack _:;
0x7FFFFFFFFFFFFFFFi64 printStack _:;
] "main" exportFunction
Expected Output During Compilation
-- decimal examples --
-128i8
32767i16
2147483647
9223372036854775807i64
-- hexadecimal examples --
127i8
-32768i16
2147483647
9223372036854775807i64
Name (NAME)
The Name node designates mentioning one selected builtin, local, or visible field by source-text name.
After the first character, names stop before a space, line feed, carriage return, ., #, ,, ;, or a closing ), ], or }; : introduces a label, while !, @, (, [, {, quotation marks, and guillemets are forbidden inside a name. A comma can begin a name: , and ,foo are ordinary names. See Grammar tokens for the character classes.
See Names, lookup, and overloads for lookup order and PRE handling: the Name node selects the newest matching candidate and is invalid when no candidate matches. If PRE processing succeeds but does not return a known Cond, the Name mention is invalid.
See Selection and destinations and Locations, references, and views for the read rule: Name executes a builtin, calls a callable selected object, or reads it according to its schema; mentioning a known NIL Code through Name is invalid, and reading a static Ref produces a known Ref (including known NIL). For a selected non-callable, non-static local or visible field, Name creates a new immutable Ref when the field is In-place, or copies the field when it is Ref or Text, making a copied Ref immutable regardless of the stored reference's mutability; these rules apply whether the value is known or unknown. For a selected non-callable Meta object or static local or field, Name creates a new object from compile-time information instead of using the non-static read branch.
The special PRE and CALL fields of a callable Dict must hold Blocks. An ordinary compilation failure inside the PRE Block skips that candidate. A non-Block PRE field is an error when PRE is evaluated; CALL must be a Block when the candidate is selected and called.
Selection and calling of a callable Dict through Name work as follows:
- If the Dict has a
PREfield, thatPRElogic is evaluated at compile time before selection. PREcaptures a non-Meta managed stack item through a Ref to the existing object, not as an owning copy; this capture does not add a destruction. A captured Meta item keeps its Meta schema.- During both
PREevaluation andCALL, the fields of the callable Dict are available for scope lookup. - For a non-Meta non-static callable Dict, a special name
closureis also available during bothPREevaluation andCALLand references that Dict with the call's access mutability: it is mutable when a non-static In-place callable Dict local is called by NAME; a call through a Ref, including a Ref local mentioned by NAME, retains that Ref's mutability. Read it as@closure; the bareclosurecalls the Dict again because the Dict is callable. In-place fields namedclosureshadow that special name.
Calling a non-Meta, non-static In-place callable Dict local by NAME gives PRE and CALL mutable access to its fields. Calling through a Ref preserves that Ref's mutability; @dict const call therefore gives immutable field access.
Example
{} () {} [
"-- builtin --" printCompilerMessage
1 2 + printStack _:;
blockLocal: ["-- block local --" printCompilerMessage];
blockLocal
codeLocal: {} () {} codeRef;
["-- code local --" printCompilerMessage] !codeLocal # a non-NIL Code
codeLocal
textLocal: 0 ("") dynamic @; # an unknown Text
"-- text local --" printCompilerMessage
textLocal printStack _:;
ordinaryLocal: 0 dynamic;
"-- In-place local --" printCompilerMessage
ordinaryLocal printStack _:;
mutableRef: @ordinaryLocal;
"-- Ref local --" printCompilerMessage
mutableRef printStack _:;
metaLocal: ();
"-- meta local --" printCompilerMessage
metaLocal printStack _:;
dict: {
field: 0 dynamic;
method: [
"-- field through scope lookup --" printCompilerMessage
field printStack _:;
];
};
dict.method
runner: {
CALL: ["-- dict with CALL --" printCompilerMessage];
};
runner
choice: ["-- fallback --" printCompilerMessage];
choice: {
CALL: ["-- matched --" printCompilerMessage];
PRE: ["-- PRE --" printCompilerMessage FALSE];
};
choice
] "main" exportFunction
Expected Output During Compilation
-- builtin --
3
-- block local --
-- code local --
-- text local --
Text
-- In-place local --
Int32 Cref
-- Ref local --
Int32 Cref
-- meta local --
()
-- field through scope lookup --
Int32 Cref
-- dict with CALL --
-- PRE --
-- fallback --
NameMember (.NAME)
The NameMember node designates mentioning one selected field by source-text name (see Structs). It pops either a bare Dict object or a Ref to Dict from the data stack, selects the last field with the specified name, and then handles that field. Mentioning does not require mutable access to the outer Dict.
Candidates are tried from newest to oldest. A callable Dict field with a PRE field is selected only if that PRE logic is compilable and produces a known TRUE. A compilation failure inside the PRE Block or a known FALSE continues selection with older fields; a non-Block PRE field is an error. Potentially invalid code may therefore be tried during overload selection without making the overall mention invalid, provided each failed candidate is skipped. If that PRE logic compiles but does not return a known Cond, the mention is invalid. If no candidate matches, the mention is invalid. See the callable-Dict field rule.
Depending on what the selected field is, NameMember does one of the following:
- Call object for Block fields, Code fields, and Dict fields with
CALLfields. Mentioning a knownNILCode field is invalid. - Create a new immutable Ref to the object for known or unknown non-static In-place fields. If the popped Dict is known to be
NIL, the result is a knownNILimmutable Ref of the field's schema, such asInt32 CNIL. - Copy the object for known or unknown non-static Ref and Text fields. If the popped Dict is known to be
NIL, the result is an object of the appropriate kind whose value is a knownNIL, such asInt32 CNILorText CNIL. Ref results are pushed as new immutable Ref views, regardless of original mutability. - Produce a new object from compile-time information for Meta fields and static fields. Static Ref objects still belong to this branch: the created object is itself a known Ref, for example a known
NILRef.
Calling a Block field through NameMember works as follows:
- During the call, the fields of the popped Dict are available for direct scope lookup.
- If the popped Dict was reached through a Ref, a special name
selfis also available during the call and references that outer Dict with matching mutability. If the popped object was a bare Dict, no separateselfname is introduced.
Selection and calling of a callable Dict field through NameMember work as follows; the special fields must satisfy the callable-Dict field rule:
- If the field has a
PREfield, thatPRElogic is evaluated at compile time before selection. - During
PREevaluation, the fields of the callable field Dict are available for scope lookup. For a non-Meta non-static callable field Dict, a special nameclosureis also available and references that field Dict with the selected field's access mutability, also respecting a stored Ref field's own mutability. Read it as@closure; the bareclosurecalls the Dict again because it is callable. - During
CALL, the fields of the popped outer Dict and the callable field Dict are both available for scope lookup. - If the popped outer Dict is available through a Ref, a special name
selfis also available duringCALLand references that outer Dict with the outer Dict's access mutability. This is separate fromclosure. - When both Dicts provide fields for direct lookup during
CALL, the callable field Dict has priority. If both contain a field with the same name, direct lookup selects the callable field’s field, while the outer field remains accessible throughself. In-place fields namedselforclosurestill shadow those special names.
Example
{} () {} [
dict: {
ordinaryField: 0 dynamic;
textField: 0 ("") dynamic @; # an unknown Text
metaField: ();
staticField: 0 virtual;
outerValue: 7;
blockField: [
"-- block field --" printCompilerMessage
self.outerValue printStack _:;
];
choice: ["-- fallback field --" printCompilerMessage];
choice: {
CALL: ["-- matched field --" printCompilerMessage];
PRE: ["-- PRE --" printCompilerMessage FALSE];
};
callableField: {
innerValue: 9;
CALL: [
"-- callable Dict field --" printCompilerMessage
self.outerValue printStack _:;
innerValue printStack _:;
@closure.innerValue printStack _:;
];
};
};
"-- In-place field --" printCompilerMessage
dict.ordinaryField printStack _:;
"-- text field --" printCompilerMessage
dict.textField printStack _:;
"-- meta field --" printCompilerMessage
dict.metaField printStack _:;
"-- static field --" printCompilerMessage
dict.staticField printStack _:;
dict.blockField
dict.choice
dict.callableField
] "main" exportFunction
Expected Output During Compilation
-- In-place field --
Int32 Cref
-- text field --
Text
-- meta field --
()
-- static field --
0
-- block field --
7 Cref
-- PRE --
-- fallback field --
-- callable Dict field --
7 Cref
9 Cref
9 Cref
NameRead (@NAME)
The NameRead node designates reading one selected local or visible field by source-text name without calling it. Attempting to read a builtin is invalid. If the resolved entity is callable, reading it still does not call it.
See Locations, references, and views for view and mutability rules: NameRead returns the selected value according to its schema; a static Ref remains a known Ref (including known NIL). For a non-static local or visible field, NameRead creates a new Ref when the field is In-place and copies the field when it is Ref, Code, or Text. NameRead creates a new object from compile-time information for a Meta object or a static local or field. A static Ref local retains its Ref mutability when read. Field-view mutability follows the access path: for a non-static In-place visible field, the returned Ref is mutable exactly when the enclosing Dict is reached mutably; for a Ref field, the returned Ref is mutable exactly when both the access path and the field's own Ref schema are mutable.
Example
{} () {} [
ordinaryLocal: 0 dynamic;
textLocal: 0 ("") dynamic @; # an unknown Text
metaLocal: ();
"-- In-place local --" printCompilerMessage
@ordinaryLocal printStack _:;
"-- text local --" printCompilerMessage
@textLocal printStack _:;
"-- meta local --" printCompilerMessage
@metaLocal printStack _:;
dict: {
ordinaryField: 0 dynamic;
textField: 0 ("") dynamic @;
staticField: 0 virtual;
CALL: [
"-- In-place field --" printCompilerMessage
@ordinaryField printStack _:;
"-- text field --" printCompilerMessage
@textField printStack _:;
"-- static field --" printCompilerMessage
@staticField printStack _:;
];
};
"-- mutable fields --" printCompilerMessage
dict
"-- immutable fields --" printCompilerMessage
@dict const call
] "main" exportFunction
Expected Output During Compilation
-- In-place local --
Int32 Ref
-- text local --
Text
-- meta local --
()
-- mutable fields --
-- In-place field --
Int32 Ref
-- text field --
Text
-- static field --
0
-- immutable fields --
-- In-place field --
Int32 Cref
-- text field --
Text
-- static field --
0
NameReadMember (.@NAME)
The NameReadMember node designates reading one selected field by source-text name without calling it (see Structs). It pops either a bare Dict object or a Ref to Dict from the data stack, selects the last field with the specified name, and reads that field. If the field is callable, reading it still does not call it. Reading does not require mutable access to the outer Dict.
Depending on what the field is, NameReadMember does one of the following:
- Produce a new Ref to the object for non-static In-place fields. If the popped Dict is known to be
NIL, the result is a knownNILRef of the field's schema, such asInt32 CNIL. - Produce the copied object for non-static Ref,
Code, and Text fields. If the popped Dict is known to beNIL, the result is an object of the appropriate kind whose value is a knownNIL, such asInt32 CNILorText CNIL. - Produce a new object from compile-time information for Meta fields and static fields. Static Ref objects still belong to this branch: the created object is itself a known Ref, for example a known
NILRef.
Field selection is by the last field with that name in the popped Dict. A non-static In-place field produces a mutable Ref when that Dict was reached mutably and an immutable Ref otherwise. Non-static Ref fields also respect their own mutability.
Example
{} () {} [
dict: {
ordinaryField: 0 dynamic;
textField: 0 ("") dynamic @; # an unknown Text
codeField: {} () {} codeRef dynamic;
metaField: ();
staticField: 0 virtual;
};
"-- mutable fields --" printCompilerMessage
@dict .@ordinaryField printStack _:;
@dict .@textField printStack _:;
@dict .@codeField printStack _:;
@dict .@metaField printStack _:;
@dict .@staticField printStack _:;
"-- immutable fields --" printCompilerMessage
dict .@ordinaryField printStack _:;
dict .@textField printStack _:;
dict .@codeField printStack _:;
dict .@metaField printStack _:;
dict .@staticField printStack _:;
] "main" exportFunction
Expected Output During Compilation
-- mutable fields --
Int32 Ref
Text
{} () {} codeRef
()
0
-- immutable fields --
Int32 Cref
Text
{} () {} codeRef
()
0
NameWrite (!NAME)
The NameWrite node designates writing one source object to a selected local or visible field by source-text name. The lookup, destination, and view rules are in Names, lookup, and overloads, Selection and destinations, and Locations, references, and views. It pops one source object and writes it to the newest local or visible field selected by source-text name, but not to a builtin; a field write requires a mutable enclosing Ref, and static fields are invalid targets.
Depending on the source and target, NameWrite does one of the following:
- Accept without calling when the source is a Block and the target is a Block local of the same schema. The Block is not called.
- Compile and store Code when the source is a Block and the target is Code of the corresponding schema. The Block is compiled and the resulting Code object is stored, but the code is not called.
- Replace the stored Ref in the target when the target is a Ref local or a non-static Ref field. The source may be either a Ref whose target schema matches the target referent schema or a direct object of the target referent schema. A direct object first produces a new Ref to that source object. The source object becomes a local of the scope that performs the write, so a Ref local of an enclosing scope cannot be pointed at it from behind a call boundary (
[5 !r] callfails withA local value has leaked to an external reference;[5 !r] ucallis accepted). Mutable Ref targets require a mutable resulting Ref view, so an immutable source Ref view is invalid there. Immutable Ref targets accept mutable or immutable resulting Ref views. - Move the source object into the target when the target is a non-Ref local or a non-static non-Ref field. The source must be a direct object of the same schema. The previous target object is destroyed, the source object is moved into its place, and the source is left uninitialized so it is not destroyed again.
Known issue: a write through NameWrite or NameWriteMember accepts a Block of a different schema for a non-static Block field but leaves the original field unchanged; a Block field can hold only the Block created with it because a Block's schema is its source position; write the Block to a Code field to compile and store an exchangeable callable.
For static locals and Meta targets, the same schema and mutability rules still apply, but the update may stay entirely at compile time because there may be no runtime target storage to rewrite.
Assigning a Block to Code through NameWrite works as follows:
- The source Block is compiled immediately and is not called.
- The destination Code schema defines the function signature and exact required output. The compiled function begins with that signature’s input stack items, not with the caller’s current runtime stack contents.
- During compilation, global names and any names that resolve through normal lookup rules to Meta locals or fields, or to static ones, remain available.
- Names that resolve through normal lookup rules to other non-global locals or fields (solid ones) cannot be captured. Using them makes the assignment invalid.
- The Block’s capture tree is matched against the captures available at the assignment site. If a compatible compiled result already exists for the same signature and capture situation, that compiled result may be reused.
- Normal capture and lifetime checks still apply. Any invalid capture makes the assignment invalid, as does a Ref output that targets one of the compiled function’s own local objects; a non-Ref output may return a whole local object of its schema.
- Any compile-time effects of compiling the Block, such as
printCompilerMessage, occur at assignment time.
Writing does not call callable sources or targets automatically.
Normal Ref lifetime rules still apply. A write is invalid if it would make the stored Ref outlive its source object.
This example uses runtime output because the write effects are shown there directly. In the runtime output, the trailing FALSE and TRUE values are the results of isConst checks on the resulting references.
Example
"String" use
{} () {} [
ordinaryLocal: 1;
mutableReference: @ordinaryLocal;
immutableReference: ordinaryLocal;
2 !ordinaryLocal
3 !mutableReference
4 !immutableReference
codeLocal: {} () {} codeRef;
["-- block compiled for code --" printCompilerMessage] !codeLocal # a non-NIL Code
dict: {
field: 5;
refField: ordinaryLocal;
method: [
6 !field
@ordinaryLocal !refField
("-- fields -- " field " " refField "\n") printList
];
};
@dict.method
("-- In-place local -- " ordinaryLocal "\n") printList
("-- mutable Ref target -- " mutableReference " " @mutableReference isConst "\n") printList
("-- immutable Ref target -- " immutableReference " " @immutableReference isConst "\n") printList
] "main" exportFunction
Expected Output During Compilation
-- block compiled for code --
Expected Output
-- fields -- 6 2
-- In-place local -- 2
-- mutable Ref target -- 3 FALSE
-- immutable Ref target -- 4 TRUE
NameWriteMember (.!NAME)
The NameWriteMember node designates writing one source object to a selected field by source-text name (see Structs). It pops one object whose dereferenced schema is Dict, selects the last field with the specified name, then pops one source object and writes it to that field. A successful write requires the popped object itself to be a mutable Ref to Dict.
If several fields with the same name are present, NameWriteMember selects the last one. Static fields are invalid write targets.
Source and destination rules are those of NameWrite; NameWriteMember differs only in selecting the field through a mutable Ref to Dict popped from the stack.
For Meta fields, the same schema and mutability rules still apply, but the update may stay entirely at compile time because there may be no runtime field storage to rewrite.
Compiling a Block into a Code field follows the NameWrite Block-to-Code rules; here the selected field’s Code schema supplies the signature and the same capture and lifetime restrictions apply.
Writing does not call callable sources or targets automatically.
Normal Ref lifetime rules still apply. A write is invalid if it would make the stored Ref outlive its source object.
This example uses runtime output because the write effects are shown there directly. In the runtime output, the trailing FALSE and TRUE values are the results of isConst checks on the resulting references.
Example
"String" use
{} () {} [
ordinaryLocal: 1;
dict: {
ordinaryField: 2;
mutableReferenceField: @ordinaryLocal;
immutableReferenceField: ordinaryLocal;
codeField: {} () {} codeRef;
};
3 @dict.!ordinaryField
4 @dict.!mutableReferenceField
5 @dict.!immutableReferenceField
["-- block compiled for code field --" printCompilerMessage] @dict.!codeField
("-- In-place field -- " dict.ordinaryField "\n") printList
("-- mutable Ref field -- " dict.mutableReferenceField " " @dict.@mutableReferenceField isConst "\n") printList
("-- immutable Ref field -- " dict.immutableReferenceField " " @dict.@immutableReferenceField isConst "\n") printList
] "main" exportFunction
Expected Output During Compilation
-- block compiled for code field --
Expected Output
-- In-place field -- 3
-- mutable Ref field -- 4 FALSE
-- immutable Ref field -- 5 TRUE
Nat8, Nat16, Nat32, Nat64, and Natx
The Nat8, Nat16, Nat32, Nat64, and Natx nodes push natural objects onto the data stack. Natural literals are zero or positive. A negative literal with a natural suffix is invalid. Natural literals always require a suffix. A literal is invalid if its value does not fit the selected schema. For Natx, this check uses pointer-width limits.
Decimal
A decimal natural literal is written as digits. The suffix must immediately follow the digits. Decimal natural literals cannot have leading zeroes except for 0 itself. The following suffix forms are available:
n8: Nat8, from0n8to255n8n16: Nat16, from0n16to65535n16n32: Nat32, from0n32to4294967295n32n64: Nat64, from0n64to18446744073709551615n64nx: Natx, with pointer-width limits
Hexadecimal
A hexadecimal natural literal begins with exactly 0x and may use digits 0 to 9 and uppercase letters A to F. The suffix must immediately follow the hexadecimal digits. The following suffix forms are available:
n8: Nat8, from0x0n8to0xFFn8n16: Nat16, from0x0n16to0xFFFFn16n32: Nat32, from0x0n32to0xFFFFFFFFn32n64: Nat64, from0x0n64to0xFFFFFFFFFFFFFFFFn64nx: Natx, with pointer-width limits
Literal syntax is case-sensitive: use exactly 0x for hexadecimal notation, lowercase suffixes such as n8 and nx, and uppercase hexadecimal letters A to F.
The example below uses only fixed-width forms. Natx is pointer-width, so its exact limits are not shown there.
Example
{} () {} [
"-- decimal examples --" printCompilerMessage
0n8 printStack _:;
65535n16 printStack _:;
4294967295n32 printStack _:;
18446744073709551615n64 printStack _:;
"-- hexadecimal examples --" printCompilerMessage
0xFFn8 printStack _:;
0xFFFFn16 printStack _:;
0xFFFFFFFFn32 printStack _:;
0xFFFFFFFFFFFFFFFFn64 printStack _:;
] "main" exportFunction
Expected Output During Compilation
-- decimal examples --
0n8
65535n16
4294967295n32
18446744073709551615n64
-- hexadecimal examples --
255n8
65535n16
4294967295n32
18446744073709551615n64
Real32 and Real64
The Real32 and Real64 nodes push real objects onto the data stack. Real objects follow the IEEE 754 floating-point standard.
A real literal may begin with -. It then contains decimal digits for the integer part, followed by a fractional part (a decimal point and decimal digits), an exponent, or both in that order. An exponent begins with the lowercase e, followed by an optional + or - and decimal digits; 1E2 is a parse error: "Number has hex digit «E» in decimal integer". If a suffix is present, it must immediately follow the last digit. If no suffix is provided, the literal is Real64. The integer part and exponent cannot have leading zeroes except for 0 itself.
The magnitude of a written exponent is limited to 308 for either sign; a larger exponent is a parse error. Conversion can also reject a lexically valid literal whose value is out of range for its width, with Real number is malformed: 9e308, 3.5e38r32, and 1e-46r32 are invalid, while 1.7976931348623157e308 and 1e-45r32 are valid. See Grammar tokens.
Suffixes
Literal syntax is case-sensitive: use lowercase e for the exponent and lowercase suffixes r32 and r64.
When formatted, Real64 objects are shown without the optional r64 suffix.
Example
{} () {} [
"-- real32 --" printCompilerMessage
5.43r32 printStack _:;
5.43e21r32 printStack _:;
5.43e-21r32 printStack _:;
-5.43r32 printStack _:;
-5.43e21r32 printStack _:;
-5.43e-21r32 printStack _:;
"-- real64 default --" printCompilerMessage
5.43 printStack _:;
5.43e21 printStack _:;
5.43e-21 printStack _:;
-5.43 printStack _:;
-5.43e21 printStack _:;
-5.43e-21 printStack _:;
"-- real64 suffixed --" printCompilerMessage
5.43r64 printStack _:;
5.43e21r64 printStack _:;
5.43e-21r64 printStack _:;
-5.43r64 printStack _:;
-5.43e21r64 printStack _:;
-5.43e-21r64 printStack _:;
] "main" exportFunction
Expected Output During Compilation
-- real32 --
5.43r32
5.43e21r32
5.43e-21r32
-5.43r32
-5.43e21r32
-5.43e-21r32
-- real64 default --
5.43
5.43e21
5.43e-21
-5.43
-5.43e21
-5.43e-21
-- real64 suffixed --
5.43
5.43e21
5.43e-21
-5.43
-5.43e21
-5.43e-21
Text ("TEXT" / «TEXT»)
The Text node pushes a Text object onto the data stack. A Text object is a pair of a size (the count of UTF-8 code units, that is bytes) and an immutable Ref to those code units. Text literals always produce known Text objects. Unknown Text objects have the same schema, but the compiler does not know that pair at compile time. They arise, for example, when text comes from a local or field whose contents are not known at compile time. Reading text through NameRead or NameReadMember may therefore produce a Text object whose size and Ref are not known at compile time.
Literal Forms
| Syntax | Closing Rule | Nesting | Escapes |
|---|---|---|---|
"TEXT" |
Ends at the next unescaped " |
No | Backslash escape sequences |
«TEXT» |
Ends at the matching outermost » |
Yes, by matching unescaped inner « ... » pairs |
Backslash escape sequences |
Both forms may span multiple source lines.
In particular, \« and \» in «TEXT» form allow literal guillemets without affecting nesting.
Escape Sequences
In either form, a backslash (\) introduces an escape sequence. The escape sequence itself does not appear in the final string; instead, it encodes one or more UTF-8 code units (bytes). The predefined escape sequences are listed below.
| Escape | Produced UTF-8 Code Units |
|---|---|
\n |
0A |
\r |
0D |
\" |
22 |
\\ |
5C |
\« |
C2 AB |
\» |
C2 BB |
Alternatively, an escape sequence may continue with one to four pairs of hexadecimal digits that directly encode the UTF-8 code units of one character. Escape syntax is case-sensitive, and the pairs use digits 0 to 9 and uppercase letters A to F. The resulting byte sequence must match one of the following valid UTF-8 patterns:
| Code Unit 1 | Code Unit 2 | Code Unit 3 | Code Unit 4 |
|---|---|---|---|
| 00..7F | |||
| C2..DF | 80..BF | ||
| E0 | A0..BF | 80..BF | |
| E1..EC | 80..BF | 80..BF | |
| ED | 80..9F | 80..BF | |
| EE..EF | 80..BF | 80..BF | |
| F0 | 90..BF | 80..BF | 80..BF |
| F1..F3 | 80..BF | 80..BF | 80..BF |
| F4 | 80..8F | 80..BF | 80..BF |
The expected output below shows the formatter’s canonical representation of Text objects. In particular, both literal forms are printed as quoted text by printStack.
Example
{} () {} [
"-- quoted --" printCompilerMessage
"Hi" printStack _:;
"\«Hi\»" printStack _:;
"\E4BDA0\E5A5BD" printStack _:;
"-- guillemet --" printCompilerMessage
«Hi» printStack _:;
««Hi»» printStack _:;
«\«Hi\»» printStack _:;
"-- equality --" printCompilerMessage
"你好" «你好» = printStack _:;
"\n" «\n» = printStack _:;
] "main" exportFunction
Expected Output During Compilation
-- quoted --
"Hi"
"«Hi»"
"你好"
-- guillemet --
"Hi"
"«Hi»"
"«Hi»"
-- equality --
TRUE
TRUE
Tuple & TupleEnd ((...))
The Tuple node begins a nested scope in which nodes are processed and remaining stack items are collected (see Structs). The matching TupleEnd node ends that scope, constructs the resulting Struct object from those remaining stack items, and pushes it onto the data stack. Inner locals do not remain available by their local names after the scope ends. Tuple constructs can be nested, and each matching ( ... ) pair produces its own Struct object.
TupleEnd collects remaining stack items, not created locals. Locals created between ( and ) do not become part of the result unless they are explicitly left on the data stack.
The collected items become unnamed fields after the scope's output-lifetime checks: a reference to a whole local created inside the Tuple scope is replaced by that local object, moved out of the scope; a reference to part of such a local is invalid. References to outer-scope objects remain references. The resulting fields retain the schemas of the items after this step.
Depending on the collected items, TupleEnd does one of the following:
- Create a List when at least one item was collected and all collected items have the same schema.
- Create a Tuple otherwise. This includes the empty case and any case with differing item schemas.
Creating the resulting object does not call callable items automatically.
The expected output below shows the formatter’s canonical representation. In particular, a homogeneous unknown result is shown as Type Count array rather than reconstructed tuple syntax.
Example
{} () {} [
"-- empty --" printCompilerMessage
() printStack _:;
"-- homogeneous known items --" printCompilerMessage
(1 2 3) printStack _:;
"-- homogeneous unknown items --" printCompilerMessage
(0 dynamic 0 dynamic) printStack _:;
"-- heterogeneous items --" printCompilerMessage
(1 FALSE) printStack _:;
"-- local used inside --" printCompilerMessage
(value: 1; @value 2 +) printStack _:;
"-- nested --" printCompilerMessage
((1 2) (3 FALSE)) printStack _:;
] "main" exportFunction
Expected Output During Compilation
-- empty --
()
-- homogeneous known items --
(1 2 3)
-- homogeneous unknown items --
Int32 2 array
-- heterogeneous items --
(1 FALSE)
-- local used inside --
(3)
-- nested --
((1 2) (3 FALSE))
Label & Variable (NAME: ...;)
The Label node pushes a name onto the name stack (see Data stack and name stack). The Variable node pops a name from that stack and one source object from the data stack, then creates a local in the current scope. The created local becomes available for name lookup in that scope and nested inner scopes until the scope ends.
If several labels are pending, Variable uses the topmost one first. Starting from an empty data stack, first: second: 1 2; ; is valid: second consumes 2, then first consumes 1.
The builtins overload, private, and virtual modify the next local created by ; or by the builtin def:
overloadmakes the created name participate in overload resolution.privatemakes the created name non-public.virtualmakes the created local static. A static local must be packable and known at compile time.
After a successful local creation, those specifier effects are cleared.
; is invalid if the name stack is empty or if no source object is available on the data stack. At scope end, any unused labels or unused specifiers are also invalid.
Example
{} () {} [
1
2
first:
second:
third: 3;
;
;
virtual staticValue: 4;
"-- first --" printCompilerMessage
first printStack _:;
"-- second --" printCompilerMessage
second printStack _:;
"-- third --" printCompilerMessage
third printStack _:;
"-- static --" printCompilerMessage
staticValue printStack _:;
] "main" exportFunction
Expected Output During Compilation
-- first --
1 Cref
-- second --
2 Cref
-- third --
3 Cref
-- static --
4