Old page wikitext, before the edit (old_wikitext) | ''''Tina''' ('''T'''his '''i'''s '''n'''ot '''a'''ssembly) is an [[esoteric programming language]] with an assembly-like syntax and a deliberately overpowered “ALU matrix” instruction format. A Tina program is assembled into a linear list of instructions plus an initial memory image, then executed on a simple machine with unbounded signed integer cells and non-negative addresses.
A reference interpreter/assembler is implemented in Python.
==Etymology==
“Tina” is a backronym for “This is not assembly”. The language is intentionally close to assembly in feel (labels, cells, jumps, stack operations), while also featuring higher-level conveniences like conditional-branching ALU instructions and built-in memory/string operations.
==Computational model==
Tina executes a sequence of instructions over:
* An unbounded memory of signed integers (cells default to 0).
* A program counter (PC) indexing the instruction list.
* Optional stack/frame support using two conventional cells named <code>SP</code> and <code>FP</code> when stack instructions are used.
Memory addresses must be non-negative; attempting to access a negative address is an error.
==Syntax overview==
* One instruction or directive per line.
* Labels are written as <code>name:</code> (multiple labels may precede a statement).
* Comments begin with <code>;</code> and continue to end of line.
* Mnemonics are case-insensitive (the reference interpreter uppercases internally).
Example line:
<pre>
loop: ADD32SNEZ #1, counter, loop ; increment and loop while counter != 0 (example only)
</pre>
==Assembler directives==
These directives allocate and/or initialize memory in the initial image.
; <code>.cell name = value</code>
: Allocate one cell, optionally initialized. If omitted, initializes to 0. Character literals like <code>'A'</code> are allowed (one character only).
; <code>.block name, n</code>
: Allocate <code>n</code> zero-initialized cells.
; <code>.data name v1, v2, ...</code>
: Allocate a list of cells initialized to the given integers (supports decimal/hex via Python-style <code>0x</code>).
; <code>.zstr name "text"</code>
: Allocate a null-terminated byte string. Each character must be in 0..255, and a trailing 0 cell is appended.
Symbols defined by these directives can be used as memory operands and (in immediates) as numeric addresses.
==Operands and addressing modes==
Tina instructions take ''operands'' which can be immediate values or memory references.
===Immediate===
; <code>#n</code>
: Immediate integer <code>n</code>. In immediates, <code>n</code> may be:
:* a numeric literal (<code>#10</code>, <code>#0xFF</code>, <code>#-3</code>)
:* a memory symbol (<code>#MSG</code> yields MSG’s address)
:* a code label (<code>#loop</code> yields the instruction index of <code>loop:</code>)
:* optionally with a small decimal offset: <code>#LABEL+3</code>, <code>#var-1</code>
Immediates are not addressable (you cannot write to <code>#...</code>).
===Memory (direct / indexed)===
; <code>x</code>
: Direct cell at address <code>x</code> (where <code>x</code> is a symbol or numeric address).
; <code>x+K</code> / <code>x-K</code>
: Direct cell at address <code>x+K</code> (offset is decimal in the reference interpreter).
===Indirect (pointer)===
; <code>@x</code>
: Indirect cell at address <code>mem[x]</code>.
; <code>@x+K</code>
: Indirect indexed cell at address <code>mem[x]+K</code>.
In pseudocode, reading <code>@p+2</code> means <code>mem[mem[p] + 2]</code>.
==ALU instruction matrix==
Most arithmetic/logic operations are expressed with a single general pattern:
<pre>
<OP><WIDTH><OVF><COND> src, dst [, label]
</pre>
Execution model:
1. Read <code>src</code> and <code>dst</code>.
2. Compute a new value (<code>new_dst</code>) using <code>OP</code>.
3. Write <code>new_dst</code> back to <code>dst</code>.
4. If a condition suffix (<code>COND</code>) is present, branch to <code>label</code> if the condition is true when applied to <code>new_dst</code>; otherwise fall through.
===Base operations (OP)===
The reference interpreter implements these ALU base ops:
* Data movement/arithmetic: <code>MOV ADD SUB MUL DIV MOD</code>
* Convenience arithmetic: <code>INC DEC NEG ABS MIN MAX</code>
* Bitwise: <code>AND OR XOR XNOR NOR NAND NOT</code>
* Shifts/rotates: <code>SHL SHR SAR ROL ROR</code>
* Bit tricks: <code>POPCNT CLZ CTZ</code>
* Comparisons (write results into <code>dst</code>): <code>CMPEQ CMPLT CMPLE CMPGT CMP3</code>
* Swap: <code>SWP</code> (special: swaps two addressable operands; immediates are not allowed)
=== Width (WIDTH) ===
Optional: <code>8</code>, <code>16</code>, <code>32</code>, <code>64</code>.
If provided, results are interpreted as signed integers of that width.
If omitted, Tina uses unbounded integers (except some bit-operations internally assume a 64-bit “view” for operations like rotate/counting, per the reference interpreter).
=== Overflow/checked behavior (OVF) ===
Only meaningful when a width is present:
* (none): wrap to the given width (two’s complement wrap)
* <code>S</code>: saturating (clamp to min/max representable signed value)
* <code>C</code>: checked (raises an error on overflow)
=== Conditional suffix (COND) ===
If present, the instruction takes an additional <code>label</code> argument.
Simple conditions (applied to <code>new_dst</code>):
* <code>LEQ</code> (≤ 0), <code>EQZ</code> (== 0), <code>NEZ</code> (!= 0)
* <code>LTZ</code>, <code>GEZ</code>, <code>GTZ</code>
* <code>ODD</code>, <code>EVN</code>
* <code>POS</code> (≥ 0), <code>NEG</code> (< 0)
Bit-test conditions:
* <code>BSET0</code>..<code>BSET63</code> (branch if bit k is 1)
* <code>BCLR0</code>..<code>BCLR63</code> (branch if bit k is 0)
=== SUBLEQ alias ===
<code>SUBLEQ</code> is an alias for:
<pre>
SUBLEQ src, dst, label ≡ SUB src, dst, label (branch if new_dst <= 0)
</pre>
This makes it easy to write classic SUBLEQ-style code while still having a larger instruction set available.
==Control flow and non-ALU instructions==
These instructions are not expressed through the ALU matrix:
* <code>JMP label</code> — unconditional jump
* <code>JMPI op</code> — jump to the value read from <code>op</code>
* <code>BR op, label</code> — branch if <code>op != 0</code>
Dedicated branches (branch based on <code>op</code>):
* <code>BZ</code>, <code>BNZ</code>, <code>BLTZ</code>, <code>BLEQZ</code>, <code>BGEZ</code>, <code>BGTZ</code>, <code>BODD</code>, <code>BEVN</code>
Loop helper:
* <code>DJNZ dst, label</code> — decrement <code>dst</code>, branch if the result is not zero
Misc memory ops:
* <code>ZAP dst</code> — set destination to 0
* <code>XCH a, b</code> — exchange two addressable operands (no immediates)
==Stack and calls==
These instructions use a conventional stack in memory and rely on two special cells if used:
* <code>.cell SP = ...</code> must exist for <code>PUSH/POP/CALL/RET/ENTER/LEAVE</code>
* <code>.cell FP = ...</code> must exist for <code>ENTER/LEAVE</code>
Instructions:
* <code>PUSH op</code> — store value at <code>mem[SP]</code>, then increment <code>SP</code>
* <code>POP dst</code> — decrement <code>SP</code>, then load from <code>mem[SP]</code> into <code>dst</code>
* <code>CALL label</code>, <code>CALLI op</code> — push return address, jump
* <code>RET</code> — pop return address into PC
* <code>ENTER #n</code> — push old FP, set FP = SP, allocate <code>n</code> locals by advancing SP
* <code>LEAVE</code> — restore SP = FP, restore old FP from stack
==Built-in memory/string instructions==
These are “library-like” instructions implemented directly by the interpreter:
* <code>MEMSET dstAddr, byte, n</code>
: Set <code>n</code> bytes at address <code>dstAddr</code> to <code>byte & 0xFF</code>.
* <code>MEMCPY srcAddr, dstAddr, n</code>
: Copy <code>n</code> cells; overlap-safe in the reference interpreter.
* <code>MEMCMP aAddr, bAddr, n, dst</code>
: Compare <code>n</code> cells lexicographically; write -1/0/1 into <code>dst</code>.
* <code>STRLENZ srcAddr, dst</code>
: Length of a null-terminated byte string.
* <code>STRCPYZ srcAddr, dstAddr</code>
: Copy a null-terminated byte string including terminator.
* <code>STRCMPZ aAddr, bAddr, dst</code>
: Compare two null-terminated byte strings; write -1/0/1.
Note: <code>srcAddr</code>/<code>dstAddr</code> operands are read as values (addresses). For example, if <code>p</code> contains 100, then <code>STRLENZ p, len</code> measures from address 100.
==Input and output==
Input is byte-based or integer-based and shares a single input stream.
* <code>INB dst, labelEOF</code>
: Read one byte. On EOF, writes -1 to <code>dst</code> and jumps to <code>labelEOF</code>.
* <code>INN dst, labelEOF</code>
: Read a signed decimal integer token (skipping whitespace). On EOF (or no integer available), jumps to <code>labelEOF</code> (reference interpreter does not guarantee writing anything to <code>dst</code> in this case).
Output:
* <code>OUTB op</code> — output low byte (<code>& 0xFF</code>)
* <code>OUTD op</code> — output decimal integer text (no newline)
* <code>OUTHEX op</code> — output as 64-bit-masked hex with <code>0x</code> prefix
* <code>OUTBIN op</code> — output as 64-bit-masked binary with <code>0b</code> prefix
* <code>OUTZ symbolOrAddr</code> — output null-terminated bytes starting at that address (note: this takes a symbol/address, not a full addressing-mode operand)
* <code>OUTZI op</code> — output null-terminated bytes starting at the address read from <code>op</code>
* <code>OUTS op</code> — output a length-prefixed string: <code>addr = op</code>, <code>mem[addr]</code> is length, bytes follow at <code>addr+1..</code>
* <code>EOL</code> — output newline
==Debug/abort==
* <code>TRAP #code</code> — terminate the program with exit code <code>code</code>
* <code>ASSERT op, #code</code> — if <code>op == 0</code>, terminate with <code>code</code>
* <code>BREAK</code>, <code>WATCH op</code> — present but treated as no-ops by the reference interpreter (useful with tracing/debugging in other implementations)
==Program termination==
* <code>HALT</code> returns exit code 0.
* Falling off the end of the instruction list ends execution (exit code 0 in the reference interpreter).
* <code>TRAP</code>/<code>ASSERT</code> can terminate with non-zero codes.
==Implementation==
The reference implementation is a combined assembler and interpreter written in Python 3.
Usage:
<pre>
python tina.py program.tina < input > output
python tina.py program.tina --trace
</pre>
==Examples==
This is a traditional "Hello World" program in Tina:
<pre>
.zstr MSG "Hello, world!\n"
start:
OUTZ MSG
HALT
</pre>
This program is a typical truth machine:
<pre>
.cell ZERO = 0
.cell C48 = 48 ; '0'
.cell ch = 0
.cell tmp = 0
start:
INB ch, done
OUTB ch
MOV ch, tmp
SUBEQZ C48, tmp, done ; tmp = ch - 48; if tmp == 0 => input was '0'
loop:
OUTB ch
SUBLEQ ZERO, ZERO, loop ; unconditional jump (classic SUBLEQ trick)
done:
HALT
</pre>
This program is a simple cat program:
<pre>
.cell ZERO = 0
.cell ch = 0
loop:
INB ch, done
OUTB ch
SUBLEQ ZERO, ZERO, loop
done:
HALT
</pre>
This program is an implementation of Fizz Buzz:
<pre>
.cell ZERO = 0
.cell ONE = 1
.cell THREE = 3
.cell FIVE = 5
.cell i = 1
.cell rem = 100
.cell c3 = 3
.cell c5 = 5
.cell f = 0
.cell b = 0
.cell tmp = 0
.cell sum = 0
.zstr SFIZZ "Fizz"
.zstr SBUZZ "Buzz"
loop:
ZAP f
ZAP b
DJNZ c3, no_fizz
MOV THREE, c3
MOV ONE, f
no_fizz:
DJNZ c5, no_buzz
MOV FIVE, c5
MOV ONE, b
no_buzz:
; if f != 0 print "Fizz"
MOV f, tmp
SUBEQZ ZERO, tmp, skip_fizz
OUTZ SFIZZ
skip_fizz:
; if b != 0 print "Buzz"
MOV b, tmp
SUBEQZ ZERO, tmp, skip_buzz
OUTZ SBUZZ
skip_buzz:
; if (f+b)==0 print the number
MOV f, sum
ADD b, sum
SUBEQZ ZERO, sum, print_num
SUBLEQ ZERO, ZERO, after_num
print_num:
OUTD i
after_num:
EOL
ADD ONE, i
DJNZ rem, loop
HALT
</pre>
This takes a number as input and calculates its factorial:
<pre>
.cell ZERO = 0
.cell ONE = 1
.cell n = 0
.cell fact = 1
.cell tmp = 0
start:
INN n, eof
MOV ONE, fact
loop:
MOV n, tmp
SUBLEQ ONE, tmp, print ; tmp = n-1; if tmp <= 0 => n <= 1 => done
MUL n, fact ; fact *= n
SUB ONE, n ; n--
SUBLEQ ZERO, ZERO, loop
print:
OUTD fact
EOL
HALT
eof:
HALT
</pre>
This implements a simple [[Brainfuck]] interpreter:
<pre>
; Brainfuck interpreter in Tina
;
; Input format on stdin:
; 1) First line: the Brainfuck program (only ><+-.,[] are kept; other chars ignored)
; 2) Remaining bytes after the newline are used as Brainfuck input for ','.
;
; Notes:
; - Tape cells are treated as 8-bit (wrapping) via ADD8/SUB8.
; - Data pointer moves right/left by 1 cell. Moving to a negative address will error.
; -------- fixed “heap” base addresses (picked far away from our .cell area) --------
.cell PROGBASE = 100000 ; program bytes stored at PROGBASE + i
.cell JUMPBASE = 200000 ; matching-bracket table at JUMPBASE + i (only for [ and ])
.cell STACKBASE = 300000 ; stack for bracket matching during load
.cell TAPEBASE = 400000 ; BF data tape starts here
; -------- required register cells for PUSH/POP --------
.cell SP = 0
.cell FP = 0
; -------- interpreter state --------
.cell PLEN = 0 ; program length (#instructions kept)
.cell IP = 0 ; BF instruction pointer (0..PLEN)
.cell DP = 0 ; BF data pointer (address into tape)
.cell OP = 0 ; current program byte
.cell TMP = 0
.cell FLAG = 0
; pointers for indirect addressing
.cell PPROG = 0
.cell PJUMP = 0
start:
MOV STACKBASE, SP
ZAP PLEN
ZAP IP
MOV TAPEBASE, DP
JMP load_loop
; --------------------------- load / filter program ---------------------------
load_loop:
INB OP, load_done ; EOF => done
; stop reading program at newline (LF)
MOV OP, FLAG
CMPEQNEZ #10, FLAG, load_done
; keep only ><+-.,[]
MOV OP, FLAG
CMPEQNEZ #62, FLAG, load_store ; '>'
MOV OP, FLAG
CMPEQNEZ #60, FLAG, load_store ; '<'
MOV OP, FLAG
CMPEQNEZ #43, FLAG, load_store ; '+'
MOV OP, FLAG
CMPEQNEZ #45, FLAG, load_store ; '-'
MOV OP, FLAG
CMPEQNEZ #46, FLAG, load_store ; '.'
MOV OP, FLAG
CMPEQNEZ #44, FLAG, load_store ; ','
MOV OP, FLAG
CMPEQNEZ #91, FLAG, load_store ; '['
MOV OP, FLAG
CMPEQNEZ #93, FLAG, load_store ; ']'
JMP load_loop
load_store:
; PROG[PLEN] = OP
MOV PROGBASE, PPROG
ADD PLEN, PPROG
MOV OP, @PPROG
; if OP == '[' => push its index
MOV OP, FLAG
CMPEQNEZ #91, FLAG, load_push
; if OP == ']' => pop and create jump links
MOV OP, FLAG
CMPEQNEZ #93, FLAG, load_pop
INC #0, PLEN
JMP load_loop
load_push:
PUSH PLEN
INC #0, PLEN
JMP load_loop
load_pop:
; underflow check: if (SP-STACKBASE) <= 0 => unmatched ']'
MOV SP, TMP
SUB STACKBASE, TMP
BLEQZ TMP, trap_unmatched_close
POP TMP ; TMP = matching '[' index
; JUMP[TMP] = PLEN
MOV JUMPBASE, PJUMP
ADD TMP, PJUMP
MOV PLEN, @PJUMP
; JUMP[PLEN] = TMP
MOV JUMPBASE, PJUMP
ADD PLEN, PJUMP
MOV TMP, @PJUMP
INC #0, PLEN
JMP load_loop
load_done:
; if stack not empty => unmatched '['
MOV SP, TMP
SUB STACKBASE, TMP
BNZ TMP, trap_unmatched_open
MOV STACKBASE, SP ; reset stack
ZAP IP ; start execution
JMP exec_loop
; --------------------------- execute BF program ---------------------------
exec_loop:
; if IP >= PLEN => halt
MOV PLEN, TMP
SUB IP, TMP
BLEQZ TMP, done
; OP = PROG[IP]
MOV PROGBASE, PPROG
ADD IP, PPROG
MOV @PPROG, OP
; dispatch on OP
MOV OP, FLAG
CMPEQNEZ #62, FLAG, op_gt ; '>'
MOV OP, FLAG
CMPEQNEZ #60, FLAG, op_lt ; '<'
MOV OP, FLAG
CMPEQNEZ #43, FLAG, op_plus ; '+'
MOV OP, FLAG
CMPEQNEZ #45, FLAG, op_minus ; '-'
MOV OP, FLAG
CMPEQNEZ #46, FLAG, op_dot ; '.'
MOV OP, FLAG
CMPEQNEZ #44, FLAG, op_comma ; ','
MOV OP, FLAG
CMPEQNEZ #91, FLAG, op_lbr ; '['
MOV OP, FLAG
CMPEQNEZ #93, FLAG, op_rbr ; ']'
; should not happen
INC #0, IP
JMP exec_loop
op_gt: ; '>'
INC #0, DP
INC #0, IP
JMP exec_loop
op_lt: ; '<'
DEC #0, DP
INC #0, IP
JMP exec_loop
op_plus: ; '+'
ADD8 #1, @DP
INC #0, IP
JMP exec_loop
op_minus: ; '-'
SUB8 #1, @DP
INC #0, IP
JMP exec_loop
op_dot: ; '.'
OUTB @DP
INC #0, IP
JMP exec_loop
op_comma: ; ','
INB TMP, op_comma_eof
MOV TMP, @DP
INC #0, IP
JMP exec_loop
op_comma_eof:
MOV #0, @DP
INC #0, IP
JMP exec_loop
op_lbr: ; '['
BZ @DP, op_lbr_zero
INC #0, IP
JMP exec_loop
op_lbr_zero:
; IP = JUMP[IP] + 1
MOV JUMPBASE, PJUMP
ADD IP, PJUMP
MOV @PJUMP, TMP
MOV TMP, IP
INC #0, IP
JMP exec_loop
op_rbr: ; ']'
BNZ @DP, op_rbr_nz
INC #0, IP
JMP exec_loop
op_rbr_nz:
; IP = JUMP[IP] + 1 (jump back to after matching '[')
MOV JUMPBASE, PJUMP
ADD IP, PJUMP
MOV @PJUMP, TMP
MOV TMP, IP
INC #0, IP
JMP exec_loop
done:
HALT
; --------------------------- error exits ---------------------------
trap_unmatched_close:
TRAP #1 ; saw ']' with no matching '['
trap_unmatched_open:
TRAP #2 ; program ended with unmatched '[' remaining
</pre>
==Computational class==
With unbounded memory, arithmetic, and conditional/unconditional jumps, Tina can implement a register machine (or simulate other Turing-complete models). Therefore Tina is [[Turing-complete]] (assuming unbounded time and memory).
==See also==
* [[OISC]]
* [[Assembly language]]
==External resources==
* Reference interpreter/assembler: [[https://github.com/OscarLo11212821/Tina/tree/main|Tina Interpreter]]
[[Category:Languages]]
[[Category:Implemented]]
[[Category:Low-level]]
[[Category:Assembly]]
[[Category:Turing complete]]' |
New page wikitext, after the edit (new_wikitext) | ''''Tina''' ('''T'''his '''i'''s '''n'''ot '''a'''ssembly) is an [[esoteric programming language]] made by user [[User:Oscarlo|Oscarlo]] with an assembly-like syntax and a deliberately overpowered “ALU matrix” instruction format. A Tina program is assembled into a linear list of instructions plus an initial memory image, then executed on a simple machine with unbounded signed integer cells and non-negative addresses.
A reference interpreter/assembler is implemented in Python.
==Etymology==
“Tina” is a backronym for “This is not assembly”. The language is intentionally close to assembly in feel (labels, cells, jumps, stack operations), while also featuring higher-level conveniences like conditional-branching ALU instructions and built-in memory/string operations.
==Computational model==
Tina executes a sequence of instructions over:
* An unbounded memory of signed integers (cells default to 0).
* A program counter (PC) indexing the instruction list.
* Optional stack/frame support using two conventional cells named <code>SP</code> and <code>FP</code> when stack instructions are used.
Memory addresses must be non-negative; attempting to access a negative address is an error.
==Syntax overview==
* One instruction or directive per line.
* Labels are written as <code>name:</code> (multiple labels may precede a statement).
* Comments begin with <code>;</code> and continue to end of line.
* Mnemonics are case-insensitive (the reference interpreter uppercases internally).
Example line:
<pre>
loop: ADD32SNEZ #1, counter, loop ; increment and loop while counter != 0 (example only)
</pre>
==Assembler directives==
These directives allocate and/or initialize memory in the initial image.
; <code>.cell name = value</code>
: Allocate one cell, optionally initialized. If omitted, initializes to 0. Character literals like <code>'A'</code> are allowed (one character only).
; <code>.block name, n</code>
: Allocate <code>n</code> zero-initialized cells.
; <code>.data name v1, v2, ...</code>
: Allocate a list of cells initialized to the given integers (supports decimal/hex via Python-style <code>0x</code>).
; <code>.zstr name "text"</code>
: Allocate a null-terminated byte string. Each character must be in 0..255, and a trailing 0 cell is appended.
Symbols defined by these directives can be used as memory operands and (in immediates) as numeric addresses.
==Operands and addressing modes==
Tina instructions take ''operands'' which can be immediate values or memory references.
===Immediate===
; <code>#n</code>
: Immediate integer <code>n</code>. In immediates, <code>n</code> may be:
:* a numeric literal (<code>#10</code>, <code>#0xFF</code>, <code>#-3</code>)
:* a memory symbol (<code>#MSG</code> yields MSG’s address)
:* a code label (<code>#loop</code> yields the instruction index of <code>loop:</code>)
:* optionally with a small decimal offset: <code>#LABEL+3</code>, <code>#var-1</code>
Immediates are not addressable (you cannot write to <code>#...</code>).
===Memory (direct / indexed)===
; <code>x</code>
: Direct cell at address <code>x</code> (where <code>x</code> is a symbol or numeric address).
; <code>x+K</code> / <code>x-K</code>
: Direct cell at address <code>x+K</code> (offset is decimal in the reference interpreter).
===Indirect (pointer)===
; <code>@x</code>
: Indirect cell at address <code>mem[x]</code>.
; <code>@x+K</code>
: Indirect indexed cell at address <code>mem[x]+K</code>.
In pseudocode, reading <code>@p+2</code> means <code>mem[mem[p] + 2]</code>.
==ALU instruction matrix==
Most arithmetic/logic operations are expressed with a single general pattern:
<pre>
<OP><WIDTH><OVF><COND> src, dst [, label]
</pre>
Execution model:
1. Read <code>src</code> and <code>dst</code>.
2. Compute a new value (<code>new_dst</code>) using <code>OP</code>.
3. Write <code>new_dst</code> back to <code>dst</code>.
4. If a condition suffix (<code>COND</code>) is present, branch to <code>label</code> if the condition is true when applied to <code>new_dst</code>; otherwise fall through.
===Base operations (OP)===
The reference interpreter implements these ALU base ops:
* Data movement/arithmetic: <code>MOV ADD SUB MUL DIV MOD</code>
* Convenience arithmetic: <code>INC DEC NEG ABS MIN MAX</code>
* Bitwise: <code>AND OR XOR XNOR NOR NAND NOT</code>
* Shifts/rotates: <code>SHL SHR SAR ROL ROR</code>
* Bit tricks: <code>POPCNT CLZ CTZ</code>
* Comparisons (write results into <code>dst</code>): <code>CMPEQ CMPLT CMPLE CMPGT CMP3</code>
* Swap: <code>SWP</code> (special: swaps two addressable operands; immediates are not allowed)
=== Width (WIDTH) ===
Optional: <code>8</code>, <code>16</code>, <code>32</code>, <code>64</code>.
If provided, results are interpreted as signed integers of that width.
If omitted, Tina uses unbounded integers (except some bit-operations internally assume a 64-bit “view” for operations like rotate/counting, per the reference interpreter).
=== Overflow/checked behavior (OVF) ===
Only meaningful when a width is present:
* (none): wrap to the given width (two’s complement wrap)
* <code>S</code>: saturating (clamp to min/max representable signed value)
* <code>C</code>: checked (raises an error on overflow)
=== Conditional suffix (COND) ===
If present, the instruction takes an additional <code>label</code> argument.
Simple conditions (applied to <code>new_dst</code>):
* <code>LEQ</code> (≤ 0), <code>EQZ</code> (== 0), <code>NEZ</code> (!= 0)
* <code>LTZ</code>, <code>GEZ</code>, <code>GTZ</code>
* <code>ODD</code>, <code>EVN</code>
* <code>POS</code> (≥ 0), <code>NEG</code> (< 0)
Bit-test conditions:
* <code>BSET0</code>..<code>BSET63</code> (branch if bit k is 1)
* <code>BCLR0</code>..<code>BCLR63</code> (branch if bit k is 0)
=== SUBLEQ alias ===
<code>SUBLEQ</code> is an alias for:
<pre>
SUBLEQ src, dst, label ≡ SUB src, dst, label (branch if new_dst <= 0)
</pre>
This makes it easy to write classic SUBLEQ-style code while still having a larger instruction set available.
==Control flow and non-ALU instructions==
These instructions are not expressed through the ALU matrix:
* <code>JMP label</code> — unconditional jump
* <code>JMPI op</code> — jump to the value read from <code>op</code>
* <code>BR op, label</code> — branch if <code>op != 0</code>
Dedicated branches (branch based on <code>op</code>):
* <code>BZ</code>, <code>BNZ</code>, <code>BLTZ</code>, <code>BLEQZ</code>, <code>BGEZ</code>, <code>BGTZ</code>, <code>BODD</code>, <code>BEVN</code>
Loop helper:
* <code>DJNZ dst, label</code> — decrement <code>dst</code>, branch if the result is not zero
Misc memory ops:
* <code>ZAP dst</code> — set destination to 0
* <code>XCH a, b</code> — exchange two addressable operands (no immediates)
==Stack and calls==
These instructions use a conventional stack in memory and rely on two special cells if used:
* <code>.cell SP = ...</code> must exist for <code>PUSH/POP/CALL/RET/ENTER/LEAVE</code>
* <code>.cell FP = ...</code> must exist for <code>ENTER/LEAVE</code>
Instructions:
* <code>PUSH op</code> — store value at <code>mem[SP]</code>, then increment <code>SP</code>
* <code>POP dst</code> — decrement <code>SP</code>, then load from <code>mem[SP]</code> into <code>dst</code>
* <code>CALL label</code>, <code>CALLI op</code> — push return address, jump
* <code>RET</code> — pop return address into PC
* <code>ENTER #n</code> — push old FP, set FP = SP, allocate <code>n</code> locals by advancing SP
* <code>LEAVE</code> — restore SP = FP, restore old FP from stack
==Built-in memory/string instructions==
These are “library-like” instructions implemented directly by the interpreter:
* <code>MEMSET dstAddr, byte, n</code>
: Set <code>n</code> bytes at address <code>dstAddr</code> to <code>byte & 0xFF</code>.
* <code>MEMCPY srcAddr, dstAddr, n</code>
: Copy <code>n</code> cells; overlap-safe in the reference interpreter.
* <code>MEMCMP aAddr, bAddr, n, dst</code>
: Compare <code>n</code> cells lexicographically; write -1/0/1 into <code>dst</code>.
* <code>STRLENZ srcAddr, dst</code>
: Length of a null-terminated byte string.
* <code>STRCPYZ srcAddr, dstAddr</code>
: Copy a null-terminated byte string including terminator.
* <code>STRCMPZ aAddr, bAddr, dst</code>
: Compare two null-terminated byte strings; write -1/0/1.
Note: <code>srcAddr</code>/<code>dstAddr</code> operands are read as values (addresses). For example, if <code>p</code> contains 100, then <code>STRLENZ p, len</code> measures from address 100.
==Input and output==
Input is byte-based or integer-based and shares a single input stream.
* <code>INB dst, labelEOF</code>
: Read one byte. On EOF, writes -1 to <code>dst</code> and jumps to <code>labelEOF</code>.
* <code>INN dst, labelEOF</code>
: Read a signed decimal integer token (skipping whitespace). On EOF (or no integer available), jumps to <code>labelEOF</code> (reference interpreter does not guarantee writing anything to <code>dst</code> in this case).
Output:
* <code>OUTB op</code> — output low byte (<code>& 0xFF</code>)
* <code>OUTD op</code> — output decimal integer text (no newline)
* <code>OUTHEX op</code> — output as 64-bit-masked hex with <code>0x</code> prefix
* <code>OUTBIN op</code> — output as 64-bit-masked binary with <code>0b</code> prefix
* <code>OUTZ symbolOrAddr</code> — output null-terminated bytes starting at that address (note: this takes a symbol/address, not a full addressing-mode operand)
* <code>OUTZI op</code> — output null-terminated bytes starting at the address read from <code>op</code>
* <code>OUTS op</code> — output a length-prefixed string: <code>addr = op</code>, <code>mem[addr]</code> is length, bytes follow at <code>addr+1..</code>
* <code>EOL</code> — output newline
==Debug/abort==
* <code>TRAP #code</code> — terminate the program with exit code <code>code</code>
* <code>ASSERT op, #code</code> — if <code>op == 0</code>, terminate with <code>code</code>
* <code>BREAK</code>, <code>WATCH op</code> — present but treated as no-ops by the reference interpreter (useful with tracing/debugging in other implementations)
==Program termination==
* <code>HALT</code> returns exit code 0.
* Falling off the end of the instruction list ends execution (exit code 0 in the reference interpreter).
* <code>TRAP</code>/<code>ASSERT</code> can terminate with non-zero codes.
==Implementation==
The reference implementation is a combined assembler and interpreter written in Python 3.
Usage:
<pre>
python tina.py program.tina < input > output
python tina.py program.tina --trace
</pre>
==Examples==
This is a traditional "Hello World" program in Tina:
<pre>
.zstr MSG "Hello, world!\n"
start:
OUTZ MSG
HALT
</pre>
This program is a typical truth machine:
<pre>
.cell ZERO = 0
.cell C48 = 48 ; '0'
.cell ch = 0
.cell tmp = 0
start:
INB ch, done
OUTB ch
MOV ch, tmp
SUBEQZ C48, tmp, done ; tmp = ch - 48; if tmp == 0 => input was '0'
loop:
OUTB ch
SUBLEQ ZERO, ZERO, loop ; unconditional jump (classic SUBLEQ trick)
done:
HALT
</pre>
This program is a simple cat program:
<pre>
.cell ZERO = 0
.cell ch = 0
loop:
INB ch, done
OUTB ch
SUBLEQ ZERO, ZERO, loop
done:
HALT
</pre>
This program is an implementation of Fizz Buzz:
<pre>
.cell ZERO = 0
.cell ONE = 1
.cell THREE = 3
.cell FIVE = 5
.cell i = 1
.cell rem = 100
.cell c3 = 3
.cell c5 = 5
.cell f = 0
.cell b = 0
.cell tmp = 0
.cell sum = 0
.zstr SFIZZ "Fizz"
.zstr SBUZZ "Buzz"
loop:
ZAP f
ZAP b
DJNZ c3, no_fizz
MOV THREE, c3
MOV ONE, f
no_fizz:
DJNZ c5, no_buzz
MOV FIVE, c5
MOV ONE, b
no_buzz:
; if f != 0 print "Fizz"
MOV f, tmp
SUBEQZ ZERO, tmp, skip_fizz
OUTZ SFIZZ
skip_fizz:
; if b != 0 print "Buzz"
MOV b, tmp
SUBEQZ ZERO, tmp, skip_buzz
OUTZ SBUZZ
skip_buzz:
; if (f+b)==0 print the number
MOV f, sum
ADD b, sum
SUBEQZ ZERO, sum, print_num
SUBLEQ ZERO, ZERO, after_num
print_num:
OUTD i
after_num:
EOL
ADD ONE, i
DJNZ rem, loop
HALT
</pre>
This takes a number as input and calculates its factorial:
<pre>
.cell ZERO = 0
.cell ONE = 1
.cell n = 0
.cell fact = 1
.cell tmp = 0
start:
INN n, eof
MOV ONE, fact
loop:
MOV n, tmp
SUBLEQ ONE, tmp, print ; tmp = n-1; if tmp <= 0 => n <= 1 => done
MUL n, fact ; fact *= n
SUB ONE, n ; n--
SUBLEQ ZERO, ZERO, loop
print:
OUTD fact
EOL
HALT
eof:
HALT
</pre>
This implements a simple [[Brainfuck]] interpreter:
<pre>
; Brainfuck interpreter in Tina
;
; Input format on stdin:
; 1) First line: the Brainfuck program (only ><+-.,[] are kept; other chars ignored)
; 2) Remaining bytes after the newline are used as Brainfuck input for ','.
;
; Notes:
; - Tape cells are treated as 8-bit (wrapping) via ADD8/SUB8.
; - Data pointer moves right/left by 1 cell. Moving to a negative address will error.
; -------- fixed “heap” base addresses (picked far away from our .cell area) --------
.cell PROGBASE = 100000 ; program bytes stored at PROGBASE + i
.cell JUMPBASE = 200000 ; matching-bracket table at JUMPBASE + i (only for [ and ])
.cell STACKBASE = 300000 ; stack for bracket matching during load
.cell TAPEBASE = 400000 ; BF data tape starts here
; -------- required register cells for PUSH/POP --------
.cell SP = 0
.cell FP = 0
; -------- interpreter state --------
.cell PLEN = 0 ; program length (#instructions kept)
.cell IP = 0 ; BF instruction pointer (0..PLEN)
.cell DP = 0 ; BF data pointer (address into tape)
.cell OP = 0 ; current program byte
.cell TMP = 0
.cell FLAG = 0
; pointers for indirect addressing
.cell PPROG = 0
.cell PJUMP = 0
start:
MOV STACKBASE, SP
ZAP PLEN
ZAP IP
MOV TAPEBASE, DP
JMP load_loop
; --------------------------- load / filter program ---------------------------
load_loop:
INB OP, load_done ; EOF => done
; stop reading program at newline (LF)
MOV OP, FLAG
CMPEQNEZ #10, FLAG, load_done
; keep only ><+-.,[]
MOV OP, FLAG
CMPEQNEZ #62, FLAG, load_store ; '>'
MOV OP, FLAG
CMPEQNEZ #60, FLAG, load_store ; '<'
MOV OP, FLAG
CMPEQNEZ #43, FLAG, load_store ; '+'
MOV OP, FLAG
CMPEQNEZ #45, FLAG, load_store ; '-'
MOV OP, FLAG
CMPEQNEZ #46, FLAG, load_store ; '.'
MOV OP, FLAG
CMPEQNEZ #44, FLAG, load_store ; ','
MOV OP, FLAG
CMPEQNEZ #91, FLAG, load_store ; '['
MOV OP, FLAG
CMPEQNEZ #93, FLAG, load_store ; ']'
JMP load_loop
load_store:
; PROG[PLEN] = OP
MOV PROGBASE, PPROG
ADD PLEN, PPROG
MOV OP, @PPROG
; if OP == '[' => push its index
MOV OP, FLAG
CMPEQNEZ #91, FLAG, load_push
; if OP == ']' => pop and create jump links
MOV OP, FLAG
CMPEQNEZ #93, FLAG, load_pop
INC #0, PLEN
JMP load_loop
load_push:
PUSH PLEN
INC #0, PLEN
JMP load_loop
load_pop:
; underflow check: if (SP-STACKBASE) <= 0 => unmatched ']'
MOV SP, TMP
SUB STACKBASE, TMP
BLEQZ TMP, trap_unmatched_close
POP TMP ; TMP = matching '[' index
; JUMP[TMP] = PLEN
MOV JUMPBASE, PJUMP
ADD TMP, PJUMP
MOV PLEN, @PJUMP
; JUMP[PLEN] = TMP
MOV JUMPBASE, PJUMP
ADD PLEN, PJUMP
MOV TMP, @PJUMP
INC #0, PLEN
JMP load_loop
load_done:
; if stack not empty => unmatched '['
MOV SP, TMP
SUB STACKBASE, TMP
BNZ TMP, trap_unmatched_open
MOV STACKBASE, SP ; reset stack
ZAP IP ; start execution
JMP exec_loop
; --------------------------- execute BF program ---------------------------
exec_loop:
; if IP >= PLEN => halt
MOV PLEN, TMP
SUB IP, TMP
BLEQZ TMP, done
; OP = PROG[IP]
MOV PROGBASE, PPROG
ADD IP, PPROG
MOV @PPROG, OP
; dispatch on OP
MOV OP, FLAG
CMPEQNEZ #62, FLAG, op_gt ; '>'
MOV OP, FLAG
CMPEQNEZ #60, FLAG, op_lt ; '<'
MOV OP, FLAG
CMPEQNEZ #43, FLAG, op_plus ; '+'
MOV OP, FLAG
CMPEQNEZ #45, FLAG, op_minus ; '-'
MOV OP, FLAG
CMPEQNEZ #46, FLAG, op_dot ; '.'
MOV OP, FLAG
CMPEQNEZ #44, FLAG, op_comma ; ','
MOV OP, FLAG
CMPEQNEZ #91, FLAG, op_lbr ; '['
MOV OP, FLAG
CMPEQNEZ #93, FLAG, op_rbr ; ']'
; should not happen
INC #0, IP
JMP exec_loop
op_gt: ; '>'
INC #0, DP
INC #0, IP
JMP exec_loop
op_lt: ; '<'
DEC #0, DP
INC #0, IP
JMP exec_loop
op_plus: ; '+'
ADD8 #1, @DP
INC #0, IP
JMP exec_loop
op_minus: ; '-'
SUB8 #1, @DP
INC #0, IP
JMP exec_loop
op_dot: ; '.'
OUTB @DP
INC #0, IP
JMP exec_loop
op_comma: ; ','
INB TMP, op_comma_eof
MOV TMP, @DP
INC #0, IP
JMP exec_loop
op_comma_eof:
MOV #0, @DP
INC #0, IP
JMP exec_loop
op_lbr: ; '['
BZ @DP, op_lbr_zero
INC #0, IP
JMP exec_loop
op_lbr_zero:
; IP = JUMP[IP] + 1
MOV JUMPBASE, PJUMP
ADD IP, PJUMP
MOV @PJUMP, TMP
MOV TMP, IP
INC #0, IP
JMP exec_loop
op_rbr: ; ']'
BNZ @DP, op_rbr_nz
INC #0, IP
JMP exec_loop
op_rbr_nz:
; IP = JUMP[IP] + 1 (jump back to after matching '[')
MOV JUMPBASE, PJUMP
ADD IP, PJUMP
MOV @PJUMP, TMP
MOV TMP, IP
INC #0, IP
JMP exec_loop
done:
HALT
; --------------------------- error exits ---------------------------
trap_unmatched_close:
TRAP #1 ; saw ']' with no matching '['
trap_unmatched_open:
TRAP #2 ; program ended with unmatched '[' remaining
</pre>
==Computational class==
With unbounded memory, arithmetic, and conditional/unconditional jumps, Tina can implement a register machine (or simulate other Turing-complete models). Therefore Tina is [[Turing-complete]] (assuming unbounded time and memory).
==See also==
* [[OISC]]
* [[Assembly language]]
==External resources==
* Reference interpreter/assembler: [[https://github.com/OscarLo11212821/Tina/tree/main|Tina Interpreter]]
[[Category:Languages]]
[[Category:Implemented]]
[[Category:Low-level]]
[[Category:Assembly]]
[[Category:Turing complete]]' |