Uxntal Syntax
In stack programming there are no variables and 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
Numbers in Uxntal are not decimal, they are expressed in hexadecimal. Which means that counting goes like: one, two, three, four, five, six, seven, eight, nine, ha, be, ce, de, he, fe, ten! It takes some getting used to, but don't worry, you'll get the hang of it.
Now, without further ado..
Let's dive into it!
The following example program prints the phrase "Hello World!" by pushing the address to a label on the stack, and iterating through each letter found at that address with a loop that increments the pointer until it reaches end of the phrase, at which point, the stack is emptied and the evaluation halts.
A word starting with @ defines a label, and one starting with ; pushes the address of a label to the stack. ;text pushes two bytes pointing to the absolute address of the @text label to the stack. Next, we define a new label named @while, to mark the start of a loop that will print each character stored at the text label.
The LDAk opcode loads a byte from the address on 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 letter from the stack, and if it is not zero, we jump to the corresponding }, which is an anonymous label.
|0100 a0 01 11 ( ;text ) @while |0103 94 ( LDAk ) |0104 06 ( DUP ) |0105 20 00 02 ( ?λ00 ) |0108 22 ( POP2 ) |0109 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 and sends the letter to the device port, printing it on the Console, leaving only the address on top of the stack.
@λ00 |010a 80 18 ( #18 ) |010c 17 ( DEO ) |010d 21 ( INC2 ) |010e 40 ff f2 ( !while )
The INC2 opcode increments the address, moving the text pointer to the next letter. The 2-mode is used because the address is made of two bytes.
Finally, we jump back to the start of the loop with !while, and repeat the loop until the letter is zero, when that happens, we POP to remove the letter, and POP2 to remove the address on the stack to keep the stack clean at the end of the evaluation.
@text |0111 48 65 6c ( H e l ) |0114 6c 6f 20 ( l o ) |0116 57 6f 72 ( W o r ) |011a 6c 64 21 ( l d ! )
To summarize, comments are within parentheses, numbers are lowercase hexadecimal 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 Runes | Number Rune | ||||||
---|---|---|---|---|---|---|---|
| | absolute | $ | relative | # | literal number | ||
Label Runes | Ascii Runes | ||||||
@ | parent | & | child | " | raw ascii | ||
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 | ~ | include |