# zo programming language — full documentation > Aggregated documentation for LLM ingestion. Generated from `apps/site/src/content/initiation/en/*.md`. For the curated index with link-only entries, see [/llms.txt](https://zo.compilords.house/llms.txt). # install A short setup before we open the first page. Two commands, two minutes. > « Simplicity is a prerequisite for reliability. » — Edsger W. Dijkstra ## get the binary ```sh curl --proto '=https' --tlsv1.2 -sSf https://zo.compilords.house/install.sh | sh ``` The script downloads and extracts the zo compiler into `bin/zo` and adds it to your `PATH` so zo is reachable from any shell. ## verify Confirm zo is reachable from your shell. ```sh zo --version ``` Succesfully it will display `zo x.x.x`. The number depends on the latest release. ## trouble? Drop into the [discord](https://discord.gg/JaNc4Nk5xw) or open a [GitHub issue](https://github.com/invisageable/zo/issues) — fastest path to a fix. You're ready. Turn the page. --- # prologue [tsh-tsh] This initiation is for the `zo` programming language. What are we waiting for to improve our developer experience? Why does having to wait several seconds, or even minutes, to get feedback on the correctness of our program bother absolutely no one? The quality of our current tools and infrastructure borders on mediocrity. Fortunately, some developers, resistant to this pervasive nonsense, perpetually continue to refine our ecosystem. To accept this mediocrity is to submit to the dictates of executive committees who force their products on us through waves of intensive marketing. zo is not a revolutionary programming language: its concepts come from languages that have proven themselves. zo does not solve any new problem that another language hasn't already solved. It is a different language, but one that aims to be familiar, so that people from diverse backgrounds can explore low-level programming through a high-level language. Whether you are back-end, front-end, a hacker, curious, a nerd, a scientist, a Ph.D., or a creative, zo is just another field of possibilities among many. It’s up to you to see if you want to discover more. Our convictions rest on simplicity, software harmony, and the developer experience. For this, with the compilords, we focused on the minuscule details that matter, but that everyone pretends not to see. The main idea remains to open new dimensions in which our thoughts transform into a series of zeros and ones, without ever sacrificing the joy. JOiN THE DEVOLUTiON. --- # language design ## keywords zo has 53 keywords in total:
Namespacing
4 letters
pack
declares the current package
load
imports items into scope
Type Definitions
misc letters
abstract
declares a behavior contract
struct
declares a record with named fields
apply
attaches behavior to a type
enum
declares a tagged union
type
declares a type alias
Member Definitions
misc letters
fun
declares a function
ffi
declares a foreign function binding
val
declares a compile-time constant
imu
declares an immutable binding
mut
declares a mutable binding
fn
declares a closure
Control Flow
misc letters
continue
skips to the next iteration
return
returns a value from a function
match
pattern matches across arms
while
loops while a condition holds
break
exits the current loop
else
alternate branch of an if
when
ternary expression
loop
infinite loop
for
iterates over a range or collection
if
conditional branch
Concurrency
misc letters
supervise
declares a supervised task scope
nursery
declares a structured task scope
select
multiplexes channels or tasks
thread
marks an OS thread spawn (parser-synthetic)
spawn
launches a concurrent task
await
suspends until a task completes
Infixes
misc letters
and
...
as
casts a value to a type
is
type test (reserved)
Modifiers
misc letters
group
...
wasm
marks an item for WebAssembly
pub
marks an item as public
Qualifiers
4 letters
Self
refers to the current type
self
refers to the current instance
Values
misc letters
false
boolean false literal
true
boolean true literal
Integers
misc letters
uint
the default unsigned integer (32-bit)
int
the default signed integer (32-bit)
s16
16-bit signed integer
s32
32-bit signed integer
s64
64-bit signed integer
u16
16-bit unsigned integer
u32
32-bit unsigned integer
u64
64-bit unsigned integer
s8
8-bit signed integer
u8
8-bit unsigned integer
Floats
misc letters
float
the default floating-point (64-bit)
f32
32-bit floating-point
f64
64-bit floating-point
Primitives
misc letters
bytes
byte buffer
bool
boolean type
char
Unicode character
str
UTF-8 string
</>
template fragment type
Fn
function type
## operators ### unary
PrecedenceOperator
0 + - !
### binary
Precedence Operator
1 ||
2 && .. ..=
3 == !=
4 < <= > >=
5 |
6 ^
7 &
8 << >>
9 + ++ -
10 * / %
12 .
### assignments
Operator
= += -= *= /= %= &= |= ^= <<= >>= := ::=
### others
Operator Name
-> arrow (return type)
=> fat arrow (match arm)
=:> template fat arrow (template-body closure)
|> pipe arrow (reserved)
:: path separator
? question
@ at
### delimiters
Open Close Name
( ) parentheses
{ } braces
[ ] brackets
<> </> template fragment
### punctuations
Symbol Name
, comma (list separator)
; semicolon (statement terminator)
: colon (type ascription)
_ underscore (wildcard pattern)
# hash (reserved)
$ dollar (reserved)
%% attribute marker
... ellipsis (spread / variadic)
--- # introduction This guide is your initiation to the zo programming language ecosystem. Read sequentially on your first pass, then skip around once you grasp the architectural patterns. ## how to use this guide Every lesson delivers a high-fidelity snapshot of a functional zo program. Pay close attention to the comments: documents comments (`-!`) leverage raw Markdown formatting to establish systemic guidelines, while line (`--`) and block (`-* *-`) markers isolate execution mechanics. ```zo -! Sup? I'm a doc comment, I support markdown -! format. What's good? ``` ```zo -- I'm a line comment, I don't care about markdown. ``` ```zo -* From my side I'm a block comment, I'm happy to help for details that matter. *- ``` Every executable compilation unit inside zo must expose an explicit, non-colored entry block called `main`: ```zo -- Wassup?! I'm `main`, a function. -- Use me as a entry point with `fun` keyword. fun main() { -- This program does nothing... yet. } -! ## the capstone. -! -! - every programs must declare a `main` block -! to serve as the runtime launchpad. ``` --- # hello Let's spin up your first interactive instance. We trigger text printing operations via an internal optimized wrapper. ```zo -! Yo! Let's start with a simple program. -! In this lesson, we learn how to print a message. fun main() { -- We call our buddy `showln`. -- It tells the compiler to display the value -- passed as argument with a newline at the end. showln("hello, hacker"); } -! ## the capstone. -! -! - `showln` is an internal compiler builtin. No -! import or namespace matching required. ``` --- # literals ## numbers Programming boils down to memory allocation layout and data mutations. Data arrives in primary primitives called literals. You do not need to memorize these constraints instantly, but you must respect their sizes. > *All contextual code snippets assume code is running inside an active `fun main()` execution block.* ### integers ```zo -! Let me introduce the gang members. -! -! ## the integer family. -! -! signed: s8, s16, s32 (int), s64 -! unsigned: u8, u16, u32 (uint), u64 -- Ya, I'm the `int` chief — a signed 32-bit integer -- by default for any bare number you write. -- I scale up to `s64` if you need more room. 42 -- I support large values natively — `600851475143` -- allocates down without complex object types. 600851475143 ``` ### floats ```zo -! And that's the rival clan. -! -! ## the float family. -! -! f32: 32-bit -! f64: 64-bit (float) -- Heyo, I'm `float` — a 64-bit double. Just add a -- `.` and you get me. 14.0 3.14159 -- I support scientific notation natively. No -- overhead, just quick compilation values. 1.0e10 2.5e-3 ``` ### bases ```zo -! Mask-on integer prefixes change internal notation -! views. 0b11110000 -- Binary notation evaluates to 240 0o77 -- Octal notation evaluates to 63 0xff -- Hexadecimal notation evaluates to 255 ``` ### parse modifiers ```zo -! A `#` prefix sets the display base. The digits stay -! decimal; only how the value prints changes. b#30 -- value 30, shown in binary o#75 -- value 75, shown in octal x#76 -- value 76, shown in hexadecimal ``` ## booleans ```zo -- Wordup, we're `bool` — only `true` and `false`. -- No "truthy" or "falsy" mind games here." true false ``` ## strings ```zo -- Look out! I'm `str` — a string literal. I live in -- the binary's read-only data section hood, so I -- cost nothing at runtime. Skuuuuuu!" "JOiN THE DEVOLUTiON." ``` ## chars ```zo -- Call me `char` — a single Unicode scalar wrapped -- in single quotes. 'z' ``` ## bytes ```zo -- Call me `bytes` — a multi-byte buffer wrapped in -- backticks. Same layout as `str`, but without the -- UTF-8 safety validation promise. Every raw byte -- is preserved. `hello` `¥orld` ``` --- # variables Data bindings demand structured tracking. zo enforces variable clarity using three dedicated allocation keywords: `val` for global or local compile-time constants, `imu` for unalterable execution parameters, and `mut` for active stack transformations. ## constants ```zo -- Hi, I'm `val` — your compile-time constant. val VERSION: str = "1.0.0"; val MAX_HEALTH: int = 100; ``` ## locals ```zo -- Hey, I'm `imu` — your immutable local. Set me -- once, read me many. imu name: str = "johndoe"; -- And me, I'm `mut` — your mutable local. I can be -- reassigned. mut health: int = 22; health = 50; -- Legal modification mutation. ``` ## shadowing ```zo -- Variable shadowing isolates structural scopes. -- Each statement allocates a fresh slot, shadowing -- the predecessor safely. imu x: int = 40; imu x: int = x + 2; -- x now safely evaluates to 42. ``` --- # interpolation Drop variables into any string with `{variable_name}`. The compiler resolves each hole at compile time — no runtime parsing, no format functions. ```zo imu name: str = "johndoe"; imu hp: int = 100; imu attack: int = 15; showln("hero: {name}, hp: {hp}"); ``` Interpolation works in every string context — assignments, arguments, return values. ```zo imu power: int = hp + attack; imu status: str = "power level: {power}"; showln(status); ``` All scalar types resolve automatically: `str`, `int`, `float`, `bool`, `char`. ```zo imu pi: float = 3.14; imu active: bool = true; imu label: str = "pi={pi}, ok={active}"; showln(label); ``` ## how it works Interpolated strings compile into a single allocation that concatenates all segments. Each non-`str` variable converts to its string representation first, then everything merges in one pass. ```zo showln("hp: {hp}"); -- Desugars to: -- show("hp: "); -- showln(hp); ``` ```zo imu msg: str = "hp: {hp}"; -- Compiles to: -- to_str(hp) → "100" -- multi_concat("hp: ", "100") → "hp: 100" ``` Direct output through `showln` skips the heap entirely — each segment writes straight to the file descriptor Assigned strings allocate once regardless of how many `{}` holes they contain. ```zo -! ## the capstone. -! -! - `{variable}` inside any `"string"` resolves the variable. -! - `\{` produces a literal brace, not interpolation. -! - direct output (`showln`) allocates nothing. -! - assigned strings allocate once, not per segment. -! - all scalar types (`str`, `int`, `float`, `bool`, `char`) supported. ``` --- # operators Operators are how you transform values. zo keeps them small and predictable — the same five arithmetic operators you've used everywhere, plus reassignment and a handful of shorthands. ## arithmetic ```zo imu power: int = hp + attack; -- + - * / % mut current_hp: int = 100; current_hp -= 25; -- += -= *= /= ``` --- # strings All strings adhere to valid UTF-8 formats, supporting raw character arrays, unicode hex variants, layout escapes, and structural symbols seamlessly. ```zo -- Custom strings are completely immutable. The -- internal data bytes never change. "\e[32mHello!\v\e[34mWorld\e[0m\n" "\x48\x65\x6c\x6c\x6f" -- hex char "\u{e9}\u{e8}\u{ea}" -- latin char "\u{1F600} \u{1F680} \u{1F4A9}" -- decoded emoji "🙈🙉🙊" -- raw unicode literals ``` ## concatenation The `++` operator acts as your layout welding torch. Merging literals fuses values immediately inside the binary compilation phase, yielding zero performance penalties at execution time. ```zo imu greeting: str = "hello"; imu name: str = "johndoe"; imu full: str = greeting ++ ", " ++ name ++ "!"; -- Direct character index extraction evaluates -- efficiently. showln(greeting[0]); -- Evaluates to 'h' ``` ```zo -! ## the capstone. -! -! - `str` is immutable. -! - `++` concatenates. -! - `s[i]` for string indexing. ``` --- # tuples A tuple organizes items into a fixed-length, ordered sequence. Unlike arrays, a single tuple can house distinct data types within the same memory footprint. ```zo imu point: (int, str, int) = (100, "john", 3); -- Extract parameters via positional indices. showln(point.0); -- Deconstruct the memory layer instantly via -- structured binding patterns. imu (x, y, z): (int, int, int) = point; -- Structured destructuring binding -- Declare structural type aliases for fast shape -- replication. type Point = (int, int); -- Type shaping alias ``` --- # arrays Arrays host homogeneous components where every single block matches a identical data type. They come in static and dynamic variants. ## static array The notation format `[N]T` fuses the length constraint straight into the data classification layer. The memory block is determined at compile-time and guarantees safe bounds-checking verification parameters during constant array access tasks. ```zo imu nums: [3]int = [10, 20, 30]; imu zeros: [5]int = [0...]; -- [0, 0, 0, 0, 0] imu grid: [2][3]int = [[1, 2, 3], [4, 5, 6]]; -- Unpack items instantly using assignment sequences. imu [a, b, c]: [int, int, int] = nums; ``` ## dynamic array The designation `[]T` offloads length calculations to execution headers rather than structural types. Coupling arrays with a `mut` binding permits array extensions. ```zo mut arr: []int = []; arr.push(10); imu last: int = arr.pop(); -- Safely extracts 10 -- The `[value...count]` expression triggers explicit -- array expansion routines. imu sevens: []int = [7...4]; -- [7, 7, 7, 7] ``` --- # blocks ... --- # functions ```zo -- I'm a function accepting two arguments of type -- int and returning a value of type int. fun sum(a: int, b: int) -> int { a + b -- implicit return } -- Then you can call me, like this: sum(39, 3); ``` --- # closures ```zo -- closure:block. imu square: Fn(int) -> int = fn(x: int) -> int { return x * x; }; -- closure:line. imu square: Fn(int) -> int = fn(x: int) -> int => x * x; -- Then you can call me, like this: square(7); ``` --- # structures Custom structures package operations into meaningful domain boundaries. The `struct` keyword models complex custom fields, while the `apply` keyword assigns functional behavior logic to those types. ## struct A field can declare a default value with `=`. Fields without one are set when you construct the value. ```zo struct Point { x: int, y: int, } struct Rect { x: int = 10, y: int = 20, w: int = 100, h: int = 200, } struct Counter { x: int = 0, } ``` ## methods Use the apply statement block to bind custom functions to a target structure definition. ```zo apply Counter { -- Static instantiation function block. fun new() -> Self { Self { x = 0 } } -- Mutable state tracking modifier function. fun incr(mut self) { self.x += 1; } } ``` --- # enums Enums manage disjoint data states safely, supporting direct discriminants along with tuple-wrapped inline data tracking blocks. ```zo -- ... enum Foo { Bar, Oof(str), -- Structural data tuple binding Rab = 42, -- Assigned explicit discriminant value } -- Instance consumption example. imu foo: Foo = Foo::Bar; imu foo: Foo = Foo::Oof("What's crackin'?"); ``` --- # abstracts Abstracts establish zo's architecture for ad-hoc polymorphism. You define a structural contract once, then declare custom implementation tracks across varying types via explicit implementation mappings: `apply Abstract for TargetType`. ```zo abstract Display { fun display(self) -> str; } struct Point { x: int, y: int, } apply Display for Point { fun display(self) -> str { return self.x.to_str() ++ ", " ++ self.y.to_str(); } } ``` ## using abstracts as parameters. Functions that accept elements under abstract contract bounds choose between three compilation techniques balancing raw execution speed against binary sizing limits. ### form 1 — implicit-mono. ```zo fun render(item: Display) -> str { item.display() } ``` The compiler manages the heavy lifting under the hood: it automatically generates a hidden type variable, extracts parameters straight from the call site, and generates a dedicated, static monomorphized copy of the function block per unique type. Zero runtime cost, zero vtable tracking lookups. Violating limits triggers error diagnostics immediately. ### form 2 — explicit-mono. ```zo fun render<$T: Display>(item: $T) -> str { item.display() } ``` This follows the exact same performance-optimal static compilation track as Form 1. Use this explicit syntax strategy whenever you must reuse the type constraint identifier across signature bounds—such as enforcing matched input types or coordinating return paths. ### form 3 — dynamic dispatch. ```zo fun render(item: any Display) -> str { item.display() } ``` Prepend the type parameter with any to box the instances safely behind a uniform vtable layout pointer. The compiler generates exactly one execution block in the final binary, executing code paths via runtime lookup addresses. This trade-off incurs slight call overhead but permits heterogeneous data grouping inside shared array vectors. ```zo mut widgets: []any Drawable = []; widgets.push(Button { label = "ok" }); widgets.push(Slider { value = 42 }); for w := widgets { showln(w.draw()); } ``` | Engineering Requirement | Optimal Architectural Choice | | :------------------------------------------------------------------ | :---------------------------------- | | Maximum dispatch velocity; isolated single types per call site. | `item: Abstract (Form 1)` | | Shared type bounds enforcement across arguments or return paths. | `<$T: Abstract>(item: $T) (Form 2)` | | Mixed collection handling; minimal compiled binary space footprint. | `item: any Abstract (Form 3)` | --- # type alias ```zo -- ... type Bar = int; -- ... imu z: Bar = 42; ``` ```zo group type Idx = int and Pair = (int, int) ; ``` --- # generics Generics are zo's form of **parametric polymorphism** — one body of code that works across many types via type parameters (`<$T>`). The other form, **ad-hoc polymorphism**, is covered by [abstracts](#017-abstracts). --- # type inference - hindley milner. --- # control flow ## if else ```zo -- same type inside if 1 == 2 { false } else if 2 == 3 { false; } else if 3 == 4 { false } else { true } ``` ## ternary ```zo -- only as expression when true ? 1 : 2; ``` ## pattern matching ```zo -- ... match 5 { 10 => check(false), _ => check(true), -- wildcard } -- ... match "z" ++ "o" { "ivs" => showln(false), "zo" => showln(true), _ => showln("default"), } ``` ## jumps (terminators) ```zo for i := 1..10 { if i == 3 { continue; } if i == 7 { break; } show(i); } -- 12456 ``` --- # loops ## while loop ```zo -- while:block mut z: int = 0; while z < 1_000_000_000 { z += 1; } -- while:line mut z: int = 0; while z < 1_000_000_000 => z += 1; ``` ## for loop ```zo -- for:block. for x := 0..3 { showln("{x}"); } -- for:block:mut. for mut x := 0..3 { x += 1 } -- for:line. for x := 0..3 => showln("{x}"); -- for:line:mut -- mutable iterator, body reassigns it. for mut n := 0..3 => n += 1; ``` ## infinite loop ```zo mut x: int = 0; loop { if x == 1_000_000 { showln(x); break; } x += 1; } ``` --- # concurrency The zo runtime ignores standard state-machine `async`/`await` transforms entirely, eliminating function coloring bugs across your system. Execution runs inside native runtime-managed green threads tracking execution scope blocks called nurseries. Blocking a task triggers immediate context frame swaps inside the scheduler. ## nursery A `nursery` container sets strict lexical boundaries for concurrent task Lifecycles. The execution block cannot exit until every spawned green thread unwinds completely. ```zo fun worker(id: int, tx: Tx) { showln("worker: {id}"); tx.send(id * 10); } fun main() { imu (tx, rx) := channel(); -- The nursery handles concurrent task tracking -- smechanics cleanly. nursery { spawn worker(1, tx); spawn worker(2, tx); } -- exical boundary block: execution holds here -- until both tasks complete. imu res1 := rx.recv(); imu res2 := rx.recv(); showln("collected results: {res1}, {res2}"); } ``` - **True Stackful Green Threads**: Every concurrent execution task allocates a lightweight runtime execution stack. Deep nested calls compile natively without restructuring code into complex async state loops. - **Unified Runtime Scheduler**: The internal task coordinator monitors state mutations directly. Invoking `rx.recv()` on an empty channel yields execution, swapping out active thread contexts immediately. - **Structural Cancellation**: Nurseries form distinct isolation islands. Triggering task cancellations propagates downstream through children stacks because the underlying runtime owns the stack handles. ## supervise ```zo ``` ## select Coordinate communication states across multiple channel references using the non-blocking select block format: ```zo select { rx1 => fn(value: int) => showln("chan1: {value}"), rx2 => fn(value: int) => showln("chan2: {value}"), } ``` ## thread ```zo ``` ## await ```zo ``` --- # tests --- # zsx The compiler builds an inline UI interface engine called zsx (zo Syntax Extension). It compiles interface markup nodes into static target rendering definitions at build-time, completely freeing the stack from heavy application bundles or runtime framework assets. ```zo fun user_account(username: str, score: int) { imu view: ::=

User: {username}

Current Score: {score}

; } ``` ## @events `@click`, `@input`, event handlers. ```zo ``` ## components ... ## style Target style behaviors directly inside your source file using scoped lexical style assignment scopes (`$: {}`). The declarations remain completely isolated to the compilation package module boundary, guaranteeing style isolation. ```zo -- Isolated module styles stay bound to local markup -- components. $: { card { background: #161b22; border-radius: 4px; padding: 16px; } button { background-color: #ff79c6; color: #0d1117; font-weight: bold; } } -- Use the public prefix modifier to pass declaration -- metrics globally. pub $: {} ``` --- # directives `#render` — ... `#html` — ... --- # foreign function interfaces zo calls C-ABI libraries directly. You declare the foreign function once, tell the linker where its symbol lives, and call it like any zo function — no wrapper layer, no runtime cost. ## declaring a foreign function A `pub ffi` declaration names a function that lives in a C library. It has no body; the symbol resolves at link time. ```zo -- A declaration ends in `;` — no body. pub ffi sqrt(x: f64) -> f64; -- A void function omits the return. pub ffi close_window(); ``` zo drives the call straight from this signature: it places arguments in registers per the platform ABI, narrows or widens scalars, and passes structs by value. Adding a function costs one line. ## linking a library A `#link` block tells the linker which dylib owns the symbols in the file. One block covers every `pub ffi` in the same pack. ```zo #link { macos: "@executable_path/libzo_provider_sqlite.dylib", linux: "@executable_path/libzo_provider_sqlite.so", } pub ffi zo_sqlite_open(path: CStr) -> int; pub ffi zo_sqlite_close(handle: int); ``` C strings cross the boundary as `CStr` (from `core::c`), never `str` — a zo `str` carries a length header that a C function would misread. ## calling a library Foreign libraries ship as opt-in providers. Load one and call it: ```zo load core::c::*; load provider::sqlite::*; fun main() { imu db: int = zo_sqlite_open(CStr::new("scores.db")); zo_sqlite_exec(db, CStr::new("CREATE TABLE s (score int)")); zo_sqlite_close(db); } ``` ## the binding generator Hand-writing a `pub ffi` line per function — and keeping each one in sync with the library — is the tedious part. `zo-binder` writes those declarations for you, from one of two sources. ### from a rust library Wrap any Rust crate in a small shim that exports plain C functions: ```rust #[unsafe(no_mangle)] pub extern "C" fn undo_stack_new() -> i64 { /* ... */ } ``` Then generate the bindings: ```sh just bind undoredo ``` zo-binder reads the shim's signatures and writes `provider/undoredo/undoredo.zo` — the `#link` block plus one `pub ffi` per function, ready to `load`. ### from a c header For a C library, feed zo-binder the header's machine-readable API. raylib emits one through its `rlparser` tool: ```sh zo-binder --json raylib_api.json --lib raylib \ --macos /opt/homebrew/lib/libraylib.dylib \ --linux /usr/lib/x86_64-linux-gnu/libraylib.so ``` zo-binder maps each C type to its zo equivalent, generates the structs, renames `InitWindow` to `init_window`, and skips what it cannot map — callbacks, variadics — reporting each one. The generated file is committed and reviewed like any other source. Nothing runs during `zo run`: you regenerate bindings deliberately, the way you would run a formatter. --- # core ## options ```zo imu some: Option = Option::Some("..."); imu none: Option = Option::None; ``` ## results ```zo imu pass: Result = Result::Pass("value"); imu fail: Result = Result::Fail("error"); ``` ## errors ### the `?` operator - short-circuit Result inside a Result-returning function - desugaring: `expr?` ≡ `match expr { Pass(v) => v, Fail(e) => return Fail(e) }` ### error propagation - chaining: `read_file(p)?.parse()?.validate()?` - composing helpers that bubble up domain errors ### errors vs panics - Result for *expected* failure modes (file not found, parse error) - panics for *bugs* (invariant broken, indexing past length) - never use Result to signal logic errors; never panic on user input ## ranges ```zo for i := 0..5 { -- iteration showln(i); -- 0 1 2 3 4 } imu slice: []int = xs[2..5]; -- slicing ``` ## collection types ### arrays ```zo imu scores: []int = [1, 2, 3, 4, 5]; imu empty: []int = []; scores.sum(); -- 15 empty.sum(); -- 0 scores.contains(3); -- true empty.contains(5); -- false scores.find(3); -- 2 empty.find(99); -- -1 scores.min_of() -- 1 scores.max_of() -- 5 empty.min_of(); -- 0 ``` ### vectors ```zo mut numbers: Vec = Vec::new(); numbers.len(); -- 0 numbers.is_empty(); -- true numbers.push(10); numbers.push(20); numbers.push(30); numbers.get(0); -- Option::Some(10) numbers.get(99); -- Option::None numbers.set(1, 42); -- set in-bounds. !numbers.set(7, 0); -- set out-of-bounds returns false. numbers.pop(); -- Option::Some(30) numbers.remove(1); -- Option::Some(42) ``` ### sets ```zo mut ids: HashSet = HashSet::new(); ids.is_empty(); -- true ids.insert(10); -- true (new key) ids.insert(20); ids.insert(10); -- false (already present) ids.contains(10); -- true ids.contains(99); -- false ids.remove(20); -- true ids.len(); -- 1 ``` ### maps ```zo mut counts: HashMap = HashMap::new(); counts.is_empty(); -- true counts.insert("a", 1); counts.insert("b", 2); counts.get("a"); -- Option::Some(1) counts.get("z"); -- Option::None counts.contains_key("b"); -- true counts.remove("a"); -- Option::Some(1) counts.len(); -- 1 ``` ### file system ```zo imu path: str = "/path/to/file"; match write_file(path, "hi") { Result::Pass(_) => {}, Result::Fail(_) => showln("write-err"), } match read_file(path) { Result::Pass(text) => showln(text), Result::Fail(_) => showln("read-err"), } exists(path); -- true remove_file(path); -- true imu names: []str = read_dir("/some/dir"); ``` ### directories ```zo load core::io; io::is_dir("/some/dir"); -- true io::copy("a.txt", "b.txt"); -- Result::Pass(bytes copied) io::remove_dir("/empty/dir"); -- Result::Pass(0) io::remove_dir_all("/whole/tree"); -- recursive teardown ``` Each returns `Result::Fail(errno)` on failure. ### terminal ```zo load core::io; -- fd 0 stdin, 1 stdout, 2 stderr. when io::isatty(1) ? showln("interactive") : showln("piped"); ``` ## environment ```zo load core::env; env::current_dir(); -- "/work/dir" env::set_current_dir("/tmp"); -- true env::temp_dir(); -- the OS temp directory env::get("HOME"); -- "/Users/me" ("" on miss) env::set("KEY", "value"); -- true env::remove("KEY"); -- true imu all: []str = env::vars(); -- ["KEY=VALUE", ...] ``` ## command-line ```zo load core::cli; load core::io; imu app: Cli = Cli::new("greet", "say hello") .flag("v", "verbose", "print more") .option("n", "name", "who to greet"); imu parsed: Parsed = app.parse(io::args()); parsed.has("verbose"); -- true when -v / --verbose given parsed.value("name"); -- Option::Some("zo") parsed.positionals(); -- bare arguments, in order ``` - `--name=zo`, `--name zo`, and `-n zo` all parse the same - unknown dashed tokens fall through to positionals - `app.help()` renders usage text from the registered specs --- # module system ## pack ```zo pack say { fun hello() { showln("hello, modular world"); } } ``` ## load ```zo load core::math::pow_i; load core::math::(pow_i, abs); ``` --- # error messages When a program does not compile, zo tells you what went wrong, where, and how to fix it. Pick the shape of that report with `--format`: a colored snippet for you, or structured data for a tool. ```zo fun main() { imu s: str = "hello" ++ 42; } ``` By default the compiler renders a human snippet to stderr — the offending line, a caret under each span, and the conflicting types in color. ```sh zo build greeting.zo ``` ```text [E0304] Error • Type mismatch ╭─[ greeting.zo:2:25 ] │ 2 │ imu s: str = "hello" ++ 42; │ ───┬─── ─┬ │ ╰─────────── conflicts with this type `str` │ ╰── incompatible type `int` here ``` ## warnings Not every diagnostic stops the build. Warnings point at code that compiles but breaks a convention — an unused variable, unreachable code, or a name that does not follow zo's naming rules: - `struct`, `enum`, `type`, and generic names are PascalCase. - `val` constants are SCREAMING_SNAKE_CASE. - everything else — `imu`/`mut` bindings, `fun` names and arguments, struct fields, `abstract` functions — is snake_case. Each naming warning carries the convention-correct rename as its help, so the fix is always one copy-paste away: ```text [E0355] Warning • Name is not snake_case ╭─[ counter.zo:2:7 ] │ 2 │ imu MyCount := 1; │ ───┬─── │ ╰───── expected a snake_case name │ │ Help • rename it to `my_count` ``` A leading underscore opts a binding out (`_unused`), and digits never need a separator (`r0`, `grid2`, `MAX2` are all fine). The program builds and runs regardless — warnings inform, errors stop. ## machine formats An agent reads text differently than you do — it never skims and it is never overwhelmed by length. So zo offers two machine formats that carry the *full* diagnostic, not a terse summary. Both stream to stdout, leaving stderr for you. `--format=json` emits one JSON object per diagnostic, one per line (NDJSON), flushed as each error is found. ```sh zo build greeting.zo --format=json ``` ```json {"$schema":1,"id":"type-mismatch","code":"E0304","severity":"error","phase":"analyzer","message":"Type mismatch","fixes":[],"notes":["The types of both operands must be compatible"],"snippet":{"before":["fun main() {"],"lines":[" imu s: str = \"hello\" ++ 42;"],"after":["}"]},"span":{"file":"greeting.zo","byte_start":35,"byte_end":37,"line_start":2,"line_end":2,"col_start":25,"col_end":27},"secondary":{"file":"greeting.zo","byte_start":24,"byte_end":31,"line_start":2,"line_end":2,"col_start":14,"col_end":21},"primary_type":"int","secondary_type":"str"} ``` `--format=xml` emits one well-formed `` document. The tag boundaries read as explicit structure — clean to drop straight into a prompt. ```sh zo build greeting.zo --format=xml ``` ```xml Type mismatch The types of both operands must be compatible fun main() { imu s: str = "hello" ++ 42; } int str ``` The two machine formats are **isomorphic**: the same fields under the same names. A JSON key maps 1:1 onto the XML element or attribute of the same name, so a tool that reads one reads the other. ## the schema Every diagnostic carries a stable identity and the data needed to act on it without re-parsing your source. - `id` — a frozen, kebab-case name (`type-mismatch`). Match on this, not the prose. - `code` — the display alias (`E0304`), derived from `id`. - `severity` — `error` or `warning`. - `phase` — where it surfaced: `tokenizer`, `parser`, `analyzer`, `codegen`, `runtime`. - `message` — the one-line headline. - `span` — the primary location: `file`, byte offsets, and 1-indexed `line`/`col` (columns count characters, so `é` advances one). - `secondary` — the conflicting location, when a diagnostic carries two spans. - `fixes` — machine-applicable edits, always an array. Each fix names a `kind` (`insert` / `replace` / `delete`), the replacement `text`, a `description`, and the exact span to edit. A tool auto-applying picks the first. - `notes` — attached context, always an array. - `snippet` — the source lines around the span (`before` / `lines` / `after`). Tune the radius with `--snippet-context N`; `0` turns it off. `fixes` and `notes` are always present — empty rather than absent — so a consumer never needs a presence check. The same source over the same input renders byte-identical output, so a tool can diff two builds. ```zo -! ## the capstone. -! -! - default `--format=human` paints a colored snippet to stderr. -! - `--format=json` streams one NDJSON object per diagnostic to stdout. -! - `--format=xml` emits one well-formed document to stdout. -! - both machine formats share one frozen, isomorphic schema. -! - match on the stable `id`, never on the prose `message`. -! - `fixes` carry exact edits; `--snippet-context N` sets the source radius. ``` --- # epilogue Your initiation is complete. Now that you have mastered the fundamentals of the `zo` language — it is up to you to get creative and show the world your talent. We have put together a collection of programs for you to browse, sample, replicate, and modify however you like. Click the following link to access them: [@how-to](/how-to) TRiLU! --- ## further reading - [Grammar (EBNF)](https://github.com/invisageable/zo/blob/main/crates/compiler/zo-notes/public/grammar/zo.ebnf) - [guidelines](https://github.com/invisageable/zo/tree/main/crates/compiler/zo-notes/public/guidelines) - [Discord](https://discord.gg/JaNc4Nk5xw) - [GitHub Issues](https://github.com/invisageable/zo/issues)