Zymbol-Lang
| Paradigm(s) | Imperative, Functional, Procedural |
|---|---|
| Designed by | User:Zymbol.Lang |
| Appeared in | 2026 |
| Computational class | Turing complete |
| Reference implementation | Unimplemented |
| File extension(s) | .zy |
Zymbol-Lang is a minimalist symbolic programming language designed to be completely language-agnostic. It uses no English keywords — every control structure, I/O operation, and type marker is a symbol. Identifiers (variable names, function names, comments) can be written in any human language or script, including full Unicode support for CJK, Arabic, Devanagari, Cyrillic, Cherokee Syllabics, Cree Syllabics, emoji, and fictional scripts such as Klingon pIqaD.
The core philosophy: operators are universal, identifiers are yours.
Overview
Zymbol-Lang is currently in public alpha (v0.0.2). It is a working implementation open for use and feedback, with active development ongoing.
The language has two execution backends:
- A tree-walking interpreter written in Rust (default, fully featured)
- A register-based virtual machine (optional via
--vm, ~4× faster on recursive workloads)
The language specification is defined in a formal EBNF grammar. Source files use the .zy extension.
Design Principles
- No keywords —
?is if,@is loop,<~is return. Nothing to translate. - Full Unicode — variable names, function names, comments, and string values accept any Unicode character.
- Language-agnostic types —
#1/#0instead oftrue/false; type tags use symbols (###,##",##?...). - Consistent operators — the same symbol means the same thing in every human language.
- Explicit over implicit — newlines in output, return values, and scope capture all require explicit syntax.
Syntax
Variables and Mutability
Zymbol-Lang distinguishes mutable variables (=) from immutable constants (:=) at the assignment level. Augmented assignment operators (+=, -=, *=, /=, %=, ^=) and increment/decrement (++, --) are supported. Attempting to reassign a constant is a runtime error.
Operators Reference
| Category | Operator | Meaning | Notes |
|---|---|---|---|
| Assignment | = |
Mutable variable | |
| Assignment | := |
Immutable constant | Reassignment raises ##Type
|
| Output | >> |
Print (no auto-newline) | Multiple values by juxtaposition |
| Newline | ¶ or \\ |
Explicit newline in output | Newlines are never implicit |
| Input | << |
Read from stdin | Accepts optional prompt string |
| If | ? |
Conditional | Braces required even for single statements |
| Else if | _? |
Else-if branch | |
| Else | _ |
Else branch | Also used as wildcard in ??
|
| Match | ?? |
Pattern match / switch | Supports ranges, strings, guards, and block arms |
| Loop | @ |
Loop (while / range / for-each / infinite) | Range syntax: i:0..N, with step: i:0..N:S
|
| Break | @! |
Break out of loop | Supports labels for nested loops: @! label
|
| Continue | @> |
Continue to next iteration | |
| Lambda | -> |
Anonymous function (lambda) | Captures outer scope (closure) |
| Return | <~ |
Return value | Also used for output parameters in function signatures |
| Pipe | > | Pipe value into function | RHS must use _ as placeholder for piped value
|
| Not-equal | <> |
Inequality | Intentionally distinct from !=
|
| Exponentiation | ^ |
Power | a ^ b; division between integers is integer division
|
| Length | $# |
Length of array or string | |
| Append | $+ |
Append to array | Returns new array; use $+[i] to insert at index
|
| Remove | $- |
Remove first occurrence by value | $-- removes all; $-[i] by index; $-[i..j] range
|
| Update | $~ |
Functional update at index | Returns new array; use arr[i] = v for in-place
|
| Contains | $? |
Test membership | $?? returns list of all matching indices
|
| Slice | $[s..e] |
Slice array or string | End-exclusive; also $[s:count] count-based form
|
| Sort | $^+ / $^- |
Sort ascending / descending | Primitives only; use $^ with comparator lambda for tuples
|
| Map | $> |
Map over array | Requires inline lambda |
| Filter | Filter array | Requires inline lambda | |
| Reduce | $< |
Reduce array | Requires inline lambda |
| String split | / |
Split string by delimiter | "a,b,c" / ',' → [a, b, c]
|
| String replace | $~~ |
Replace in string | Supports max-count: $~~["old":"new":N]
|
| Try | !?{ } |
Try block | |
| Catch | :! ##Type { } |
Catch specific error type | :! { } catches any error; _err holds message
|
| Finally | :>{ } |
Finally block | Always executes |
| Is error | $! |
Check if value is an error | |
| Propagate | $!! |
Propagate error to caller | |
| Module declare | # |
Declare module name | |
| Export | #>{ } |
Export symbols | Exports must appear before definitions; supports renaming |
| Import | <# |
Import module | |
| Alias | <= |
Import alias | Required — unaliased imports are not supported |
| Module call | :: |
Call module function | |
| Field access | . |
Access named tuple field or module member | |
| Type metadata | #? |
Type introspection (postfix) | Returns structured tuple (type_tag, size, value)
|
| Parse number | ..\| | Parse string to Int or Float | Fail-safe: returns original string on failure |
| Round | ..\| | Round to N decimal places | |
| Truncate | ..\| | Truncate to N decimal places | |
| Shell exec | <\ cmd \> |
Execute shell command, capture stdout | String interpolation supported in command |
Type System
Types are dynamic. Type metadata is accessed via the postfix operator #?, which returns a structured tuple of the form (type_tag, size_in_digits, value) — allowing programs to inspect not just the type but the magnitude of a value.
| Type | Literal Example | Type Tag | Notes |
|---|---|---|---|
| Integer | 42, -7, 0xFF |
### |
64-bit signed; supports hex, binary, octal, decimal prefixes |
| Float | 3.14, 1.5e10 |
##. |
Scientific notation; division between two integers yields integer |
| String | "hello" |
##" |
Interpolation: "Hello {name}"; juxtaposition in >>
|
| Char | 'A' |
##' |
Single Unicode grapheme |
| Bool | #1, #0 |
##? |
Not numeric — #1 ≠ 1
|
| Array | [1, 2, 3] |
##] |
0-indexed; negative indices supported (arr[-1] = last)
|
| Tuple | (a, b), (x: 1, y: 2) |
##) |
Named fields accessed by .field or by index
|
| Unit | (no value) | ##_ |
Returned by functions with no explicit return |
Scoping and Function Isolation
Zymbol-Lang enforces a strict separation between named functions and their calling context. Named functions have fully isolated scopes — they cannot read or modify variables from the enclosing scope. This is an intentional design decision to prevent hidden state dependencies.
Lambdas (->) behave differently: they capture the enclosing scope (closure semantics). Named functions that need to affect the caller's state do so through output parameters, declared with <~ in the function signature:
swap(a<~, b<~) {
tmp = a
a = b
b = tmp
}
x = 10
y = 20
swap(x, y)
>> "x=" x " y=" y ¶ // → x=20 y=10
Blocks ({ }) use lexical scoping with automatic cleanup on exit.
Named Functions and First-Class Values
Named functions in Zymbol-Lang are not first-class values. They cannot be stored in variables, passed as arguments, or stored in arrays directly. To use a named function as a higher-order argument, it must be wrapped in a lambda:
double(x) { <~ x * 2 }
nums = [1, 2, 3, 4, 5]
r = nums$> (x -> double(x)) // ✅ wrapped in lambda
Lambdas, by contrast, are first-class and can be stored in variables, arrays, and passed freely.
Destructuring
Zymbol-Lang supports destructuring assignment for arrays and tuples. The rest-capture operator * collects remaining array elements, and _ discards positions:
arr = [10, 20, 30, 40, 50] [a, b, c] = arr // a=10 b=20 c=30 [first, *rest] = arr // first=10 rest=[20,30,40,50] [x, _, z] = [1, 2, 3] // _ discards person = (name: "Ana", age: 25, city: "Madrid") (name: n, age: a) = person // n="Ana" a=25
Pipe Operator
The pipe operator (|>) passes a value into a function. Unlike most languages, Zymbol-Lang requires an explicit placeholder _ on the right-hand side, making the injection point unambiguous when the function takes multiple arguments:
add = (a, b) -> a + b 5 |> add(_, 3) // → 8 (_ is the piped value) 5 |> add(2, _) // → 7 (position matters)
Pipes can be chained by assigning intermediates.
Match Expression
The ?? operator supports value matching, range matching, guard conditions, and block arms. Guards use the _? syntax inside a match:
// Range match
grade = ?? score {
90..100 : 'A'
80..89 : 'B'
_ : 'F'
}
// Guard match (condition-driven)
state = ?? temp {
_? temp < 0 : "ice"
_? temp < 20 : "cold"
_ : "warm"
}
Shell Integration
Zymbol-Lang can execute shell commands and capture their standard output via the <\ cmd \> construct. String interpolation works inside the command, enabling dynamic shell calls:
date = <\ date +%Y-%m-%d \>
file = "data.txt"
content = <\ cat {file} \>
This feature is available in the tree-walker backend only.
Examples
Hello, World!
>> "Hello, World!" ¶
The same program in Japanese:
>> "こんにちは、世界!" ¶
In Arabic (right-to-left identifiers, same operators):
تحية = "مرحبا، أيها العالم!" >> تحية ¶
Control Flow
x = 7
? x > 100 {
>> "large" ¶
} _? x > 0 {
>> "positive" ¶
} _ {
>> "zero or negative" ¶
}
Loops
@ i:0..4 { >> i " " } // → 0 1 2 3 4 (range, inclusive)
@ i:1..9:2 { >> i " " } // → 1 3 5 7 9 (with step)
@ i:5..0:1 { >> i " " } // → 5 4 3 2 1 0 (reverse)
fruits = ["apple", "banana", "mango"]
@ item:fruits { >> item ¶ } // for-each array
@ { // infinite loop
i++
? i >= 5 { @! }
}
Labeled breaks are supported for exiting nested loops:
@ @outer {
@ @inner {
? condition { @! outer }
}
}
Functions and Lambdas
// Named function
add(a, b) { <~ a + b }
>> add(3, 4) ¶ // → 7
// Lambda (implicit return)
double = x -> x * 2
>> double(5) ¶ // → 10
// Higher-order functions
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
doubled = nums$> (x -> x * 2)
evens = nums$| (x -> x % 2 == 0)
total = nums$< (0, (acc, x) -> acc + x)
>> total ¶ // → 55
// Closure / factory
make_adder(n) { <~ x -> x + n }
add10 = make_adder(10)
>> add10(5) ¶ // → 15
FizzBuzz
fizzbuzz(n) {
? n % 15 == 0 { <~ "FizzBuzz" }
_? n % 3 == 0 { <~ "Fizz" }
_? n % 5 == 0 { <~ "Buzz" }
_ { <~ n }
}
@ i:1..20 { >> fizzbuzz(i) ¶ }
Error Handling
!? {
data = read_file("config.txt")
} :! ##IO {
>> "File not found" ¶
} :! ##Parse {
>> "Parse error" ¶
} :! {
>> "Unknown error: " _err ¶
} :> {
>> "Cleanup done" ¶
}
Error types: ##IO, ##Network, ##Parse, ##Index, ##Type, ##Div, ##_ (generic catch-all).
Modules
// lib/math.zy
# math
#> { add, get_PI }
_PI := 3.14159
add(a, b) { <~ a + b }
get_PI() { <~ _PI }
// main.zy <# ./lib/math <= m >> m::add(5, 3) ¶ // → 8 pi = m::get_PI() >> pi ¶ // → 3.14159
Exports must be declared before definitions. Exported symbols can be renamed at the export site with <=.
Computational Class
Zymbol-Lang is Turing complete. It supports:
- Unbounded loops (
@with arbitrary while-conditions) - Arbitrary recursion (with tail-call optimization in the VM backend)
- Dynamic arrays of unlimited size
- Integer arithmetic without fixed bounds (64-bit signed integers)
Internationalization
Because Zymbol-Lang has no English keywords, programs written in any human language are syntactically identical to programs written in any other. Operators remain constant; only identifiers, string contents, and comments change.
A program written entirely in Arabic, Japanese, Cherokee Syllabics, or Klingon pIqaD is structurally the same as one written in Latin script — no translation layer, no preprocessing. The Unicode grapheme cluster model ensures that scripts with complex glyph composition (Devanagari, Thai, Burmese, Cree Syllabics) are handled correctly at the lexer level.
Implementation
The reference implementation is written in Rust and consists of:
- Lexer / Parser — produces an AST from
.zysource; handles Unicode grapheme clusters natively - Semantic analyzer — type-checks, detects unused variables, circular imports
- Tree-walking interpreter — default execution path, fully featured
- Bytecode compiler + Register VM — optional backend (
--vm), ~4× faster on recursive workloads; module system support is partial
The interpreter is available as a CLI tool:
zymbol run program.zy # tree-walker (default) zymbol run --vm program.zy # register VM zymbol repl # interactive REPL zymbol check program.zy # syntax and semantic check zymbol fmt program.zy --write # format in-place
See Also
- Brainfuck — another language with a minimal operator set
- Befunge — symbolic, stack-based esolang
- Unlambda — purely functional with minimal syntax