A programming language for the Uxn virtual processor.

Uxn programs are written in a concatenative flavor of assembly designed especially to map to the idiosyncrasies of this strange little computer.

Introduction

Firstly, 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 the 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 program
 01  01  02  03  06 stack
     01      02

Uxntal numbers are expressed in hexadecimal.

The following example program prints the phrase "Hello World!" by pushing the address to a label on the stack, and iterating through each letter of the string found at that address. A loop increments the pointer until it reaches end of the text, at which point, the stack is emptied and the evaluation ends.

A word starting with @ defines a label, and one starting with ; pushes the absolute address of a label to the stack. With that in mind, ;text pushes the two bytes equal to the address of the @text label to the stack. In the interpreter above, press "step" to walk through each step of the evaluation.

Next, we define a new label named @while, to mark the start of the loop that will print each character stored at the text label.

The LDAk opcode loads a byte at the address currently at the top of the stack, in this case, the ascii letter H(48). The k-mode indicates that the operator will not consume the address.

The DUP opcode makes a copy of the letter. The ?{ pops that copy from the stack, and if it is not zero, we jump to the corresponding }, which is an anonymous label(λ00).

( Disassembly of the example above:
|addr   bytecode   Uxntal
-----   --------   ------- )
|0100   a0 01 12   ( ;text )

@while
|0103   94         ( LDAk )
|0104   06         ( DUP )
|0105   20 00 03   ( ?λ00 )
|0108   02         ( POP )
|0109   22         ( POP2 )
|010a   00         ( BRK )

The #18 word pushes a number to the stack, which maps to the Console/write port(#18), followed by the DEO opcode that pops both bytes(the letter and the port) and sends the letter to that device port, telling the Console to print it, leaving only the address on top of the stack.

The INC2 opcode increments the address, moving the text pointer to the next letter. The 2-mode is used because address addresses are always made of two bytes.

@λ00
|010b   80 18      ( #18 )
|010d   17         ( DEO )
|010e   21         ( INC2 )
|010f   40 ff f1   ( !while )

Finally, with !while we jump back to the @while label, and repeat the loop until there are no more letters to load. When that happens, we POP to remove the duplicated letter, and POP2 to remove the address on the stack to keep the stack clean at the end of the evaluation.

@text
|0112   48 65 6c   ( H e l )
|0115   6c 6f 20   ( l o   )
|0118   57 6f 72   ( W o r )
|011b   6c 64 21   ( l d ! )

Summary

Comments are within parentheses,numbers are lowercase hexadecimal shorts or bytes, and opcodes are uppercase reserved words with lowercase modes. Addressing is done by one of six runes. Valid label and macro names are unique non-numeric, non-opcode and non-runic.

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

Uxntal Stacks

All programming in Uxntal is done by manipulating the working stack, and return stack, each stack contains 256 bytes. Here are some stack primitives assuming the initial state of the stack is a b c where c is the top of the stack:

POPa bDiscard top item.
NIPa cDiscard second item.
SWPa c bMove second item to top.
ROTb c aMove third item to top.
DUPa b c cCopy top item.
OVRa b c bCopy second item to top.

A byte is a number between 0-255(256 values), a short is a number between 0-65535(65536 values) made of two bytes, each byte in a short can be manipulated individually:

#0a #0b POP 0a
#12 #3456 NIP 12 56
#1234 DUP 12 34 34

The two stacks are circular, to pop an empty stack does not trigger an error, but merely means to set the stack pointer to 255. There are no invalid programs, any sequence of bytes is a potential Uxn program. Values are moved between stacks with the STH opcode.

WST 00 00 00 00 00 00|12 34 <02
RST 00 00 00 00 00 00 00|56 <01

The program above contains 12 and 34 on the working stack, and 56 on the return stack. The stack content can always be printed by sending a non-null byte to the System/debug port.

Uxntal Notation

The stack-effect 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. On each side, the right-most item is the last to the pushed and the first to be removed:

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

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

The dot notation is used to indicate that stack-effects to the right of the dot are happening on the return stack:

@routine ( a . b -- c )
	STHr ADD JMP2r

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

@on-event ( -> )
	BRK

This notation also holds for macros as well, the notation goes before the macro's body:

%macro ( a b -- res ) {
	DIVk MUL SUB }

The stack notation is merely present to help readability but can be altogether disregarded without impacting the program.

Comments

A comment starts with any token beginning with opened parenthesis, and ends at its corresponding closed parenthesis. Comments may be nested, the enclosed comments parentheses must be whitespace separated on both sides.

( ( nested ) )
( 1+2*(4/3) )

Outermost comments may be named, which means that sometimes the open parenthesis is immediately followed by a word holding some meaning to external tools.

(doc
	This is a docstring. )

Special comments are sometimes used to group routines together, they are similar to the pragma mark notation:

(
@|Group )

Brackets

The square brackets do nothing, they are used as an aid for readability and formatting, they are useful for making explicit certain things like grouping behaviors, joining literals or indicating lookup tables.

@routine ( -- )
	[ LIT2 20 -Console/write ] DEO JMP2r

%min ( a b -- r ) {
	GTHk [ JMP SWP ] POP }

@sprite [ 00 66 ff ff ff 7e 3c 18 ]

Uxntal Numbers

Uxntal uses only lowercase unsigned hexadecimal numbers. There are different types of numbers:

#12 #34 LIT2 5678 ADD2 68 ac

Using and operating on negative numbers in Uxntal.

Uxn doesn't have built-in support for negative integers. However, you can emulate signed numbers by treating some unsigned values as negative. For example, treating unsigned bytes as signed results in the following:

hex 000102 7e7f808182 fdfeff
unsigned 012 126127128129130 253254255
signed 012 126127-128-127-126 -3-2-1

The first 128 integers (0-127) are represented the same as unsigned and signed, but the latter 128 are different. The basic idea here is that for values greater than #7f (127) we subtract 256 to get their signed value:

signed = n < 128 ? n : n - 256

It turns out that many unsigned operations "work" even when treating the values as signed. (In other words, you get the same result as you would have using a language with signed integer types.) The following arithmetic instructions work correctly with "signed" values:

#13 #ff ADD returns #12
#02 #03 SUB returns #ff
#02 #ff MUL returns #fe

Be careful! The smallest negative value (-128 for bytes, -32768 for shorts) has no corresponding positive value. This means that some operations will not work as expected:

#80 #ff MUL returns #80 (-128 * -1 = -128)
#00 #80 SUB returns #80 (0 - (-128) = -128)

Also, negative and positive values will "wrap around" in the usual way when dealing with two's-complement representations:

#7f #01 ADD returns #80 (127 + 1 = -128)
#80 #01 SUB returns #7f (-128 - 1 = 127)
#80 #80 ADD returns #00 (-128 + (-128) = 0)

Other instructions will not handle "negative" integers correctly. These routines will safely compare "signed" bytes:

@signed-lth ( x y -- res )
	DUP2 #8080 AND2 EQU ?&diff LTH JMP2r &diff LTH #00 NEQ JMP2r

@signed-gth ( x y -- res )
	DUP2 #8080 AND2 EQU ?&diff GTH JMP2r &diff GTH #00 NEQ JMP2r

Similarly, division will not correctly handle signed values. The simplest way to handle this is to make both values non-negative, do unsigned division (i.e. DIV) and then set the correct sign at the end.

@abs ( x -- abs-x sign )
	DUP #7f GTH #fe MUL INC STHk MUL STHr JMP2r

@signed-div ( x y -- x/y )
	abs STH SWP abs STH SWP DIV MULr STHr MUL JMP2r

The unsigned shift operator treats the sign bit like any other. This means shifting left will lose the sign bit (reversing the sign) and that shifting right will convert the sign bit into a value bit. Signed numbers will also need their own routines for decimal input and output, if those are required by your program.

@signed-print ( num -- )
	( - ) DUP #80 LTH ?{ LIT "- #18 DEO #7f AND #80 SWP SUB }
	( 100 ) DUP #64 DIV signed-print/emit
	( 10 ) DUP #0a DIV signed-print/base
	&base ( digit -- ) #0a DIVk MUL SUB
	&emit ( digit -- ) LIT "0 ADD #18 DEO JMP2r

If you need a sign-aware shift you'll likely want to convert negatives to positive values, perform a shift, and then restore the sign. Keep in mind that -128 cannot be converted to a positive value, and may require special treatment.

Uxntal Opcodes

Uxn has 32 standard opcodes and 4 immediate opcodes. In the table below, the pipe(|) character indicates an effect on the return stack, the pc is the program counter, a value8 indicates a byte length, a value* indicates a short length, an unspecified length follows the short mode and a [value] is read from memory.

Stack I             Logic              Memory I            Arithmetic
BRK --              EQU a b -- a=b     LDZ abs8 -- [abs8]  ADD a b -- a+b
INC a -- a+1        NEQ a b -- a≠b     STZ val abs8 --     SUB a b -- a-b
POP a --            GTH a b -- a>b     LDR rel8 -- [rel8]  MUL a b -- a×b
NIP a b -- b        LTH a b -- a<b     STR val rel8 --     DIV a b -- a÷b

Stack II            Stash              Memory II           Bitwise
SWP a b -- b a      JMP addr --        LDA abs* -- [abs*]  AND a b -- a&b
ROT a b c -- b c a  JCN cond8 addr --  STA val abs* --     ORA a b -- a|b
DUP a -- a a        JSR addr -- | pc*  DEI dev -- [dev]    EOR a b -- a^b
OVR a b -- a b a    STH a -- | a       DEO val dev --      SFT a sft8 -- res

LIT -- [pc*]        JCI cond8 --       JMI --              JSI -- | pc*

Modes

An opcode is any name in which the 3 first characters are found in the opcode table, followed by any combination of 2, k and r. Each opcode has 3 possible modes, which can combined:

INC2r
kr2opcode
011 00001

By default, operators consume bytes from the working stack, notice how in the following example only the last two bytes #45 and #67 are added, even if there are two shorts on the stack.

#1234 #4567 ADD12 34 ac

The short mode consumes two bytes from the stack. In the case of jump opcodes, the short-mode operation jumps to an absolute address in memory. For the memory accessing opcodes, the short mode operation indicates the size of the data to read and write.

#1234 #4567 ADD2 57 9b

The keep mode does not consume items from the stack, and pushes the result on top. Every opcode begins by popping values from the stack before operating on them. This mode keeps a copy of the stack pointer to recover after the popping stage.

#1234 #4567 ADD2k 12 34 45 67 57 9b

The return mode swaps the stacks on which an opcode operates. Under this mode, a return address will be pushed to the working stack, and stashing will take from the return stack. For that reason, there is no return opcode. For example, the JSR opcode pushes the return address onto the return stack, and JMP2r jumps to that address.

LITr 12 #34 STH ADDr STHr 46

Immediate opcodes

Immediate opcodes are operators that do not take items from the stack, but read values stored immediately after the opcode in the program's memory. Uxntal has 4 immediate opcodes:

The immediate jump opcodes are slightly faster than their standard opcode counterparts, but do not have modes and cannot be used to do pointer arithmetic. The address value of the immediate opcodes are stored in memory as relative shorts, enabling routines making use of these opcodes to be moved around in the program's memory.

@on-reset ( -> )
	#0007 fac-rec BRK

@fac-rec ( n* -- res* )
	#0001 GTH2k ?{ NIP2 JMP2r }
	OVR2 SWP2 SUB2 fac-rec MUL2 JMP2r

To learn more about each opcode, see the Opcode Reference.

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.

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 Strings

Uxntal has superficial support to convert raw ascii characters into bytes. The string token begins with the double-quote character ", there is no escaping of special characters. Here are a few string types that can be created using anonymous labels:

@null-string "Hello 20 "world. 00
@counted-string _{ "ordinator }
@linked-string ={ "turnip }

This token type can also be used to inline characters, for example, the following example will push the hexadecimal value 0x61 on the stack before printing the letter "b".

LIT "a INC .Console/write DEO

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

A macro may contain nested anonymous labels:

%square-quoted ( a -- res ) {
	{ DUP MUL JMP2r } STH2r }

Macros can contain labels but remember that labels and sublabels must be unique, so the macro may not be used in the same scope. It is a common mistake to use macros to encode constants, instead consider using labels.

Uxntal Memory

There are 64kb of addressable memory. Roms are always loaded at 0x0100, which is the address of the Reset Vector and where evaluation begins. During boot, the stacks, device and addressable memories are zeroed. During a soft-reboot, the content of the zero-page is preserved.

SharedMemoryRAMData64kb pages
PrivateStacksWorking StackData256 bytes
Pointer1 byte
Return StackData256 bytes
Pointer1 byte
IODevicesData256 bytes

The device page and stacks are located outside of addressable memory.

|18 @width

|100 @on-reset ( -> )
	;buffer/end BRK 02 18

|200 @buffer $width &end

Memory is big-endian, when writing or reading a short from memory, the position is that of the high-byte. The low-byte of a short written at 0xffff wraps to 0x0000.

#12 #0200 STA 0x0200=12
#3456 #0400 STA2 0x0400=34, 0x0401=56
#0400 LDA 34

The zero-page is the memory located below 0x0100, its purpose is to store variables that will be accessed often, or needs to be preserved across a soft-reboot. It is sligthly faster to read and write from the zero-page using the LDZ and STZ opcodes as they use only a single byte instead of a short. This memory space cannot be pre-filled in the rom prior to initialization. The low-byte of a short written at 0xff wraps to 0x00.

#1234 #80 STZ2 0x0080=12, 0x0081=34
#81 LDZ 34

Uxntal Devices

Uxn is non-interruptible, vectors are locations in programs that are evaluated when certain events occur. A vector is evaluated until a BRK opcode is encountered. Uxn can communicate with a maximum of 16 devices, each device has 16 ports, each port handles a specific I/O message. Ports are mapped to the devices memory page, which is located outside of the main addressable memory.

All programs begin by executing the reset vector located at 0x100. The content of the stacks are preserved between vectors, but it is discouraged to use the stacks to pass data between vectors.

@on-reset ( -> )
	( set vector )
	;on-mouse .Mouse/vector DEO2
	BRK

@on-mouse ( -> )
	( read state )
	.Mouse/state DEI ?&on-touch
	BRK

&on-touch ( -> )
	( A mouse button is pressed )
	BRK

For example, the address stored in the Mouse/vector ports points to a part of the program to be evaluated when the cursor is moved, or a button state has changed.

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.

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.

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

Uxntal Software

Here's a list of small self-hosted development tools:

A collection of commonly used routines in Uxntal projects.

The following snippets are in the standard format. If you discover faster and smaller helpers, please get in touch with me.

Hexadecimal Numbers

To print an hexadecimal number:

@<phex> ( short* -: )
	SWP /b
	&b ( byte -: )
		DUP #04 SFT /c
	&c ( byte -: )
		#0f AND DUP #09 GTH #27 MUL ADD [ LIT "0 ] ADD #18 DEO
		JMP2r

To convert an hexadecimal string to a value:

@shex ( str* -: val* )
	[ LIT2r 0000 ]
	&w ( str* `acc* -: val* )
	LDAk chex INC #00 EQU ?{
		[ LITr 40 ] SFT2r LDAk chex [ LITr 00 ] STH
		ADD2r INC2 LDAk ?&w }
	POP2 STH2r JMP2r

To convert an hexadecimal character to a nibble:

@chex ( c -: val! )
	( dec ) [ LIT "0 ] SUB DUP #09 GTH ?{ JMP2r }
	( hex ) #27 SUB DUP #0f GTH ?{ JMP2r }
	( err ) POP #ff JMP2r

Decimal Numbers

To print a decimal short to decimal:

@pdec ( short* -- )
	#000a SWP2 [ LITr ff ]
	&>get ( -- )
		SWP2k DIV2k MUL2 SUB2 STH
		POP OVR2 DIV2 ORAk ?&>get
	POP2 POP2
	&>put ( -- )
		STHr INCk ?{ POP JMP2r }
		[ LIT "0 ] ADD #18 DEO !&>put

To print a decimal byte to decimal:

@print-dec ( dec -- )
	DUP #64 DIV print-num/try
	DUP #0a DIV print-num/try
	( >> )

@print-num ( num -- )
	#0a DIVk MUL SUB [ LIT "0 ] ADD #18 DEO
	JMP2r
	&try ( num -- )
		DUP ?print-num
		POP JMP2r

To convert a decimal string to a hexadecimal value.

@sdec ( str* -- val* )
	[ LIT2r 0000 ]
	&w ( -- )
		( validate ) LDAk [ LIT "0 ] SUB #09 GTH ?&end
		( accumulate ) [ LIT2r 000a ] MUL2r
		( combine ) LDAk [ LIT "0 ] SUB [ LITr 00 ] STH ADD2r
		( continue ) INC2 LDAk ?&w
	&end POP2 STH2r JMP2r

Strings

To print a string.

@<pstr> ( str* -: )
	LDAk #18 DEO
	INC2 & LDAk ?<pstr>
	POP2 JMP2r

Memory

To print an entire page of memory:

@pmem ( addr* -- )
	#0000
	&l ( -- )
		ADD2k LDA phex/b
		DUP #0f AND #0f NEQ #16 MUL #0a ADD #18 DEO
		INC NEQk ?&l
	POP2 POP2 JMP2r

Helpers for bitwise operations.

@popcount ( byte -- count ) LITr 00 #00 &w SFTk #01 AND STH ADDr INC SFTk ?&w POP2 STHr JMP2r
@popcnt ( v* -- num ) LITr 00 &>w #01 ANDk STH ADDr SFT2 ORAk ?&>w POP2 STHr JMP2r

Dates

To find the day of the week from a given date, Tomohiko Sakamoto's method:

@dotw ( y* m d -- dotw )
	( y -= m < 3; )
	OVR STH SWP2 #00 STHr #02 LTH SUB2
	STH2
	( t[m-1] + d )
	#00 ROT ;&t ADD2 LDA #00 SWP
	ROT #00 SWP ADD2
	( y + y/4 - y/100 + y/400 )
	STH2kr
	STH2kr #02 SFT2 ADD2
	STH2kr #0064 DIV2 SUB2
	STH2r #0190 DIV2 ADD2
	ADD2
	( % 7 )
	#0007 DIV2k MUL2 SUB2 NIP
	JMP2r
		&t [ 00 03 02 05 00 03 05 01 04 06 02 04 ]

To find if a year is a leap year:

@is-leap-year ( year* -- bool )
	( leap year if perfectly divisible by 400 )
	DUP2 #0190 ( MOD2 ) DIV2k MUL2 SUB2 #0000 EQU2 ?&leap
	( not a leap year if divisible by 100 )
	( but not divisible by 400 )
	DUP2 #0064 ( MOD2 ) DIV2k MUL2 SUB2 #0000 EQU2 ?¬-leap
	( leap year if not divisible by 100 )
	( but divisible by 4 )
	DUP2 #0003 AND2 #0000 EQU2 ?&leap
	( all other years are not leap years )
	¬-leap
	POP2 #00
	JMP2r
		&leap POP2 #01 JMP2r

Memory

@msfl ( b* a* len* -- )
	STH2
	SWP2 EQU2k ?&end
	&l ( -- )
		DUP2k STH2kr ADD2 LDA ROT ROT STA
		INC2 GTH2k ?&l
	POP2 POP2 &end POP2r JMP2r

@msfr ( b* a* len* -- )
	STH2
	EQU2k ?&end
	&l ( -- )
		DUP2 LDAk ROT ROT STH2kr ADD2 STA
		#0001 SUB2 LTH2k ?&l
	POP2 POP2 &end POP2r JMP2r

Random

@prng-init ( -- )
	[ LIT2 00 -DateTime/second ] DEI
		[ LIT2 00 -DateTime/minute ] DEI #60 SFT2 EOR2
		[ LIT2 00 -DateTime/hour ] DEI #c0 SFT2 EOR2 ,prng/x STR2
	[ LIT2 00 -DateTime/hour ] DEI #04 SFT2
		[ LIT2 00 -DateTime/day ] DEI #10 SFT2 EOR2
		[ LIT2 00 -DateTime/month ] DEI #60 SFT2 EOR2
		.DateTime/year DEI2 #a0 SFT2 EOR2 ,prng/y STR2
	JMP2r

@prng ( -- number* )
	[ LIT2 &x $2 ]
		DUP2 #50 SFT2 EOR2
		DUP2 #03 SFT2 EOR2
	[ LIT2 &y $2 ] DUP2 ,&x STR2
		DUP2 #01 SFT2 EOR2 EOR2
		,&y STR2k POP
	JMP2r

Misc

To convert a signed byte to a signed short.

DUP #7f GTH #ff MUL SWP
@smax ( x* y* -> smax* ) EOR2k POP #80 AND ?min !max
@min ( x* y* -> min* ) LTH2k JMP SWP2 POP2 JMP2r
@max ( x* y* -> max* ) LTH2k JMP SWP2 NIP2 JMP2r
@mod ( x y -- z ) DIVk MUL SUB JMP2r
@mod2 ( x* y* -- z* ) DIV2k MUL2 SUB2 JMP2r

( Signed macros )

@abs ( a -- b ) DUP #80 LTH ?{ #00 SWP SUB } JMP2r
@abs2 ( a* -- b* ) DUP2k #1f SFT2 MUL2 SUB2 JMP2r
@lts2 ( a* b* -- f ) #8000 STH2k ADD2 SWP2 STH2r ADD2 GTH2 JMP2r
@gts2 ( a* b* -- f ) #8000 STH2k ADD2 SWP2 STH2r ADD2 LTH2 JMP2r

( Binary macros )

@rol ( x y -- z ) DUP #07 SFT SWP #10 SFT ADD JMP2r
@ror ( x y -- z ) DUP #70 SFT SWP #01 SFT ADD JMP2r
@rol2 ( x* y* -- z* ) DUP2 #0f SFT2 SWP2 #10 SFT2 ADD2 JMP2r
@ror2 ( x* y* -- z* ) DUP2 #f0 SFT2 SWP2 #01 SFT2 ADD2 JMP2r

This self-replicating program will emit its own bytecode when run:

@q ( -> )
	;&end ;q &l LDAk #18 DEO INC2 GTH2k ?&l &end
uxnasm src.tal seed.rom && uxncli seed.rom > res.rom

This cyclical self-replicating program will emit a program that prints ying and which emits a program like itself that will print yang, which in turn will emit a program that prints ying again, and so forth:

@y ( -> )
	[ LIT2 "y 19 ] DEO [ LIT2 &c "ai ] SWPk ,&c STR2 #19 DEO
	[ LIT2 "n 19 ] DEO [ LIT2 "g 19 ] DEO
	;&end ;y &w LDAk #18 DEO INC2 GTH2k ?&w &end
uxnasm yingyang.tal ying.rom && uxncli ying.rom > yang.rom

This quine program will emit a second program that emits its own bytecode as hexadecimal ascii characters, which is also a valid program source:

a001 32a0 0100 b460 000b a020 1817 2121
aa20 fff2 0004 6000 0006 8004 1f60 0000
800f 1c06 8009 0a80 271a 1880 3018 8018
176c
uxnasm src.tal seed.rom && uxncli seed.rom > src.tal

This ambigram program executes the same backward or forward, every single opcode is evaluated, and prints the palindrome "tenet". It is Devine's entry to BGGP1:

1702 a018 a002 a074 a002 0417 1702 a018
a002 a065 a002 0417 1702 a018 a002 a06e
a002 a018 a002 1717 0402 a065 a002 a018
a002 1717 0402 a074 a002 a018 a002 17
uxnasm src.tal turnstile.rom && uxncli turnstile.rom

This short ambigram prints "6" and halts.

80 c5 36 17 36 c5 80
uxnasm 6.tal 6.rom && uxncli 6.rom

This cursed ambigram also prints "6", but will only work at exactly 54 minutes and 18 seconds, any hour of the day. It is Devine's entry to BGGP6:

80 c5 36 17 36 c5 80
uxnasm 6c.tal 6c.rom && uxncli 6c.rom

This self-replicating program produces exactly 1 copy of itself, names the copy "4", does not execute the copied file, and prints the number 4. It is Devine's 19 bytes entry to BGGP4:

|a0 @File &vector $2 &success $2 &stat $2 &delete $1 &append $1 &name $2 &length $2 &read $2 &write $2
|100
	[ LIT2 13 -File/length ] DEO2
	INC [ LIT2 -&filename -File/name ] DEO2
	INC SWP .File/write DEO2
	[ LIT2 "4 18 ] DEO
	&filename "4
uxnasm src.tal seed.rom && uxncli seed.rom

This polyglot program is both a valid tga image, and a valid rom that will print that same image. It is Devine's entry to BGGP2

|20 @Screen &vector $2 &width $2 &height $2 &auto $1 &pad $1 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1
|100
@length [ 40 ] @header [ 01 01 ]
@color-map [ 0000 3000 20 ] [ 0000 1000 1000 1000 0820 ]
@description $40
@color-map-data [ 0000 00ff ffff ffff $aa !program $b ]
@body [
0101 0101 0101 0100 0001 0101 0101 0101
0101 0101 0101 0000 0000 0101 0101 0101
0101 0101 0100 0000 0000 0001 0101 0101
0101 0101 0100 0101 0101 0001 0101 0101
0101 0101 0100 0101 0101 0001 0101 0101
0101 0101 0100 0001 0100 0001 0101 0101
0101 0101 0101 0001 0100 0101 0101 0101
0101 0101 0000 0101 0101 0000 0101 0101
0101 0100 0101 0101 0101 0101 0001 0101
0101 0100 0100 0101 0101 0101 0001 0101
0101 0100 0100 0101 0101 0001 0001 0101
0101 0100 0101 0001 0101 0001 0001 0101
0101 0101 0001 0001 0100 0100 0101 0101
0101 0100 0100 0001 0100 0001 0001 0101
0101 0001 0101 0101 0101 0101 0100 0101
0101 0000 0000 0000 0000 0000 0000 0101 ]

@program ( -> )
	( print 2 ) [ LIT2 "2 18 DEO ]
	( | draw tga )
	#0010 DUP2 .Screen/width DEO2
	.Screen/height DEO2
	#0f08 DEOk INC INC DEOk INC INC DEO
	#0000
	&>l ( -- )
		DUP2 #0f AND .Screen/x DEO2
		DUP2 #04 SFT .Screen/y DEO2
		DUP2 ;body ADD2 LDA .Screen/pixel DEO
		INC DUP ?&>l
	POP2
uxnasm src.tal mothra.tga && gimp mothra.tga

This maze program ensnares one into the iconic Commodore 64 demo:

( seed ) #c5 DEI2 ,&seed STR2
[ LIT2 "/\ ] #f800
&>w ( -- )
	( break ) DUP #3f AND ?{ #0a18 DEO }
	( seed ) OVR2 [ LIT2 &seed &x $1 &y $1 ]
	( randx ) ADDk #50 SFT EOR DUP #03 SFT EOR DUP ,&x STR
	( randy ) SUBk #01 SFT EOR EOR DUP ,&y STR
	( emit ) #01 AND [ LIT POP ] ADD [ #00 STR $1 ] #18 DEO
	INC2 ORAk ?&>w
POP2 POP2
uxnasm src.tal res.rom && uxncli res.rom
DUP EOR ORA

This program unlocks the scorching power to create COMEFROM statements at runtime and prints exclamation marks in an infinite loop:

( 10 ) ;&label COMEFROM
( 20 ) [ LIT2 "! 18 ] DEO
( 30 ) &label $4
( 40 ) BRK

@COMEFROM ( label* -- )
    ( LIT2 ) STH2k [ LIT LIT2 ] STH2kr STA
    ( JMP2 ) INC2r INC2r INC2r [ LIT JMP2 ] STH2r STA
    ( addr* ) STH2kr SWP2 INC2 STA2
    JMP2r
uxnasm src.tal res.rom && uxncli res.rom

This program compiles caret-prefixed Brainfuck directly into uxntal using nothing but macros:

%>  ( m* -- m* ) { INC2 }
%<  ( m* -- m* ) { #0001 SUB2 }
%^+ ( m* -- m* ) { STH2k LDAk INC STH2r STA }
%^- ( m* -- m* ) { STH2k LDAk #01 SUB STH2r STA }
%^. ( m* -- m* ) { LDAk #18 DEO }
%^[ ( m* -- m* ) { #00 JSR LDAk #00 EQU ?{ }
%^] ( m* -- m* ) { LDAk #00 EQU ?{ JMP2kr }\ }\ }

@on-reset ( -> ) ;memory ^+ ^+ ^+ ^[ ^. ^- ^] ^. BRK
@memory
uxnasm bf.tal res.rom && uxncli res.rom

Many Factorials in Uxntal

Inspired by Marvin Borner's Many Factorials in Bruijn.

@fac-classic ( n -- res )
	DUP ?{ INC JMP2r }
	DUP #01 SUB fac-classic MUL JMP2r

@fac-keep ( n -- res )
	#01 GTHk ?{ NIP JMP2r }
	OVR SWP SUB fac-keep MUL JMP2r

@fac-tail ( n -- res )
	#01 &rec ( n acc -- res )
		OVR ?{ NIP JMP2r }
		OVR MUL #0100 SUB2 !&rec

@fac-algol ( n -- res )
	#00 STZ
	#00 LDZ #00 EQU ?{
		#00 LDZ
		#00 LDZ #01 SUB fac-algol
			MUL JMP2r }
	#01 JMP2r

@fac-bitwise ( n -- res )
	DUP ?{ #01 !/add }
	DUP #ff /add fac-bitwise
	&mul ( x y -- res )
		DUP ?{ NIP JMP2r }
		#ff /add OVR /mul
	&add ( x y -- res )
		DUP ?{ POP JMP2r }
		ANDk #10 SFT STH EOR STHr !/add

%fac-obfuscate ( n -- res ) {
	8001 0780 0008 2000 0907
	1aa0 0100 3940 fff0 0300 }