v1.3.0  ·  Turing complete  ·  LLM-authored

PatrickScript

A programming language designed end-to-end by an LLM agent. The originator fixed two things and walked away; everything else — the computational model, the grammar, the semantics, the instruction set, the calling convention, the assembler, the test corpus — is the agent's.

Live Step a PatrickScript program one opcode at a time — stack, memory, and I/O all animated. VS Code PatrickScript is a VS Code extension. Open .ps and .psa files with syntax highlighting, a per-arity semantic palette, and a step-through CodeLens disassembler.

Install: VS Code → Extensions → search patrickscript, or run ext install prmichaelsen.patrickscript-vscode from the Quick Open palette (Ctrl+P). Offline: download the .vsix and pick Install from VSIX… from the Extensions overflow menu.

The originator's two constraints

  1. A name PatrickScript
  2. A two-token alphabet The word patrick and a single space . Two tokens, nothing else parses.

The agent took those two constraints and decided everything else. Design questions were not routed back. Forks were resolved in the spec. If a choice looks strange, it was a coherent choice under the constraint, not an oversight.

The alphabet

patrick word
space

That is the whole lexicon. A bare is a valid token; so is patrick. Nothing else parses.

How it reads

A program is a sequence of instructions. Each instruction is two numbers, both expressed as counts:

So patrick (one patrick, three spaces) is PUSH 3. patrickpatrick (two patricks, one space) is POP. There is no other structural information in the source.

Hello, World

Written in the assembly mnemonic, then compiled to the source the parser actually sees.

; hello-world.psa
.string "Hello, World!\n"
HALT
patrick
patrickpatrickpatrickpatrickpatrickpatrickpatrickpatrick  patrick
patrickpatrickpatrickpatrickpatrickpatrickpatrickpatrick  patrick

The right column is the literal .ps file. Each PUSH n is one patrick followed by n spaces. OUTCHAR is eight patricks and one space. The full program is a few kilobytes of patrick and whitespace.

Programs

Three programs in full. The left column is the assembler mnemonic; the right column is the literal .ps source the parser sees — the same characters as the left, expanded to patrick tokens and spaces, wrapped to fit the page. Every contiguous run of patrick is one instruction's word-arity; the spaces after it are its gap-encoded argument.

add — push 3, push 5, add, print → 8

; add.psa
PUSH 3
PUSH 5
ADD
OUTNUM
HALT
patrick    patrick      patrickpatrickpatrick patrickpatrickpatrickpatrickpatrickpatrickpatrickpatrick    patrickpatrickpatrickpatrickpatrickpatrickpatrickpatrickpatrickpatrick

square — read n from stdin, print n²

; square.psa — uses PICK 0 (non-destructive top-of-stack copy)
INNUM       ; read n
PICK 0      ; copy top: [n, n]
MUL         ; [n²]
OUTNUM
PUSH 10
OUTCHAR     ; newline
HALT
patrickpatrickpatrickpatrickpatrickpatrickpatrickpatrick   patrickpatrickpatrickpatrickpatrickpatrickpatrickpatrickpatrickpatrickpatrickpatrickpatrickpatrick patrickpatrickpatrick   patrickpatrickpatrickpatrickpatrickpatrickpatrickpatrick    patrick           patrickpatrickpatrickpatrickpatrickpatrickpatrickpatrick  patrickpatrickpatrickpatrickpatrickpatrickpatrickpatrickpatrickpatrick 

echo — copy stdin to stdout, halt on EOF

; echo.psa — loop, read a byte, write it, until EOF (-1)
loop:
INCHAR        ; read byte (-1 on EOF)
DUP
PUSH 1
ADD           ; -1 + 1 = 0 → EOF
JUMPZ done
OUTCHAR
JUMP loop
done:
POP
HALT
patrickpatrickpatrickpatrickpatrickpatrickpatrickpatrick patrickpatrick  patrick  patrickpatrickpatrick patrickpatrickpatrickpatrickpatrickpatrick        patrickpatrickpatrickpatrickpatrickpatrickpatrickpatrick  patrickpatrickpatrickpatrickpatrick patrickpatrick patrickpatrickpatrickpatrickpatrickpatrickpatrickpatrickpatrickpatrick 

The full corpus is on GitHub — fizzbuzz, recursive factorial, rot13, and forty-seven others.

Instruction set, abridged

ArityMnemonicEffect
1PUSH nPush integer n.
2POP / arithStack ops, ADD/SUB/MUL/DIV/MOD/NEG via gap_arg.
3EQ / LT / GT / bitwiseComparison and AND/OR/XOR/NOT.
4JUMP nJump to instruction index n.
5JUMPZ nJump if top of stack is zero.
6JUMPNZ nJump if non-zero.
7INCHAR / OUTCHAR / INNUM / OUTNUMByte and decimal I/O.
8LOAD / STOREInteger-addressed memory.
9HALTStop execution.
11CALL nPush return address; jump to n.
12RETPop return address; jump there.
13PUSHN nPush −n (single-instruction negative literal).
14PICK nCopy element n positions from top of stack.

Turing-completeness comes from JUMPZ/JUMPNZ plus unbounded integer-addressed memory. Subroutines and recursion via CALL/RET. The complete instruction set with formal semantics is in the Full specification below.

Install

# clone the reference implementation
git clone git@github.com:prmichaelsen/patrick-script.git
cd patrick-script

# run a program
python3 src/patrickscript/ps.py examples/hello-world.ps

# or write in the assembler and compile
python3 src/patrickscript/psa.py examples/hello-world.psa > hello.ps
python3 src/patrickscript/ps.py hello.ps

Reference interpreter and assembler are pure-stdlib Python. PyPI publish is queued (pip install patrickscript once the package goes up).

Read further

The proof

Spec §8 cites this file as proof PatrickScript is Turing complete: 14,266 bytes of patrick and spaces, compiling to a working Brainfuck interpreter.

examples/brainfuck.ps — 14,266 bytes of the word patrick and spaces, arranged correctly. A Brainfuck interpreter, compiled. The proof spec §8 cites. Tap image to enlarge.
examples/brainfuck.ps rendered in an editor — 14,266 bytes of the word 'patrick' and spaces, the compiled Brainfuck interpreter that discharges spec §8's Turing-completeness claim

Full specification

Transcluded directly from spec/patrickscript.md in the reference repo at build time — the canonical source, not a copy.

PatrickScript Language Specification

Version: 1.3.0 Status: active Authored by: patrick-script-worker track, 2026-05-15


1. Overview

PatrickScript is a Turing-complete, stack-based programming language whose entire design — computational model, grammar, semantics, tooling — was authored by an LLM. The only human-fixed inputs are:

Every other property of the language is a decision made by the patrick-script-worker track. No design question is deferred to the originator.

PatrickScript programs are sequences of these two tokens. Structure is encoded in the count of consecutive identical tokens: how many patricks in a row, and how many spaces in a row. These counts carry all meaning.


2. Lexical Model

A PatrickScript source file is a sequence of bytes drawn exclusively from two token types:

Token Representation Unicode
WORD The string patrick 7 ASCII bytes
SP A single space character U+0020

No other bytes are legal in a PatrickScript source. Specifically: - Newlines, tabs, carriage returns, and all other whitespace are illegal. - Any byte sequence other than patrick or is illegal. - An empty file (zero bytes) is a valid program (it halts immediately).

2.1 Tokens vs. Characters

The unit of tokenization is not the character but the token as defined above. The string patrick is a single WORD token, not seven character tokens. The string patrick is illegal (not a WORD token and not a SP token).


3. Syntactic Structure

Tokens are organized into two syntactic constructs:

Word: a maximal contiguous run of WORD tokens. The arity of a word is its length in WORD tokens.

Gap: a maximal contiguous run of SP tokens. The width of a gap is its length in SP tokens.

A PatrickScript program is a sequence of zero or more instructions. Each instruction is a (word, gap) pair: a word followed by a gap. The final instruction in a program may omit its trailing gap; when it does, the gap is treated as having width 1.

Formal Grammar

program     ::= instruction* trailing?
instruction ::= word gap
trailing    ::= word
word        ::= 'patrick'+
gap         ::= ' '+

Since words and gaps are each maximal runs of their respective token, they alternate strictly: no two words are adjacent (they would fuse into one word), and no two gaps are adjacent (they would fuse into one gap).

3.1 Instruction Encoding

Each instruction encodes:


4. Machine Model

A PatrickScript machine has the following state:

Execution proceeds by fetching the instruction at IP, executing it, advancing IP, and repeating until HALT or an error condition.


5. Instruction Set

Notation

Stack effects use the convention before → after, where items are listed left-to-right with the top of stack at the right. For example, a b → a+b means: before execution, b is on top with a below it; after execution, their sum is on top.

n denotes the gap_arg value of the current instruction (the immediate integer argument).

5.1 Stack Manipulation

Arity gap_arg Mnemonic Stack effect Description
1 n PUSH → n Push the integer n onto the stack
2 0 POP a → Discard the top of the stack
2 1 DUP a → a a Duplicate the top of the stack
2 2 SWAP a b → b a Swap the top two elements
2 3 ROT a b c → b c a Rotate: move third element to top

5.2 Arithmetic

All arithmetic pops two values unless noted. The operand order is: pop b (top), pop a (below), compute and push result.

Arity gap_arg Mnemonic Stack effect Description
3 0 ADD a b → a+b Integer addition
3 1 SUB a b → a-b Integer subtraction (a minus b)
3 2 MUL a b → a*b Integer multiplication
3 3 DIV a b → a÷b Integer division (floor), b divides a
3 4 MOD a b → a mod b Integer remainder (sign follows a)
3 5 NEG a → -a Negate (unary; pops one, not two)

Negative integer literals: PUSH encodes its argument as gap_arg, which is always ≥ 0. There is no literal syntax for negative integers. To push −n, push n then negate: PUSH n followed by NEG. The result is −n on the stack. The assembler supports this directly via two lines.

Division and modulo: a is the dividend (below in stack), b is the divisor (top of stack). DIV computes floor(a / b). MOD computes a - b * floor(a / b). Division by zero is a runtime error.

5.3 Comparison

Arity gap_arg Mnemonic Stack effect Description
4 0 EQ a b → (a==b) 1 if equal, 0 otherwise
4 1 LT a b → (a<b) 1 if a < b, 0 otherwise
4 2 GT a b → (a>b) 1 if a > b, 0 otherwise
4 3 AND a b → a&b Bitwise AND
4 4 OR a b → a|b Bitwise OR
4 5 XOR a b → a^b Bitwise XOR
4 6 NOT a → ~a Bitwise NOT (ones' complement)

Comparison operand order: pop b (top), pop a (below), push result.

5.4 Control Flow

Arity gap_arg Mnemonic Stack effect Description
5 n JUMP Set IP to n (unconditional)
6 n JUMPZ a → Pop a; if a == 0, set IP to n
7 n JUMPNZ a → Pop a; if a != 0, set IP to n

JUMP and JUMPZ set the IP to the instruction at 0-based index n. After a jump, execution continues from the new IP (no automatic increment for that step). If n is out of bounds, it is a runtime error.

JUMPZ and JUMPNZ always pop the condition value, whether or not the branch is taken.

5.5 Input / Output

Arity gap_arg Mnemonic Stack effect Description
8 0 INCHAR → c Read one byte from stdin; push its value (0–255). Push -1 on EOF.
8 1 OUTCHAR c → Pop c; write c mod 256 as one byte to stdout
8 2 INNUM → n Read one decimal integer from stdin (leading whitespace ignored); push n. Push -1 on EOF or parse error.
8 3 OUTNUM n → Pop n; write the decimal representation of n to stdout, followed by a newline

5.6 Memory

Arity gap_arg Mnemonic Stack effect Description
9 0 LOAD addr → val Pop addr; push M[addr]
9 1 STORE val addr → Pop addr; pop val; set M[addr] = val

5.7 Halt

Arity gap_arg Mnemonic Stack effect Description
10 any HALT Terminate the program with exit code 0

5.8 Subroutines (v1.1.0)

Arity gap_arg Mnemonic Stack effect Description
11 n CALL → ret Push (IP+1); jump to instruction n
12 any RET ret → Pop ret; jump to instruction ret

CALL n saves the return address (the index of the instruction immediately following CALL) by pushing it onto the value stack, then sets IP to n. Execution continues from instruction n.

RET pops the top of the value stack as a return address and sets IP to it. If the stack is empty when RET executes, it is a stack underflow error. If the popped value is not a valid instruction index, it is a jump-out-of-bounds error.

The gap_arg of CALL is the jump target (like JUMP). The gap_arg of RET is ignored (like HALT).

Subroutines can be nested: each CALL pushes a return address, and each matching RET pops it. Recursive calls are legal but may exhaust stack space in practice.

5.9 Negative Push (v1.2.0)

Arity gap_arg Mnemonic Stack effect Description
13 n PUSHN → -n Push the integer −n onto the stack

PUSHN n pushes the negation of its immediate argument. The argument n is encoded as gap_arg (≥ 0), so the pushed value is −n (≤ 0). This provides single-instruction access to negative constants, which PUSH alone cannot represent (since gap_arg ≥ 0).

Examples: PUSHN 5 pushes −5. PUSHN 0 pushes 0 (same as PUSH 0).

The two-instruction idiom PUSH n / NEG remains valid and equivalent. PUSHN is a convenience instruction, not a new capability.

5.10 Stack Copy (v1.3.0)

Arity gap_arg Mnemonic Stack effect Description
14 n PICK a[n]..a[1] a[0] → ... a[0] a[n] Copy the element n positions from top

PICK n copies the element at depth n (0-indexed from the top of the stack) and pushes it onto the top. The original element is not removed.

PICK is useful for accessing subroutine arguments buried below return addresses, and for implementing n-ary operations that need a value without consuming it.

Examples: - Stack [1 2 3] (3 on top): PICK 0[1 2 3 3] - Stack [1 2 3]: PICK 2[1 2 3 1]

5.11 Reserved and Illegal Instructions

Arities 15 and above are reserved for future versions. An instruction with arity ≥ 15 is a runtime error in v1.3.0.

There is no instruction with arity 0 (a zero-length word is not grammatically possible).


6. Error Conditions

The following conditions are runtime errors. A conforming interpreter MUST terminate with a non-zero exit code and SHOULD emit a diagnostic message to stderr.

Condition Example
Stack underflow POP on empty stack; PICK n where n ≥ depth
Division by zero DIV or MOD with 0 on top
Jump out of bounds JUMP n where n ≥ program length
Illegal instruction word arity ≥ 15
Illegal gap_arg for opcode gap_arg outside defined range for a given arity

For arity 2, gap_arg values 4 and above are illegal. For arity 3, gap_arg values 6 and above are illegal. For arity 4, gap_arg values 7 and above are illegal. For arities 5, 6, 7, any gap_arg is legal (it is the target or unused). For arity 8, gap_arg values 4 and above are illegal. For arity 9, gap_arg values 2 and above are illegal. For arity 10 (HALT), all gap_arg values are legal. For arity 11 (CALL), any gap_arg is legal (it is the target address). For arity 12 (RET), all gap_arg values are legal (the gap_arg is ignored). For arity 13 (PUSHN), any gap_arg is legal (it is the value to negate). For arity 14 (PICK), any gap_arg is legal (it is the stack depth index), but a PICK n where n ≥ stack depth is a stack underflow runtime error.

Lexical errors (illegal characters in source) are parse-time errors and MUST cause the interpreter to exit with a non-zero exit code before any execution.


7. Encoding Reference

An instruction is written as:

('patrick' × arity) (' ' × (gap_arg + 1))

except the last instruction in a program, which may omit trailing spaces (treated as gap_arg = 0).

7.1 Encoding Examples

Within a word, patrick tokens are concatenated with no separating characters. A word of arity n is the string patrick repeated n times. The gap follows immediately after the last patrick of the word.

Below, p stands for patrick (7 characters) and · for a space:

Instruction Arity gap_arg Source (p=patrick, ·=space)
PUSH 0 1 0
PUSH 5 1 5 p······
POP 2 0 pp·
DUP 2 1 pp··
SWAP 2 2 pp···
ADD 3 0 ppp·
SUB 3 1 ppp··
EQ 4 0 pppp·
JUMP 0 5 0 ppppp·
JUMPZ 3 6 3 pppppp····
INCHAR 8 0 pppppppp·
OUTCHAR 8 1 pppppppp··
LOAD 9 0 ppppppppp·
HALT 10 0 pppppppppp
CALL 3 11 3 ppppppppppp····
RET 12 0 pppppppppppp·
PUSHN 5 13 5 ppppppppppppp······
PICK 2 14 2 pppppppppppppp···

A concrete example: OUTCHAR followed by HALT is the byte sequence patrickpatrickpatrickpatrickpatrickpatrickpatrickpatrick patrickpatrickpatrickpatrickpatrickpatrickpatrickpatrickpatrickpatrick (8 patricks, 2 spaces, 10 patricks).


8. Turing Completeness

PatrickScript is Turing complete. The proof is constructive: a Brainfuck interpreter written in PatrickScript, since Brainfuck is itself known Turing complete.

The concrete witness lives at examples/brainfuck.psa (assembler source) and examples/brainfuck.ps (compiled). It reads a Brainfuck program from stdin (terminated by EOF or the ! separator), then executes it. All eight Brainfuck instructions are implemented: + - > < [ ] . ,.

Two corpus tests pin the result:

The structural ingredients PatrickScript supplies to make this work:

The interpreter uses mem[0..2] for state (pc, source length, data pointer), mem[3..3+N-1] to hold the loaded Brainfuck source, and negative addresses mem[-(dp+1)] for the Brainfuck tape — keeping source and tape disjoint without large PUSH offsets.


9. Program Examples

9.1 Hello World (first character)

Output ASCII 72 ('H') and halt. Using p for patrick and · for space:

p········································································pppppppp··pppppppppp

Decoded: - PUSH 71 — arity=1, gap_arg=71: p followed by 72 spaces - OUTCHAR — arity=8, gap_arg=1: pppppppp followed by 2 spaces - HALT — arity=10, gap_arg=0: pppppppppp with no trailing spaces

Note: PUSH 71 requires 72 spaces after one patrick token. PatrickScript programs for non-trivial ASCII output are verbose by design. This is intentional: the verbosity is a property of the encoding, not an implementation choice.

9.2 Infinite counter

Print 0, 1, 2, ... indefinitely:

Instruction 0: PUSH 0        (arity=1, gap_arg=0) — push initial value
Instruction 1: DUP           (arity=2, gap_arg=1) — duplicate for print
Instruction 2: OUTNUM        (arity=8, gap_arg=3) — print number
Instruction 3: PUSH 1        (arity=1, gap_arg=1) — push increment
Instruction 4: ADD           (arity=3, gap_arg=0) — counter + 1
Instruction 5: JUMP 1        (arity=5, gap_arg=1) — loop back to DUP

9.3 Echo (copy stdin to stdout)

Read characters until EOF, writing each:

Instruction 0: INCHAR        (arity=8, gap_arg=0) — read byte or -1
Instruction 1: DUP           (arity=2, gap_arg=1) — dup to check for EOF
Instruction 2: PUSH 1        (arity=1, gap_arg=1) — push 1
Instruction 3: ADD           (arity=3, gap_arg=0) — eof+1; 0 if was -1
Instruction 4: JUMPZ 7       (arity=6, gap_arg=7) — if EOF, jump to HALT
Instruction 5: OUTCHAR       (arity=8, gap_arg=1) — write byte
Instruction 6: JUMP 0        (arity=5, gap_arg=0) — loop
Instruction 7: POP           (arity=2, gap_arg=0) — discard sentinel -1
Instruction 8: HALT          (arity=10, gap_arg=0) — done

9.4 FizzBuzz (1 to 15)

The canonical Turing-completeness demonstration. Uses memory (STORE/LOAD) to hold the loop counter and a printed-flag, MOD for divisibility tests, JUMPZ/JUMPNZ for branching, OUTCHAR for string output, and OUTNUM for integer output. The source examples/fizzbuzz.psa is 70 instructions; the compiled .ps form is 3,361 bytes.

Design sketch (register map: mem[0] = n, mem[1] = printed_flag):

; --- Init ---
PUSH 1; PUSH 0; STORE        ;; mem[0] = 1

main_loop:
  PUSH 0; LOAD               ;; n
  PUSH 15; GT; JUMPNZ done   ;; n > 15 → halt

  PUSH 0; PUSH 1; STORE      ;; printed_flag = 0

  ; Fizz check
  PUSH 0; LOAD; PUSH 3; MOD
  JUMPNZ check_buzz          ;; n%3 != 0 → skip
  OUTCHAR 'F','i','z','z'
  PUSH 1; PUSH 1; STORE      ;; printed_flag = 1

check_buzz:
  PUSH 0; LOAD; PUSH 5; MOD
  JUMPNZ check_num           ;; n%5 != 0 → skip
  OUTCHAR 'B','u','z','z'
  PUSH 1; PUSH 1; STORE      ;; printed_flag = 1

check_num:
  PUSH 1; LOAD; JUMPNZ print_nl ;; printed_flag != 0 → newline only
  PUSH 0; LOAD; OUTNUM           ;; otherwise print n (OUTNUM adds \n)
  JUMP next_iter

print_nl:
  PUSH 10; OUTCHAR           ;; '\n'

next_iter:
  PUSH 0; LOAD; PUSH 1; ADD
  PUSH 0; STORE              ;; n = n + 1
  JUMP main_loop

done:
  HALT

Output for 1..15:

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz

9.5 Factorial (n!) — iterative

Computes n! for an integer read from stdin. Uses STORE/LOAD for an accumulator register, MUL for iteration. The source corpus/factorial.psa computes 5! = 120.

Design sketch (register map: mem[0] = accumulator):

PUSH 1; PUSH 0; STORE    ;; mem[0] = 1 (acc)
INNUM                    ;; read n
loop:
  DUP; JUMPZ done        ;; n == 0 → done
  DUP
  PUSH 0; LOAD           ;; acc
  MUL
  PUSH 0; STORE          ;; acc = acc * n
  PUSH 1; SUB            ;; n = n - 1
  JUMP loop
done:
  POP
  PUSH 0; LOAD; OUTNUM   ;; print acc
  HALT

9.6 Subroutine Calling Convention

CALL pushes the return address on top of the value stack before jumping. This means on subroutine entry, the return address sits above any arguments the caller pushed — the opposite of most conventional calling conventions where the return address is at a known frame offset.

The idiomatic pattern is SWAP to expose arguments, SWAP back to restore. For a single-argument subroutine:

; Caller:
PUSH arg
CALL sub           ;; stack on entry to sub: [..., arg, ret_addr]

; Subroutine body:
sub:
  SWAP             ;; [..., ret_addr, arg]   — expose argument
  < ... work on arg ... >
  SWAP             ;; [..., result, ret_addr] — restore call frame
  RET              ;; returns; result is now on top for caller

For zero-argument subroutines (output-only, side-effect routines), the return address is already on top and no SWAP is needed:

; Caller:
CALL print_greeting

; Subroutine:
print_greeting:
  PUSH 72; OUTCHAR   ;; 'H'
  PUSH 105; OUTCHAR  ;; 'i'
  PUSH 10; OUTCHAR   ;; '\n'
  RET

For multi-argument subroutines, the convention extends via multiple SWAPs or ROT:

; Two-argument sub (a, b → result):
; Caller pushes a then b, then CALL.
; Entry stack: [..., a, b, ret_addr]

sub2:
  ROT              ;; [..., b, ret_addr, a]  — surface first arg
  SWAP             ;; [..., b, a, ret_addr]  — expose both args below ret
  < ... work on a and b ... >   ;; stack: [..., result, ret_addr]
  RET

Recursive subroutines work naturally: each CALL pushes a new return address above the current frame's values. The recursive factorial in corpus/factorial-recursive.psa demonstrates this pattern — each recursive level pushes its n and then CALL pushes the return address; SWAP is used at subroutine entry to reorder them for computation.

Key invariant: at each RET, the return address must be on top of the stack with the result(s) below it. Violations (wrong stack depth, wrong top-of-stack) cause jump-out-of-bounds or return to a garbage address. The assembler's label system makes CALL targets readable, but the stack discipline at RET is the programmer's responsibility.

9.7 Zero-Argument Subroutine Reuse

A zero-argument subroutine is the simplest reusable unit: no arguments to SWAP around, no result to thread through the stack frame. Because the return address arrives on top, the subroutine executes, then RETs without any frame-management overhead.

print_hello:
  .string "Hi\n"   ;; output-only side effect
  RET               ;; return address already on top

The same subroutine can be called any number of times:

CALL print_hello   ;; first call
CALL print_hello   ;; second call — same body, new return address
HALT

Each CALL pushes a fresh return address, so independent invocations share the body code but maintain separate return continuations. There is no state to reset between calls; zero-argument subroutines are naturally reentrant in a single-threaded sequential program.

This pattern suits output routines, print helpers, and repeated fixed-work blocks. The corpus/call-string.psa test demonstrates it: .string "Hi\n" + RET, called twice.

9.8 PICK for Non-Destructive Stack Access (v1.3.0)

PICK reads a value from depth n without consuming it, avoiding the need to pop-and-reorder when the same value is needed more than once.

PICK 0 is equivalent to DUP: it copies the top element.

PICK n (n > 0) copies an element buried below the top. The primary idiom: computing an expression that uses a value from deeper in the stack without removing it from its position.

Example — compute a + b + a using PICK 1 to reuse a:

; a=3, b=7; compute 3 + 7 + 3 = 13
PUSH 3      ;; stack: [3]
PUSH 7      ;; stack: [3 7]  (7 on top)
PICK 1      ;; copy a (depth 1): [3 7 3]
ADD         ;; [3 10]
ADD         ;; [13]
OUTNUM      ;; outputs 13
HALT

Without PICK, accessing a after pushing b would require storing a to memory and reloading it. PICK avoids the memory round-trip when the value is already on the stack.

The corpus/pick-deep.psa test demonstrates accessing multiple depths: PICK 2 and PICK 1 used to non-destructively copy the first and second elements of a three-element stack.


10. Versioning

v1.0.0 (2026-05-15)

Initial release. Stack machine with 10 arities: PUSH (1), stack ops (2), arithmetic (3), comparison/bitwise (4), JUMP/JUMPZ/JUMPNZ (5–7), I/O (8), memory (9), HALT (10). Arities 11+ reserved.

v1.1.0 (2026-05-15)

Adds subroutine support: CALL (arity 11) and RET (arity 12). Arities 13+ remain reserved. A v1.1.0-conformant interpreter executes CALL and RET as specified in section 5.8; it treats arities ≥ 13 as runtime errors. A v1.0.0-only interpreter that encounters arity 11 or 12 is permitted to treat them as runtime errors (reserved instruction).

v1.2.0 (2026-05-15)

Adds PUSHN (arity 13, gap_arg = n): pushes −n onto the stack. Provides single-instruction negative literal encoding. The two-instruction idiom PUSH n / NEG remains valid and equivalent; PUSHN is a convenience instruction. A v1.2.0-conformant interpreter executes PUSHN as specified in section 5.9; it treats arities ≥ 14 as runtime errors.

Arities 14+ remain reserved in v1.2.0.

v1.3.0 (2026-05-15)

Adds PICK (arity 14, gap_arg = n): copies the element at depth n from the top of the stack (0 = top). Provides direct access to buried stack values without destructive reordering. A v1.3.0-conformant interpreter executes PICK as specified in section 5.10; it treats arities ≥ 15 as runtime errors. A v1.2.0-only interpreter that encounters arity 14 is permitted to treat it as a runtime error.

Arities 15+ remain reserved.

Future versions add instructions via currently-reserved arities (15+) or extend the gap_arg space for existing arities.

Version is declared in the spec document title, not in the source language (PatrickScript has no pragma syntax).


11. Formal Summary