We are currently working on new rules for what content should and shouldn't be allowed on this website, and are looking for feedback! See Esolang:2026 topicality proposal to view and give feedback on the current draft.
Crawssembly
| Paradigm(s) | Imperative, assembly |
|---|---|
| Designed by | Jonah Crawford |
| Appeared in | 2025 |
| Memory system | Cell-based |
| Dimensions | one-dimensional |
| Computational class | Turing complete |
| Reference implementation | Crawssembly |
Crawssembly (commonly abbreviated casm) is an educational assembly-like programming language, instruction set and virtual machine created by Jonah Crawford in 2025. It is designed as an approachable introduction to low-level programming and computer architecture.
Rather than targeting an existing processor architecture, Crawssembly defines its own fictional computer. The architecture uses 32-bit signed data, 256 registers, 21-bit machine instructions, numeric labels, random-access memory, persistent storage, and a collection of virtual I/O devices. Programs can interact with a terminal, framebuffer, keyboard, mouse and synthesised audio in addition to ordinary memory and file input/output.
Crawssembly is unusual among languages documented on the Esolang Wiki in that esotericism was not one of its original design goals. It is deliberately intended to make low-level programming easier, rather than more difficult. Its esoteric characteristics instead arise from the design of its artificial processor architecture and from features such as 21-bit instructions, 256 registers, dynamically executable machine code, unusual control flow, and high-level facilities implemented as assembly-language libraries.
The reference implementation is primarily written in Rust and consists of a compiler and virtual machine. The wider project includes the Crawssembly Standard Library (CSL), the Crawssembly Package Manager (CPM), and an embeddable Rust library.
Design philosophy
Crawssembly was designed as a bridge between high-level programming and conventional assembly language.
Real-world instruction sets such as x86 and ARM contain numerous architectural features resulting from performance requirements, backwards compatibility and the history of the hardware they target. Crawssembly instead begins with the concepts that the programmer is intended to learn and constructs an artificial machine around them.
Consequently, the programmer is exposed directly to concepts including:
- registers;
- signed binary integers;
- arithmetic and bitwise operations;
- jumps and conditional execution;
- memory addresses;
- volatile and persistent storage;
- machine-code encoding;
- the fetch-decode-execute cycle;
- memory-mapped-style device interaction;
- stacks and heaps;
- executable code represented as data.
The core virtual machine deliberately leaves out several operations that would ordinarily be expected from a practical architecture. For example, multiplication, division, exponentiation, stacks, and dynamic memory allocation are implemented in Crawssembly itself rather than being fundamental processor operations.
This gives Crawssembly two levels of abstraction: a deliberately small virtual processor, and software written for that processor which gradually constructs more sophisticated facilities.
The Crawssembly machine
A Crawssembly program executes on a register-based virtual machine.
The machine's ordinary values are signed 32-bit integers represented using two's complement.
Registers
There are 256 registers, numbered hexadecimal from r00 through rff:
r00 r01 r02 ... r09 r0a ... rfe rff
Most registers are general-purpose, but several have special meanings.
| Register | Purpose |
|---|---|
r00
|
Program input. This register exposes bytes supplied through an input file. |
r01
|
Arithmetic and comparison result register. Operations performed by cal store their result here, and most conditional instructions inspect it.
|
ree
|
Error information, including errors reported by I/O operations and standard-library routines. |
ref
|
Character output. Writing a character code to this register prints the corresponding character. |
rff
|
Binary output. The low eight bits of values written here are appended to the program's output. |
This means that the small program
sav 72 ref sav 105 ref
prints:
Hi
while
sav 72 rff sav 105 rff
writes the two bytes representing Hi to the program's binary output.
Constant registers
Fourteen registers near the end of the register space contain predefined constants.
| Register | Value | Meaning |
|---|---|---|
rf0 |
314159265 | π × 10⁸ |
rf1 |
271828182 | e × 10⁸ |
rf2 |
30102999 | log₁₀(2) × 10⁸ |
rf3 |
47712125 | log₁₀(3) × 10⁸ |
rf4 |
69897000 | log₁₀(5) × 10⁸ |
rf5 |
69314718 | ln(2) × 10⁸ |
rf6 |
109861228 | ln(3) × 10⁸ |
rf7 |
160943791 | ln(5) × 10⁸ |
rf8 |
141421356 | √2 × 10⁸ |
rf9 |
173205080 | √3 × 10⁸ |
rfa |
223606797 | √5 × 10⁸ |
rfb |
125992105 | ∛2 × 10⁸ |
rfc |
144224957 | ∛3 × 10⁸ |
rfd |
170997594 | ∛5 × 10⁸ |
rfe |
2147483647 | 2³¹ − 1 |
The mathematical constants are scaled by 10⁸ because Crawssembly's basic data type is an integer.
Basic syntax
Crawssembly source files conventionally use the .craw extension.
Instructions are line-oriented:
sav 10 r02 cal add 5 r02 io text int r01 stp
This program stores 10 in r02, adds 5 to it, prints the resulting value of 15, and terminates.
Comments begin with a semicolon:
sav 42 r02 ; the answer
Whitespace is not structurally significant, although indentation is commonly used around conditional regions.
Immediate operands are signed eight-bit integers, giving an ordinary immediate range of −128 to 127. Larger values must therefore be constructed, loaded, or obtained by other means.
Core instructions
Despite the size of the virtual machine's environment, the CPU instruction set is relatively small.
sav
sav copies a value into a register.
sav 42 r02 sav r02 r03
The first instruction places 42 into r02. The second copies the contents of r02 into r03.
cal
All primitive arithmetic and bitwise operations are performed using cal:
cal OPERATION VALUE REGISTER
The result is always placed in r01.
Eight primitive operations are available:
| Operation | Meaning |
|---|---|
not |
bitwise NOT |
and |
bitwise AND |
or |
bitwise OR |
xor |
bitwise XOR |
shl |
logical shift left |
shr |
logical shift right |
sar |
arithmetic shift right |
add |
addition |
For example,
sav 17 r02 cal add 25 r02
leaves 42 in r01.
There is deliberately no primitive multiplication or division instruction.
inp, nop and stp
inp advances the input stream to its next byte.
nop performs no operation.
stp terminates execution.
Labels and control flow
Labels in Crawssembly are numeric.
A line containing only a number defines a label:
100
The basic unconditional jump is jmp:
1 sav 65 ref jmp 1
This program prints A indefinitely.
Conditional jumps
Conditional jumps examine r01:
| Instruction | Behaviour |
|---|---|
jmg n |
Jump to n if r01 > 0
|
jmz n |
Jump to n if r01 = 0
|
jml n |
Jump to n if r01 < 0
|
Thus a conventional loop can be constructed as:
sav 10 r02 1 io text int r02 io text newline rff cal add -1 r02 sav r01 r02 jmg 1 stp
which outputs the integers from 10 down to 1.
Conditional regions
Crawssembly also contains an unusual second form of conditional execution.
ifg, ifz and ifl begin conditional regions, while rmv terminates them.
| Instruction | Execute region if |
|---|---|
ifg n |
r01 > 0
|
ifz n |
r01 = 0
|
ifl n |
r01 < 0
|
For example:
sav 10 r01 ifg 100 sav 89 ref sav 101 ref sav 115 ref rmv 100
prints Yes.
The indentation has no semantic significance. The numbered scope and corresponding rmv determine the region.
Indirect jumps
fgo provides indirect control flow.
For an ordinary non-zero operand,
fgo 42
transfers execution to label 42.
However,
fgo 0
uses the value currently stored in r01 as the label identifier.
Consequently, a program can calculate its next destination at runtime. This can be used to implement constructs such as jump tables and dispatch systems.
Input and output
Hardware-like facilities are accessed through a single io instruction:
io DEVICE COMMAND REGISTER
Unlike sav and cal, I/O operations take register operands rather than immediate values.
The architecture currently defines eight device classes:
| Device | Purpose |
|---|---|
text |
terminal output and formatting |
time |
timestamps and delays |
screen |
graphical framebuffer |
keyboard |
keyboard state |
mouse |
mouse state |
speaker |
audio synthesis |
mem |
volatile random-access memory |
disk |
persistent storage |
This arrangement keeps peripheral operations outside the core arithmetic instruction set.
I/O operations can report errors through ree. Defined errors distinguish successful operations, invalid commands, invalid values and unavailable devices.
Text output
The text device can output characters, signed integers and hexadecimal values:
sav 42 r02 io text int r02 io text newline rff io text hex r02
It also supports spaces, terminal clearing, timestamps and RGB terminal colours.
For single characters, writing directly to ref is shorter:
sav 67 ref sav 114 ref sav 97 ref sav 119 ref
Graphics
Crawssembly includes a virtual graphical display implemented inside the terminal.
The programmer selects an X coordinate, Y coordinate and colour, modifies pixels in an internal framebuffer, and then explicitly presents the completed frame.
For example:
sav 5 r01 sav 255 r02 io screen x r01 io screen y r01 io screen red r02 io screen green r02 io screen blue r02 io screen pixel rff io screen present rff
draws a white pixel at (5, 5).
Screen operations include:
- setting X and Y coordinates;
- setting red, green and blue components;
- drawing a pixel;
- erasing a pixel;
- clearing the framebuffer;
- presenting the framebuffer;
- dumping a simplified representation of the screen.
Higher-level drawing operations such as lines, rectangles and circles are not CPU instructions. They are implemented in the standard library.
Keyboard and mouse
Keyboard input is obtained with:
io keyboard poll r02
Printable keys use character codes. Special keys are represented using additional values.
Keyboard modifier state is available as a bit field containing Shift, Control, Alt and Super.
Mouse input similarly provides X and Y coordinates and a bit field representing the mouse buttons.
These facilities allow interactive programs and games to be written without adding special language constructs for them.
Sound
Crawssembly provides eight virtual audio channels.
A program selects a channel and configures its frequency, volume and waveform before enabling it:
sav 0 r02 io speaker channel r02 sav 440 r02 io speaker freq r02 sav 50 r02 io speaker volume r02 io speaker on rff
Available waveforms include square, sine, triangle, sawtooth and noise.
Together with the time device, this is sufficient for programs to synthesise tones and simple music.
Memory
Registers provide only a small amount of directly addressable state, so Crawssembly also exposes random-access memory.
Memory uses an active address model. The program first selects an address:
sav 100 r02 io mem addr r02
and can then read or write the corresponding cell:
sav 42 r03 io mem write r03 io mem read r04
After these operations, r04 contains 42.
The documented address representation permits 32-bit addresses, although the reference virtual machine allocates a smaller finite amount of physical storage. Thus the architectural address space and the storage supplied by a particular implementation are distinct.
Persistent storage
The disk device follows the same active-address model as memory:
sav 100 r02 io disk addr r02 sav 42 r03 io disk write r03
Unlike ordinary memory, disk storage persists between executions.
The program can explicitly request that the backing storage be saved:
io disk save rff
This gives Crawssembly programs access to persistent mutable state without requiring a filesystem abstraction inside the virtual architecture.
Machine code
Crawssembly source is compiled to a custom fixed-width 21-bit instruction format.
Most instructions have the form:
aa bbb cccccccc dddddddd
The fields are interpreted differently depending on the instruction class.
Broadly:
aaselects a core instruction group;bbbselects an operation or control mode;- the remaining two eight-bit fields contain registers, immediate values, device commands, or portions of a label identifier.
This gives exactly 2²¹ possible 21-bit instruction words.
Basic encodings
| Source | Encoding |
|---|---|
nop
|
00 000 00000000 00000000
|
sav rA rB
|
00 000 aaaaaaaa bbbbbbbb
|
sav imm rB
|
01 000 iiiiiiii bbbbbbbb
|
cal op rA rB
|
10 ooo aaaaaaaa bbbbbbbb
|
cal op imm rB
|
11 ooo iiiiiiii bbbbbbbb
|
run rA
|
01 010 00000000 aaaaaaaa
|
inp
|
01 100 00000000 00000000
|
stp
|
01 111 11111111 11111111
|
The arithmetic field ooo encodes the eight cal operations.
Control-flow instructions use a 16-bit field for their label identifier.
The io encoding divides one eight-bit field into a four-bit device identifier and four-bit command identifier:
01 110 dddd cccc rrrrrrrr
Thus the source syntax is closely related to the layout of the underlying virtual instruction word rather than being an unrelated textual notation.
Writing machine code directly
Crawssembly permits an encoded instruction to appear directly in source.
For example:
010000011010000000001 io text int r01
is equivalent to:
sav 100 r01 io text int r01
This makes the relationship between the assembly syntax and the fictional machine code directly observable to the programmer.
Dynamic instruction execution
One of Crawssembly's more unusual instructions is run.
run REGISTER
The low 21 bits of the register are interpreted as a Crawssembly machine instruction and immediately executed.
For example:
sav 0x82A03 r02 run r02
interprets 0x82A03 as:
sav 42 r03
and consequently stores 42 in r03.
This is not a separate interpreter invocation. The generated instruction executes in the current machine context and can alter registers, access devices, stop the program or modify control flow just as an instruction compiled normally would.
Code as data
Since machine instructions are ordinary 21-bit values, they can be stored and manipulated like other integers.
An instruction may therefore be:
- stored in a register;
- stored in memory;
- written to persistent storage;
- read from persistent storage;
- modified;
- generated algorithmically;
- executed using
run.
For example:
sav 0 r01 io disk addr r01 io disk read r02 run r02
reads an instruction from disk address zero and executes it.
This provides a basis for loaders, interpreters, dynamically generated code and self-modifying programs written entirely within Crawssembly.
Compilation and execution
The reference implementation conceptually consists of two stages: the compiler and the executioner.
The compiler converts textual Crawssembly instructions into 21-bit machine instructions. The resulting program is then executed by the virtual machine.
Execution follows a conventional fetch-decode-execute model:
┌──────── Virtual machine ────────┐
│ │
program.craw ──► compiler ──► machine code │
│ │ │
│ ▼ │
│ fetch │
│ │ │
│ ▼ │
│ decode │
│ │ │
│ ▼ │
│ execute ──────┐ │
│ ▲ │ │
│ └───────────┘ │
└─────────────────────────────────┘
The fictional CPU therefore has a machine-code representation independently of the source syntax.
Macros and source inclusion
Crawssembly has no primitive high-level function-call mechanism.
Instead, source can be included at compile time:
execute path/to/program.craw
The contents of the specified source file are inserted into the program during compilation.
Because this is source inclusion rather than a conventional function call, the included code shares the surrounding program's machine state.
Standard-library routines use:
executestd path/to/library.craw
This mechanism forms the basis of the Crawssembly Standard Library.
Crawssembly Standard Library
The Crawssembly Standard Library (CSL) implements facilities that would ordinarily be built into a larger instruction set or programming language.
Rather than hiding these facilities inside the VM, CSL implements them as Crawssembly programs.
Among its operations are:
- equality and comparison;
- multiplication;
- integer division;
- modulo;
- exponentiation;
- absolute value;
- negation;
- pseudorandom number generation;
- line drawing;
- rectangle drawing;
- circle drawing;
- stack operations;
- heap allocation.
Calling convention
Library routines conventionally receive arguments beginning at r02 and return results beginning at r02.
For example:
sav 12 r02 sav 5 r03 executestd math/multiply.craw io text int r02
outputs:
60
Each CSL routine documents its scope: the registers, labels and memory locations that it may alter.
This is important because library execution occurs inside the same virtual machine state as the calling program.
Implementing multiplication
Multiplication demonstrates the distinction between the Crawssembly CPU and software constructed on top of it.
There is no mul instruction. At the lowest level, multiplication must be reduced to operations such as addition, shifting and control flow.
A simple implementation could repeatedly add one operand:
; conceptual multiplication of r02 × r03 sav 0 r04 1 sav r04 r01 cal add r02 r04 sav r01 r04 cal add -1 r03 sav r01 r03 jmg 1
The standard library can provide a reusable and more complete implementation while the processor itself remains small.
This is a recurring design pattern in Crawssembly: capabilities are preferably constructed in Crawssembly when they do not need to exist as privileged machine operations.
The stack
Crawssembly does not have a hardware stack instruction.
CSL implements one using ordinary memory.
The stack is initialised using:
executestd stack/init.craw
and values can then be pushed and popped:
sav 42 r02 executestd stack/push.craw sav 17 r02 executestd stack/push.craw executestd stack/pop.craw io text int r02 executestd stack/pop.craw io text int r02
which retrieves 17 before 42.
The stack pointer is itself part of the program-visible state. Consequently, the stack is not a privileged structure supplied by the virtual processor: it is merely a convention implemented using memory and Crawssembly instructions.
The heap
Dynamic memory allocation is similarly implemented in CSL rather than by the virtual machine.
A program first initialises the allocator and can then request a block:
executestd heap/init.craw sav 100 r02 executestd heap/alloc.craw
The allocator searches its managed memory for a sufficiently large free block and returns an address.
The resulting address is an ordinary Crawssembly value and can be used with the memory device:
sav r02 r10 io mem addr r10 sav 42 r02 io mem write r02
Allocated memory can later be released:
sav r10 r02 executestd heap/free.craw
Allocation metadata is stored in the same memory available to ordinary programs.
Thus dynamic allocation, like the stack, is an emergent software convention rather than a special property of the VM.
Graphics library
The framebuffer exposes individual pixels, but higher-level geometry is provided by CSL.
Line drawing is implemented using a Bresenham-style integer algorithm. Rectangles and circles are similarly implemented in software.
This distinction allows the programmer to observe the complete chain from a high-level operation such as:
executestd graphics/line.craw
down to repeated manipulation of individual virtual pixels.
Example: keyboard echo
The following illustrates a basic interactive loop:
1
io keyboard poll r02
sav r02 r01
ifg 2
io text char r02
rmv 2
jmp 1
The program repeatedly polls the keyboard and prints received character codes.
Even simple interactive programs therefore expose the polling loop explicitly.
Example: persistent counter
Because disk state survives execution, a program can maintain a persistent value:
sav 0 r02 io disk addr r02 io disk read r03 cal add 1 r03 sav r01 r03 io text int r03 io disk write r03 io disk save rff stp
Each execution reads the previous value, increments it, displays it and writes it back.
Example: generated instruction
The run instruction permits the machine-code representation itself to become part of a program's data model.
sav 0x82A03 r02 run r02 io text int r03 stp
The value 0x82A03 is not merely an arbitrary constant: its low 21 bits encode sav 42 r03.
This permits a Crawssembly program to operate simultaneously on ordinary data and on representations of its own instruction set.
Implementation
The primary implementation is written in Rust and is available for Linux, Windows and macOS.
The command-line program craw compiles and executes Crawssembly source.
A minimal invocation is:
craw program.craw
The implementation also exposes options controlling facilities such as the virtual display, benchmarking and input files.
The virtual machine is also available as an embeddable Rust component, allowing other programs to execute Crawssembly code and inspect the resulting machine state.
Crawssembly Package Manager
The Crawssembly Package Manager (CPM) provides a registry for reusable Crawssembly software.
Packages can be installed using:
craw pkg install PACKAGE
and installed libraries can subsequently be incorporated into programs through executestd.
CPM supports operations for packaging, publishing, installing, listing, updating and removing packages. Published package metadata includes information such as package name, version, author, description and integrity information.
The existence of a package manager is somewhat unusual for a small experimental assembly language, but follows from Crawssembly's development beyond individual demonstration programs into a reusable programming environment.
Example programs and software
Programs written for Crawssembly include or have included examples demonstrating:
- bitmap loading;
- keyboard input;
- graphical drawing;
- memory inspection;
- register inspection;
- timers;
- arithmetic routines;
- a command shell;
- text editing;
- persistent storage;
- networking;
- dynamically executed code.
Why 21 bits?
Crawssembly's machine instructions are neither byte-aligned nor powers of two in width.
The instruction format is instead sized around the information needed by the architecture:
2 bits core group 3 bits operation/mode 8 bits operand 8 bits operand ---------------------- 21 bits total
This allows two full register identifiers or immediate operands to coexist with an instruction class and operation identifier without expanding the conceptual instruction format merely to obtain a conventional byte-aligned size.
As a consequence, Crawssembly binaries have an unusual relationship with ordinary host-machine storage: the virtual instruction word is 21 bits even though the implementation necessarily stores or packs it using conventional host data structures.
Esoteric properties
Crawssembly was not intentionally designed as an esoteric programming language.
Its original purpose is educational, and many of its choices are intended to make assembly programming less intimidating. Nevertheless, it exhibits several properties unusual enough to place it naturally alongside experimental and esoteric languages:
- the language targets an entirely fictional CPU;
- instructions are 21 bits wide;
- the processor exposes 256 registers;
- several mathematical constants occupy predefined registers;
- immediate values are only eight bits despite registers holding 32-bit values;
- arithmetic operations share a single
calinstruction; - multiplication and division are library routines rather than processor instructions;
- conditional regions are associated with numbered labels and terminated using
rmv; fgo 0performs a computed jump usingr01;- peripherals are accessed through a unified
ioinstruction; - instructions can be represented as integer data and executed dynamically;
- a stack and heap are implemented in the language itself;
- the same system encompasses assembly syntax, machine-code encoding and the virtual architecture that executes it.
The result is closer to an imaginary computer architecture than to an intentionally difficult notation.
Relationship between casm and Crawssembly
The terms casm and Crawssembly are frequently used interchangeably.
More precisely, casm can refer to the assembly language and instruction syntax, while Crawssembly can refer to the larger system consisting of the language, virtual architecture, implementation, standard library and related tooling.
In ordinary usage this distinction is not generally important.
Computational class
Crawssembly is Turing-complete, as it is capable of implementing an interpreter for Brainfuck, which is itself Turing-complete.
The following implementation stores the Brainfuck source program in memory addresses 0x0000–0x0fff and uses 0x1000–0x1fff as the Brainfuck tape. It supports all eight Brainfuck instructions, including nested loops. Running this program using craw brainfuck.craw --file program.bf will execute the brainfuck program proper.
This a simple, unoptimised Brainfuck interpreter written in Crawssembly. Brainfuck source code is supplied to the interpreter using the Crawssembly input file.
sav 0 r01 ; init scratch register
sav 0 r02 ; init memory address
1 ; loader loop
sav r00 r01 ; ready input value
ifg 2 ; input > 0
io mem addr r02 ; access memory address
io mem write r00 ; write input
cal add 1 r02 ; increment address
sav r01 r02 ; update address
inp ; get next input value
jmp 1 ; continue loader loop
rmv 2 ; end input check
sav 0 r10 ; init bf pc (brainfuck program counter)
sav 0 r12 ; init tpv (tape pointer value)
sav 32 r01 ; 0b0..11111
sav 7 r02 ; shift amount
cal shl r01 r02 ; 0b0..111110000000 (0x01000)
sav r01 r11 ; init tp (tape pointer)
sav 0 r01 ; reset scratch register
1 ; main loop
io mem addr r10 ; access bf symbol
io mem read r04 ; get symbol into r04
io mem addr r11 ; access tp value
io mem read r12 ; get tpv
sav r04 r01 ; ready symbol code
cal add -62 r01 ; > symbol found
ifz 2 ; > branch
cal add 1 r11 ; increment tp
sav r01 r11 ; update tp
cal add 1 r10 ; next bfpc
sav r01 r10 ; update bfpc
jmp 1 ; continue loop
rmv 2 ; end > branch
sav r04 r01 ; ready symbol code
cal add -60 r01 ; < symbol found
ifz 2 ; < branch
cal add -1 r11 ; decrement tp
sav r01 r11 ; update tp
cal add 1 r10 ; next bfpc
sav r01 r10 ; update bfpc
jmp 1 ; continue loop
rmv 2 ; end < branch
sav r04 r01 ; ready symbol code
cal add -43 r01 ; + symbol found
ifz 2 ; + branch
cal add 1 r12 ; increment tpv
io mem addr r11 ; access tpv
io mem write r01 ; update tpv
cal add 1 r10 ; next bfpc
sav r01 r10 ; update bfpc
jmp 1 ; continue loop
rmv 2 ; end + branch
sav r04 r01 ; ready symbol code
cal add -45 r01 ; - symbol found
ifz 2 ; - branch
cal add -1 r12 ; decrement tpv
io mem addr r11 ; access tpv
io mem write r01 ; update tpv
cal add 1 r10 ; next bfpc
sav r01 r10 ; update bfpc
jmp 1 ; continue loop
rmv 2 ; end - branch
sav r04 r01 ; ready symbol code
cal add -46 r01 ; . symbol found
ifz 2 ; . branch
io text char r12 ; output tape char
cal add 1 r10 ; next bfpc
sav r01 r10 ; update bfpc
jmp 1 ; continue loop
rmv 2 ; end . branch
sav r04 r01 ; ready symbol code
cal add -44 r01 ; , symbol found
ifz 2 ; , branch
3 ; get key loop
io keyboard poll r01 ; get key press
ifz 4 ; no key
jmp 3 ; continue loop
rmv 4 ; end no key check
io mem addr r11 ; access tpv
io mem write r01 ; tpv = key
rmv 3 ; free label
cal add 1 r10 ; next bfpc
sav r01 r10 ; update bfpc
jmp 1 ; continue loop
rmv 2 ; end , branch
sav r04 r01 ; ready symbol code
cal add -91 r01 ; [ symbol found
ifz 2 ; [ branch
sav r12 r01 ; ready tpv
ifz 3 ; tpv = 0, start depth walk to find ]
sav 0 r05 ; init symbol offset value
sav 0 r06 ; init offsetted symbol
sav 0 r07 ; init depth value (+1 for [, -1 for ])
4 ; walk loop
cal add 1 r05 ; increment symbol offset value
sav r01 r05 ; update symbol offset value
cal add r01 r10 ; bfpc + offset
io mem addr r01 ; get offsetted bfpc
io mem read r06 ; read walked symbol
cal add -93 r06 ; test for ]
ifz 5 ; ] found
cal add -1 r07 ; decrease depth
sav r01 r07 ; update depth
ifl 6 ; depth < 0 (match found)
cal add r05 r10 ; get symbol address
cal add 1 r01 ; first symbol after matching ]
sav r01 r10 ; update bfpc
jmp 1 ; continue main loop
rmv 6 ; end match check
jmp 4 ; no match, continue walk
rmv 5 ; end ] check
cal add -91 r06 ; test for [
ifz 5 ; [ found
cal add 1 r07 ; increase depth
sav r01 r07 ; update depth
jmp 4 ; continue walk
rmv 5 ; end [ check
jmp 4 ; different symbol, continue walk
rmv 4 ; free label
rmv 3 ; end depth walk
cal add 1 r10 ; tpv != 0, continue
sav r01 r10 ; update bfpc
jmp 1 ; continue loop
rmv 2 ; end [ branch
sav r04 r01 ; ready symbol code
cal add -93 r01 ; ] symbol found
ifz 2 ; ] branch
sav r12 r01 ; ready tpv
ifg 3 ; tpv > 0, start depth walk to find [
sav 0 r05 ; init symbol offset value
sav 0 r06 ; init offsetted symbol
sav 0 r07 ; init depth value (+1 for ], -1 for [)
4 ; walk loop
cal add -1 r05 ; decrement symbol offset value
sav r01 r05 ; update symbol offset value
cal add r01 r10 ; bfpc + offset
io mem addr r01 ; get offsetted bfpc
io mem read r06 ; read walked symbol
cal add -91 r06 ; test for [
ifz 5 ; [ found
cal add -1 r07 ; decrease depth
sav r01 r07 ; update depth
ifl 6 ; depth < 0 (match found)
cal add r05 r10 ; get symbol address
cal add 1 r01 ; first symbol after matching [
sav r01 r10 ; update bfpc
jmp 1 ; continue main loop
rmv 6 ; end match check
jmp 4 ; no match, continue walk
rmv 5 ; end [ check
cal add -93 r06 ; test for ]
ifz 5 ; ] found
cal add 1 r07 ; increase depth
sav r01 r07 ; update depth
jmp 4 ; continue walk
rmv 5 ; end ] check
jmp 4 ; different symbol, continue walk
rmv 4 ; free label
rmv 3 ; end tpv > 0 check
sav r12 r01 ; ready tpv
ifl 3 ; tpv < 0, start depth walk to find [
sav 0 r05 ; init symbol offset value
sav 0 r06 ; init offsetted symbol
sav 0 r07 ; init depth value (+1 for ], -1 for [)
4 ; walk loop
cal add -1 r05 ; decrement symbol offset value
sav r01 r05 ; update symbol offset value
cal add r01 r10 ; bfpc + offset
io mem addr r01 ; get offsetted bfpc
io mem read r06 ; read walked symbol
cal add -91 r06 ; test for [
ifz 5 ; [ found
cal add -1 r07 ; decrease depth
sav r01 r07 ; update depth
ifl 6 ; depth < 0 (match found)
cal add r05 r10 ; get symbol address
cal add 1 r01 ; first symbol after matching [
sav r01 r10 ; update bfpc
jmp 1 ; continue main loop
rmv 6 ; end match check
jmp 4 ; no match, continue walk
rmv 5 ; end [ check
cal add -93 r06 ; test for ]
ifz 5 ; ] found
cal add 1 r07 ; increase depth
sav r01 r07 ; update depth
jmp 4 ; continue walk
rmv 5 ; end ] check
jmp 4 ; different symbol, continue walk
rmv 4 ; free label
rmv 3 ; end tpv < 0 check
cal add 1 r10 ; tpv = 0, continue
sav r01 r10 ; update bfpc
jmp 1 ; continue loop
rmv 2 ; end [ branch
sav r04 r01 ; EOF character ready
ifz 2 ; code = 0 => EOF
stp ; end program
rmv 2 ; end EOF check
cal add 1 r10 ; next bfpc
sav r01 r10 ; update bfpc
jmp 1 ; continue main loop
Example
Given the Brainfuck program:
++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.
the interpreter produces:
Hello World!
As with any implementation running on a physical computer, the reference Crawssembly implementation has finite memory; Turing-completeness refers to the language's computational model under the usual assumption of unbounded available memory.
It should be noticed that the interpreter's cells are Crawssembly integer cells, being 32-bit width, rather than the common 8-bit wrapping Brainfuck implementation. This doesn't affect Crawssembly's computational class in any way, but for brainfuck programs that utilise this feature, not all programs will function as expected.
Development
Crawssembly remains under development.
Its scope has expanded considerably from its original role as a small educational assembly environment. Later additions have included a standard library, graphical and audio devices, persistent storage, dynamic instruction execution, reusable packages and increasingly complex software written for the architecture.
Despite this expansion, the core design principle remains that functionality should be implemented on the fictional machine where practical rather than continually enlarging the CPU instruction set.