Zymbol-Lang

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

  1. No keywords? is if, @ is loop, <~ is return. Nothing to translate.
  2. Full Unicode — variable names, function names, comments, and string values accept any Unicode character.
  3. Language-agnostic types#1 / #0 instead of true / false; type tags use symbols (###, ##", ##?...).
  4. Consistent operators — the same symbol means the same thing in every human language.
  5. 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 .zy source; 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

External Links