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.

Symblicity

From Esolang
Jump to navigation Jump to search

Symblicity is a low-level esoteric programming language with five 8-bit registers, a unified 65,536-byte address space, a double-ended data stack, a dedicated nested return stack, bidirectional execution, direction-sensitive letter jumps, stateful binary file I/O, and optional asynchronous audio.

Every non-formatting character in a .sym file has a meaning. Newline, carriage-return, form-feed, and vertical-tab characters are ignored as formatting. A literal TAB begins a comment and ignores the rest of that physical source line. A literal ASCII space is an instruction that skips the next instruction.

Instruction set

Registers

Symblicity operates on five 8-bit registers: R0 through R4.

There are two selectable register banks:

Set A B Shared result
0 R0 R1 R2
1 R3 R4

The \ instruction switches between the two register banks. Unless otherwise specified, instructions referring to A or B operate on the currently selected bank.

All register arithmetic uses 8-bit wraparound.

Constants

Instruction Operation Description
0 A += 1 Adds 20 to A.
1 A += 2 Adds 21 to A.
2 A += 4 Adds 22 to A.
3 A += 8 Adds 23 to A.
4 A += 16 Adds 24 to A.
5 A += 32 Adds 25 to A.
6 A += 64 Adds 26 to A.
7 A += 128 Adds 27 to A.
8 A -= 1 Subtracts 1 from A.
9 A -= 2 Subtracts 2 from A.

For example:

4320

adds:

16 + 8 + 4 + 1 = 29

to A.

Arithmetic and bitwise operations

Arithmetic and bitwise operations use the selected A and B registers and store their result in the shared R2 register.

Instruction Operation Description
+ R2 = A + B Addition.
- R2 = A - B Subtraction.
* R2 = A * B Multiplication.
/ R2 = A / B Unsigned integer division. Division by zero produces 0.
% R2 = A % B Unsigned modulo. Modulo by zero produces 0.
^ R2 = A ^ B Bitwise XOR.
| R2 = A | B Bitwise OR.
& R2 = A & B Bitwise AND.

Register operations

Instruction Operation Description
\ Switch register bank Toggles between R0/R1 and R3/R4.
: Swap A and B Exchanges the selected A and B registers.
; Swap B and R2 Exchanges the selected B register with the shared R2 register.
` Clear A Sets the selected A register to zero.

Conditional skipping

Conditional instructions test the current register state. If their condition is satisfied, the next instruction in the current execution direction is skipped.

Instruction Condition Description
= A == B Skip the next instruction if A equals B.
< A < B Skip the next instruction if A is less than B.
> A > B Skip the next instruction if A is greater than B.
! A != B Skip the next instruction if A does not equal B.
? R2 == 0 Skip the next instruction if the shared result register is zero.
Space Always Unconditionally skips the next instruction in the current direction.

A literal ASCII space is therefore executable and should not be used merely for source formatting.

Input and output

Character and numeric I/O use the selected primary register A:

  • R0 while register bank 0 is active.
  • R3 while register bank 1 is active.
Instruction Operation Description
' Byte input → A Reads one byte of input into A.
" Output A Outputs A as one byte/character.
. Decimal input → A Reads a decimal integer and stores its low 8 bits in A.
, Print A Outputs A as an unsigned decimal number.

The reference interpreter supports a configurable input wait for ' and .:

  • --input-wait infinite or --input-wait -1 blocks indefinitely and is the default.
  • --input-wait 0 performs a nonblocking poll.
  • --input-wait N waits for up to N milliseconds, where N is 0 through 60000.
  • --input-timeout is a compatibility alias for --input-wait.

Infinite mode preserves the original input behavior and leaves R2 unchanged. In finite or nonblocking mode, successful input stores the value in A and sets R2 = 1. Timeout or EOF sets R2 = 0 and leaves A unchanged. A partial decimal token is discarded if numeric input times out.

Stack operations

Symblicity's stack is a double-ended byte stack/deque. Push and pop operate on the currently selected end.

Instruction Operation Description
@ Push A Pushes A onto the active end of the stack.
$ Pop → A Pops from the active end into A. An empty pop produces 0.
_ Switch stack end Toggles which end is used by both push and pop.

The reference interpreter currently allocates 4096 stack bytes; stack overflow is an error.

Direction and structured control flow

Symblicity execution can proceed in either source direction.

Instruction Operation Description
{ Forward direction Forces execution to continue forward through the source.
} Backward direction Forces execution to continue backward through the source.
[ Loop boundary One side of a repeating [] block.
] Loop boundary The other side of a repeating [] block.
~ Break loop Breaks only the innermost enclosing [] loop. Outside a loop it is a no-op.
# Relative jump Advances A + 1 source positions in the current execution direction.

The behavior of these instructions is relative to the current direction rather than being inherently left-to-right. In particular, ~ does not break a {} bidirectional region; it only exits an enclosing square-bracket loop.

Parenthesis calls

Parentheses provide a dedicated nested return mechanism separate from the byte stack.

Instruction Operation Description
( Push/return Pushes its position when it is the active caller symbol; otherwise returns from the most recent saved position.
) Push/return Pushes its position when it is the active caller symbol; otherwise returns from the most recent saved position.

The reference interpreter maintains a 4096-entry return stack containing source positions only. One shared caller character, either ( or ), applies to the whole active stack:

  1. When the return stack is empty, the first encountered parenthesis becomes the caller character and its current source position is pushed.
  2. While the stack is non-empty, encountering that same caller character pushes another current position.
  3. Encountering the opposite parenthesis pops the top saved position and immediately resumes at saved_position + current_direction.
  4. When the final saved position is popped, the shared caller character is cleared.

The current execution direction is used at return time; direction is not stored in the return stack. Both orientations are valid:

( ... )
) ... (

Nested calls and recursion are therefore supported. The parenthesis return stack is independent of the double-ended byte data stack.

Letter jumps

Ordinary alphabetic characters form uppercase/lowercase directional jump pairs. For example, A searches in the current execution direction for the next a, while g searches for the next G.

If no opposite-case occurrence exists in the current direction, execution simply continues.

Some letters are reserved by other parts of the VM:

  • W/w and Y/y are the low/high memory-address registers.
  • X/x are unified-memory read/write operations.
  • Z/z are stateful binary file read/write operations in the normal memory model.
  • U/u and V/v become audio operations when at least one sound is loaded.
  • T/t become second-channel audio operations only when sounds are loaded and dual-audio mode is enabled.
  • When those optional audio modes are not active, T/t/U/u/V/v retain normal opposite-case jump behavior.

The eight memory/file letters w/W/x/X/y/Y/z/Z are always reserved and never perform ordinary case-pair jumps.

Memory and file I/O

Symblicity exposes one unified 65,536-byte memory space. Two 8-bit address registers form a 16-bit address:

address = (high << 8) | low
Instruction Operation Description
w low address = A Stores A in the low address byte.
W A = low address Loads the low address byte into A.
y high address = A Stores A in the high address byte.
Y A = high address Loads the high address byte into A.
x memory[address] = A Writes A to unified memory, except that writes to 0xFEFF are ignored.
X A = memory[address] Reads unified memory; 0xFEFF returns the wrapped system-time byte.

System-time byte

Address 0xFEFF, immediately before the file-name buffer, is a read-only memory-mapped clock byte:

memory[0xFEFF] = Unix time in milliseconds mod 256

Reading it with X returns the current low 8 bits of system time in milliseconds. The value wraps every 256 milliseconds. Writing it with x is ignored.

The top 256 bytes, 0xFF00-0xFFFF, remain ordinary memory and also serve as the file-name buffer for z/Z.

File I/O

z/Z operate on one stateful binary file stream. The file name is a NUL-terminated byte string beginning at memory address 0xFF00, with a maximum length of 255 bytes.

Instruction Operation Description
z Write A Opens the named file if necessary, creating it if it does not exist, then writes A at the current file position.
Z Read → A Opens the named existing file if necessary and reads the next byte into A.

Reads and writes share one advancing file position. If the bytes of the file name change, the current file is closed and the newly named file is opened from position 0 on the next z or Z.

Successful reads and writes set R2 = 0. Reading at EOF sets R2 = 1 and leaves A unchanged. File-system, open, read, and write failures are VM errors. The native interpreter closes the file automatically when it exits.

The native VM accesses host files directly. The browser VM provides the same language operations through a persistent origin-local sandbox rather than arbitrary host paths.

Legacy two-bank compatibility

Programs written for the original two independent 256-byte memory banks can opt into a compatibility map with a TAB-comment directive:

<TAB>@memory legacy-2x256

The directive is detected before normal TAB-comment preprocessing and contributes zero executable source positions.

In this mode:

w/W + x/X -> unified page 0x00 (0x0000-0x00FF)
y/Y + z/Z -> unified page 0x01 (0x0100-0x01FF)

Thus legacy z/Z regain their historical second-memory-bank behavior. Programs without the directive use the normal 16-bit address model and stateful file I/O.

Optional asynchronous audio

The reference interpreter can load an alphabetically sorted array of sound files:

sym --sounds ./sounds program.sym

When one or more sounds are loaded:

Instruction Operation Description
U Next sound Selects the next sound in the shared sound array, wrapping at the end.
u Previous sound Selects the previous sound, wrapping at the beginning.
V Play channel 1 Starts the currently selected sound asynchronously on channel 1.
v Stop channel 1 Stops channel 1.

A second simultaneous playback channel can be enabled with:

sym --sounds ./sounds --dual-audio program.sym

or:

sym --sounds ./sounds -2 program.sym

With dual audio enabled:

Instruction Operation Description
T Play channel 2 Starts the currently selected sound asynchronously on channel 2.
t Stop channel 2 Stops channel 2.

Both channels use the same shared sound selection controlled by U/u, but their playback is independent. Therefore VT can play the same selected sound on both channels, while VUT starts the current sound on channel 1, moves the shared address to the next sound, then starts that sound on channel 2.

The reference interpreter uses ffplay for playback, so FFmpeg is required only when using the audio extension.

If no sounds are loaded, U/u/V/v behave as ordinary letter jumps. If dual-audio mode is not enabled, T/t also remain ordinary letter jumps.

Comments and formatting

Character Meaning
TAB Begins a comment through the physical end of the line.
Newline / CR / form-feed / vertical-tab Ignored as source formatting.
Space Executable instruction: unconditionally skip the next instruction.

Program execution normally ends when the program counter runs off either end of the source.

Compact reference

Symbol Meaning
0-7 Add 2N to A
8 A -= 1
9 A -= 2
+ - * / % ^ | & A/B arithmetic or bitwise operation → R2
= < > ! Conditional skip using A and B
? Skip next if R2 == 0
Space Unconditionally skip next instruction
\ Toggle R0/R1 ↔ R3/R4
: Swap A and B
; Swap B and R2
` A = 0
@ Push A
$ Pop into A
_ Switch active stack end
' Read byte into A
" Output A as byte
. Read decimal into A
, Output A as unsigned decimal
# PC += (A+1) × direction
{ Force forward execution
} Force backward execution
[ ] Repeating loop
~ Break innermost enclosing [] loop only
( ) 4096-entry nested parenthesis return stack
Ordinary letters Jump to next opposite-case partner in the current direction
w/W Set/read low 8-bit memory address byte
y/Y Set/read high 8-bit memory address byte
x/X Unified 64 KiB memory write/read
z/Z Stateful binary file write/read; legacy page-1 memory under @memory legacy-2x256
U/u Next/previous sound when sounds are loaded; otherwise ordinary jumps
V/v Play/stop channel 1 when sounds are loaded; otherwise ordinary jumps
T/t Play/stop channel 2 in dual-audio mode; otherwise ordinary jumps
TAB Comment through physical end of line

Special memory locations:

Address Meaning
0xFEFF Read-only Unix-time-in-milliseconds modulo 256
0xFF00-0xFFFF Ordinary memory and NUL-terminated file-name buffer for z/Z

Examples

Hello World

36"589"38""01"3567"34"01"999"9999"6789"

Output:

HelloWorld!

Functions

Symblicity can build reusable function-like regions from directional letter jumps, #, bidirectional execution, and the dedicated parenthesis return stack. Nested calls and recursion are supported by the 4096-entry return stack.

One canonical backward-entered convention used by larger Symblicity programs defines a function pair A/a with:

{( 1`<reversed-body>a

and calls it with:

`)#A}

The literal space in the definition is an executable Symblicity instruction, not formatting. The function body is stored physically reversed because this convention enters and executes it right-to-left.

A second compact convention uses A as a one-bit call/return state with #: A = 0 selects the call path and A = 1 selects the post-return path. The current project conventions reserve m/M for the main-entry guard and e/E for an exit guard in larger programs.

Online Simulator

Try Symblicity without installing anything:

Symblicity Online Simulator

The browser VM implements the same core register, stack, memory, nested return, timed-input, file-I/O, ANSI-terminal, and optional audio behavior. Browser file I/O uses a persistent origin-local sandbox. The playground includes example programs such as Battleship and Terminal Zero.

Reference interpreter

The maintained native interpreter, language documentation, installers, and examples are kept on GitHub:

Useful input modes include:

sym --input-wait infinite program.sym
sym --input-wait 0 program.sym
sym --input-wait 80 program.sym

-n is shorthand for nonblocking input and -b restores infinite blocking input. --input-timeout is retained as a compatibility alias for --input-wait.

Optional native audio is enabled with:

sym --sounds ./sounds program.sym
sym --sounds ./sounds --dual-audio program.sym

The native audio extension uses ffplay, so FFmpeg is required only when that extension is used.

The repository is dedicated to the public domain under CC0 1.0 Universal.