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

From Esolang
Jump to navigation Jump to search
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:

  • aa selects a core instruction group;
  • bbb selects 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 cal instruction;
  • multiplication and division are library routines rather than processor instructions;
  • conditional regions are associated with numbered labels and terminated using rmv;
  • fgo 0 performs a computed jump using r01;
  • peripherals are accessed through a unified io instruction;
  • 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.


See also

External resources