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.
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.
- Zine, by Clemens Scott.
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 Runes | Number Rune | ||||||
|---|---|---|---|---|---|---|---|
| | absolute | $ | relative | # | literal number | ||
| Label Runes | Ascii Runes | ||||||
@ | parent | & | child | " | raw string | ||
| Addressing Runes | Wrapping Runes | ||||||
, | literal relative | _ | raw relative | ( ) | comment | ||
. | literal zero-page | - | raw zero-page | { } | anonymous | ||
; | literal absolute | = | raw absolute | [ ] | ignored | ||
| Immediate Runes | Pre-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:
| POP | a b | Discard top item. |
|---|---|---|
| NIP | a c | Discard second item. |
| SWP | a c b | Move second item to top. |
| ROT | b c a | Move third item to top. |
| DUP | a b c c | Copy top item. |
| OVR | a b c b | Copy 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:
- A Literal Hex, like #ab, denotes a number that will be pushed on the stack when evaluated, it is made of a LIT opcode that matches its length, followed by a Raw Hex number. It is either 2 or 4 characters in length.
- A Raw Hex, like aa, is the standard textual encoding of data in a program, generally speaking these are more often loaded than evaluated. It can be anything, an opcode, an ascii byte, an address, part of a sprite. It is either 2 or 4 characters in length.
#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 | 00 | 01 | 02 | 7e | 7f | 80 | 81 | 82 | fd | fe | ff | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| unsigned | 0 | 1 | 2 | 126 | 127 | 128 | 129 | 130 | 253 | 254 | 255 | ||
| signed | 0 | 1 | 2 | 126 | 127 | -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:
- The short mode 2 operates on shorts, instead of bytes.
- The keep mode k operates without consuming items.
- The return mode r operates on the return stack.
| INC2r | |||||||
|---|---|---|---|---|---|---|---|
| k | r | 2 | opcode | ||||
| 0 | 1 | 1 | 0 | 0 | 0 | 0 | 1 |
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 literal LIT opcode, also written as #.
- The jump !routine.
- The conditional jump ?routine.
- The subroutine routine.
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.
- 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 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 }
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.
| Shared | Memory | RAM | Data | 64kb pages |
|---|---|---|---|---|
| Private | Stacks | Working Stack | Data | 256 bytes |
| Pointer | 1 byte | |||
| Return Stack | Data | 256 bytes | ||
| Pointer | 1 byte | |||
| IO | Devices | Data | 256 bytes |
The device page and stacks are located outside of addressable memory.
- An Absolute Padding, like |100 moves the program generation to an address specified by a number or label.
- A Relative Padding, like $18 moves the program generation by a distance specified by a number or label.
|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:
- Drif is an assembler that also emits a symbols file.
- Uxnfor is a formatter that standardize the source code, this is the formatting style used across the Uxntal documentation.
- Uxnlin is a peephole optimizer that reveals potential optimizations in opcode sequences.
- Uxnbal is a program validator that warns when routines do not match their definitions.
- Uxndis is a disassembler that prints the opcodes in a rom file.
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 }