XXIIVV

Uxntal Syntax

In concatenative programming, there are no precedence rules, the calculations are merely performed in the sequence in which they are presented. The order with which elements come off a stack is known as last in, first out. In the stack a b c, the c item was the last to be added, and will be the first to be removed.

#01 DUP ADD #03 MUL ( result: 06 )

Comments are within parentheses, opcodes are uppercased, hexadecimal numbers are lowercased. The # prefix indicates a literal number be pushed onto the stack.

Example Program

The first line of our example begins with the padding token |10, followed by definitions for @labels, &sublabels and $length that aligns with the various ports of the Console device so that we can reference it by name in our program. To learn more, see Uxntal Structs.

The following program is not the most efficient way of printing a string, merely a length of code that covers most basic functionalities of the language.

The second segment moves the program location to the address 0x0100, which is where the first page of memory ends, and where all Uxn programs begin. Next, inside parentheses, is a comment with the arrow symbol indicating that the following operation is a vector. To learn more, see Uxntal Notation.

@on-reset
0100:   a0 01 12   LIT2 my-string
0103:   60 00 01   JSI print-text
0106:   00         BRK

The ;my-string token pushes an absolute address, made of two bytes, pointing to a series of letters in memory. Values pushed to the stack in this fashion are called a literals, as opposed to values stored in memory which are called immediates. Next, we call the @print-text routine.

Both &while and @while are ways to define labels, but using the ampersand rune will prefix our new label with the name of the last parent label, creating @print-text/while. To learn more, see Label Scope.

@print-text/while
0107:   94         LDAk
0108:   80 18      LIT 18
010a:   17         DEO
010b:   21         INC2
010c:   94         LDAk
010d:   20 ff f7   JCI print-text/while
0110:   22         POP2
0111:   6c         JMP2r

Next, the LDAk opcode loads the byte in memory found at that address; the ASCII letter H, to the top of the stack. The k-mode indicates that the operation will not consume the address. That value is sent to the device port #18, defined by our Console label and its sublabels, with DEO which prints that character to the terminal. To learn more, see Uxntal Devices.

We then increment the absolute address found on top of the stack with INC2, we use the 2-mode because the address is made of two bytes. We load the byte at the incremented value and do a conditional immediate jump with ?&while for as long as the item on the stack is not zero. We use POP2 to remove the address on the stack and keep the stack clean at the end of the subroutine.

Lastly, we encounter JMP2r which jumps to the absolute address that we left on the return stack when we entered the @print-text subroutine.

To summarize, comments are within parentheses, numbers are lowercase hexdecimal shorts or bytes, opcodes are uppercased reserved words, and anonymous labels are within curlies. Runes are special characters at the start of a word that define its meaning, here is the full list:

Padding RunesNumber Rune
|absolute $relative #literal number
Label RunesAscii Runes
@parent &child "raw ascii
Addressing RunesWrapping Runes
,literal relative _raw relative ( )comment
.literal zero-page -raw zero-page { }lambda
;literal absolute =raw absolute [ ]ignored
Immediate RunesPre-processor Runes
!jmi ?jci %macro ~include

Uxntal Notation

The Uxntal notation follows that of the Forth programming language, where each item on the left of the -- spacer is the state of the stack before, and to the right, the state of the stack after:

@routine ( a b -- a b c )
	ADDk JMP2r

By default, single items are a byte long, and shorts are indicated with a * suffix, the order in which they appear is the order of the stack with the top item to the right:

@routine ( a b* -- b* a )
	ROT JMP2r

If a routine is a vector, it uses the arrow notation.

@on-event ( -> )
	BRK

If a specific item on the stack is a pointer, or a -1 value for failure, the item is within angular brackets.

@routine ( name* -- <res>* )
	lut/find-name ORAk ?{ POP2 #ffff } JMP2r

Uxntal Numbers

Uxntal uses only lowercase unsigned hexadecimal numbers of either 2 or 4 characters in length. There are two types of numbers:

#12 #34 LIT2 5678 ADD2 68 ac

Uxntal Macros

A macro is a way of defining inline routines, it allows to create new words that will be replaced by the body of the macro, as opposed to a jump where the program counter will move to a routine and back, therefore it needs to be defined before its usage, as follow:

%modulo ( num denum -- res ) {
	DIVk MUL SUB }

@routine ( -- c* )
	#18 #03 modulo JMP2r

In the previous example, the token modulo will get replaced by the body of the macro during assembly:

@routine ( -- c* )
	#18 #03 DIVk MUL SUB JMP2r

Note: A macro does not have a scope, so it may not contain sublabels if the macro is to be used multiple time within a single parent label, lambdas are immune to this limitation.

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.

Enums are labels with unique values that can be used as constants in a program, they begin by rolling back the program address with |00:

|00 @Suit &clubs $1 &diamonds $1 &hearts $1 &spades

@is-diamond ( suit -- f )
	.Suit/clubs EQU
	JMP2r

Structs define padded labels, for example the ;person/age label holds a value of 2, using that offset allows to access specific members of a data structure by applying the sublabels to a pointer:

|00 @Person &name $2 &age $1 &height $2
@members
	=dict/melanye 2a 008c
	=dict/alexane 2c 009a

@get-height ( member* -- height* )
	;Person/height ADD2 LDA2
	JMP2r

Constants are labels that hold a specific value through the entire execution of the program. They allow to create label that can be used in place of a number, making the code more readable.

|1400 @limit

@within-limit ( value* -- f )
	;limit LTH2
	JMP2r

Pro Tip: 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

Labels Scope

Uxntal objects are defined statically using a @label token, it allows for the enclosed methods to access local &labels. The example below creates an object with the method get-x, accessible from outside the scope as Object/get-x. By capitalizing the object name, we communicate to the assembler that the label will not be called and should not raise a warning.

@Object &x $1 &y $1

&get-x ( -- x )
	,&x LDR
	JMP2r

New methods and members can be appended to an existing scope 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 permissions for accessing local labels as during the object definition. To learn more, see symbols.

@Object/get-y ( -- y )
	,&y LDR
	JMP2r

When calling local routines the scope's name can be omitted. To see a complete example in that pseudo object-oriented style, see timer.tal.

&get-both ( -- x y )
	Object/get-x 
	!/get-y

Anonymous Labels

In the context of Uxntal, lambdas is a label designated by 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 ;{.

Counted Strings

Similarly to counted strings, lambdas can encode strings in memory by preceeding their content by the address of the end of the string, so the reading of that string data is not looking for a null byte, but running until reaching the bounds. The advantage is that the address of the next character to append is readily available.

@on-reset ( -> )
    ;cstr print-counted
    BRK

@cstr ={ "foo 20 "bar }

@print-counted ( cstr* -- )
    LDA2k SWP2 INC2 INC2
    &l ( -- )
        LDAk #18 DEO
        INC2 GTH2k ?&l
    POP2 POP2 JMP2r

Data Structures

We can inline a list of items, here's an implementation a function that returns the member in a list, or nil. Notice how the lambda jump requires the list address to be moved from the return stack.

{ =cat =dog =bat } STH2r ;rat member?
@member? ( {items}* target* -- res* )
	,&t STR2
	DUP2k #0002 SUB2 LDA2 ADD2 SWP2
	&l ( -- )
		LDA2k [ LIT2 &t $2 ] EQU2 ?&found
		INC2 INC2 GTH2k ?&l
	POP2 ;nil &found NIP2
	JMP2r

Unless Blocks

It is important to notice that a in the case of a conditional jump, the lambda's content is jumped over when the flag byte is true.

.button LDZ ?{ skipped-on-button }

Lambdas can also be nested into one another, only the outermost layer of a nested lambda is evaluated at a time:

#01 { { "foo $1 } STH2r !print-lambda } STH2r JCN2

incoming uxn