Action at a distance is an anti-pattern in computer science in which behavior in one part of a program modifies operations in another part of the program. This anti-pattern should be avoided whenever possible, but if wielded carefully SMC can become a practical ally when writing Uxntal.
Uxntal Labels
A label is a non-numeric, non-opcode, and non-runic symbol that correspond to a number between 0 and 65536. A label name is made of two parts, a scope and a sublabel. Sublabels can be added to a scope with the &name rune, or by writing the full name, like @scope/name. Note that a labels like bed, add and cafe are considered numeric.
Functions are simply labels that will be jumped to, and returned from.
@func ( a b -- c ) &loop INC GTHk ?&loop ADD JMP2r
Constants are labels that hold a specific value through the entire execution of the program. They allow to assign a name to a number, making the code more readable.
|1400 @text/sz |2000 @text/buf $&sz
Enums are labels with padded members of equal sizes that can be used as constants in a program, they typically begin by rolling back the program address with |00:
|00 @Suit &clubs $1 &diamonds $1 &hearts $1 &spades
Structs are labels with padded members of different sizes, that maps on a data-structure, they typically space the different members with $1:
|00 @Person &name $2 &age $1 &height $2
Labels can also be used with the padding runes to define a global length. For example, if one needs to specify a length of 0x30 for multiple members of a struct, a value can be specified as follow:
|30 @length |00 @Struct &field $length
Scope
Uxntal objects are defined statically and allow for the enclosed methods to access encapsulated local &members. The example below contains an object with the method set-color, accessible from outside the scope as pen/set-color.
@pen &position &x $2 &y $2 &color $1 &set-color ( color -- ) ,/color STR JMP2r
New methods and members can extend an existing scope from anywhere by creating a label with the scope name followed by a slash and the name of the extension. The &labels declared within the extension have the same access to local labels as the rest of the object.
@pen/get-position ( -- x* y* ) ,/x LDR2 ,/y LDR2 JMP2r
When calling local methods the scope's name can be omitted, starting at the slash, like /method:
@pen/paint ( -- ) /get-position canvas/draw-line-to JMP2r
The notion of an interface is what truly characterizes objects - not classes, not inheritance, not mutable state.Gilad Bracha
Addressing
A labels is a way of assigning a name to a number. There are six ways to get
the number corresponding to that label. Literal addressing prefixes the label
with a LIT for Relative and Zero-Page addressing, and
LIT2 for absolute addressing.
- Literal Relative, like ,label, pushes a relative distance byte to the label.
- Literal Zero-Page, like .label, pushes an absolute address byte to the label.
- Literal Absolute, like ;label, pushes an absolute address short to the label.
- Raw Relative, like _label, writes a relative distance byte to the label.
- Raw Zero-Page, like -label, writes an absolute address byte to the label.
- Raw Absolute, like =label, writes an absolute address short to the label.
Raw addressing is used for building data-structures and more advanced programs. A relatively common usage of raw runes is to create literals directly into the return stack:
[ LIT2r 08 -label ] LDZr ADDr | [.label]+8
Anonymous Labels
Anonymous labels are designated with a curly bracket that points to its associated closing bracket, and can be nested. Under the hood, the opening bracket assembles to the address of the closing bracket which allows the destination address to be used like any other label such as a JCI ?{, a JMI, !{ or a plain literal ;{. Here are some example data-structures:
@counted-string
_{ "foo 20 "bar }
@linked-list
={ ={ "A } ={ "B ={ "C } } }
Unless Blocks
It is important to notice that in the case of a conditional jump, the lambda's content is jumped over when the flag byte is true.
[ LIT2 &last $1 -Mouse/state ] DEI DUP ,&last STR
DUP2 #0001 NEQ2 ?{ ( on down ) }
DUP2 #0101 NEQ2 ?{ ( on drag ) }
DUP2 #0100 NEQ2 ?{ ( on release ) }
POP2
The opening curly bracket assembles to a unique label reference, and the closing bracket to a corresponding matching label definition. They do not affect the scope.
Uxntal Doors
The ability to treat instructions as data makes programs that write programs possible. Self-modifying code(SMC) is generally considered harmful, and is therefore not permitted in most modern computer architectures today.
A door is an allocation of local memory that can store state across vectors.
@routine ( -- i ) [ LIT &door $1 ] INCk ,&door STR JMP2r
Caching Doors
In most cases, SMC is used to cache data that would otherwise be difficult or slow to retrieve, like when writing a responsive application that would make frequent requests to a device.
In the following door, we are comparing the state of the mouse device between vector events, we could store the previous state in a zero-page variable, but keeping the value locally allows to reserve a byte from within the context where it is needed, and is faster by being inlined.
@on-mouse ( -> ) [ LIT2 &last $1 -Mouse/state ] DEI DUP ,&last STR EORk ?&changed POP2 BRK
Callback Doors
To chain operations across vectors, one might try passing the next operation pointer on the stack, but since we cannot be certain which vector will happen next, we can't expect a specific stack state between events. A safer way is to write the next operation directly into a door where it will be needed, ideally preserving the label scope.
@set-animation ( callback* -- ) ,&callback STR2 ;&run .Screen/vector DEO2 JMP2r &run ( -> ) [ LIT &time f0 ] INCk ,&time STR #00 EQU ?&done try-redraw BRK &done ( -> ) [ LIT2 &callback $2 ] JSR2 BRK
Depth-Punching Doors
Routines should try and avoid accessing stack values that are further than 2 or 3 shorts deep on either stacks, but sometimes it cannot be helped. In the following example, we want to run a function over each value of a 2d array. Instead of juggling the stacks on each iteration to bring out the function pointer, it is often more efficient to write the function pointer across the nested loop.
@each-pixel ( fn* -- ) ,&fn STR2 #1000 &h STHk #2000 &x DUP STHkr [ LIT2 &fn $2 ] JSR2 INC GTHk ?&x POP2 POPr INC GTHk ?&h POP2 JMP2r
incoming: uxntal uxntal strings uxntal macros 2025 2024 2023