Ziman

A language you can read
without running it.

Ziman is small and statically typed. Its syntax and semantics are designed to be cheap for an LLM to generate correctly and cheap for a human to check by eye: no implicit coercion, no exceptions, no operator overloading, one obvious way to spell most things, and errors that say what to write instead.

The reference interpreter — lexer, parser, type checker, tree-walking evaluator — is written in C11, but C is only its host. Ziman itself is designed from scratch. It is v0: complete enough to run a real HTTP backend against PostgreSQL, not yet fast.

A taste

use json

#[derive(Json)]
struct Item {
    name: string
    qty: int
}

fn total(items: [Item]) -> int {
    var sum = 0
    for item in items {
        sum = sum + item.qty
    }
    return sum
}

fn summarize(text: string) -> Result<string, string> {
    val parsed = try json.parse(text)
    val item = try item_from_json(parsed)
    return ok(format("{} x{}", item.name, item.qty))
}

fn main() {
    val items = [Item { name: "pen", qty: 3 },
                 Item { name: "ink", qty: 4 }]
    printf("total units: {}\n", total(items))
    print(summarize("{\"name\":\"cup\",\"qty\":2}"))
    print(summarize("{\"name\":1}"))
}
total units: 7
Ok(cup x2)
Err(name: expected a string)

Errors are values. try propagates them; there is nothing to catch.

This program is tests/lang/readme_example.zm, run by the test suite on every change — so it stays true. It is one of 1,142 fixtures that work the same way.

Errors that say what to write instead

A diagnostic is part of the language’s surface, not an afterthought. Ziman reports every error it can find rather than stopping at the first, names the file each one is in, and points a caret at the column. Where there is a right thing to write, the message says what it is.

ziman: type error at 2:19 in hello.zm: arithmetic
operator requires two operands of the same numeric
type, found int and float (use 'as' to cast
explicitly)
  |
2 |     val total = 3 + 2.5
  |                   ^

No implicit coercion — and the fix is in the message.

ziman: type error at 1:1 in pick.zm: function
'pick' does not return a value on every path
  |
1 | fn pick(n: int) -> string {
  | ^

Checked without running: ziman --check loads and type-checks only. --json reports each error as one JSON object for an editor or an agent.

The language at a glance

  • Types. int, float, bool, string, byte, arbitrary-precision bigint and decimal, arrays, structs, tagged-union enums, Option<T>, Result<T, E>, Map, Set, Channel<T> and function types. Values are immutable unless declared var.
  • Errors are values. Result and Option with a prefix try to propagate, and a narrow bare-value auto-wrap so return x works in a Result function.
  • Control flow. if/else, while, for x in arr, an exhaustive switch over enums and results, and defer for cleanup.
  • Generics. Explicit type arguments with Eq, Hash and Ord bounds. No general traits.
  • Derives. #[derive(Json)], #[derive(Row)] for SQL rows, #[derive(Entity)], #[derive(Ord)], #[derive(Properties)]. --emit-derived prints what each one generates.
  • Concurrency. Real OS threads with spawn and thread-safe channels, in a single-writer style rather than shared memory.
  • Modules. use path/to/mod or use mod.Name as alias. The standard library is bundled with the binary and needs no installation.

Standard library

Bundled with the binary, reached with use name. Every pub signature is generated from the source into the corelib reference, and a gate fails if the committed copy drifts.

ModulesWhat they are
fs, os, process,
clock, random
Files, environment and this process, starting and signalling children, time source, entropy
timejava.time-shaped dates, times, durations and instants, with named zones from the system’s own zone files
json, ndjson, csv,
toml*, yaml*
JSON, and the other formats as converters to and from the same Json tree
xml*, markdown*Document trees of their own, each with a reader, a writer that escapes per position, and limits where a format has no syntax errors to lean on
net, http, server,
client, url
TCP and TLS, an HTTP/1.1 server toolkit with per-key rate limiting and connection caps, an HTTP client
db, sql*, eql*,
sqlite, pg*, mysql*,
memdb*
One database API over SQLite, PostgreSQL and MySQL, with pooling, a SQL statement tree, an entity query language, and an in-memory SQL engine written in Ziman
uuid, number,
encoding, compress,
bits
UUIDs, number formatting, base 2/8/16/32/64, deflate and gzip, bitwise helpers
crypto, jwt, otpSHA-256, HMAC, PBKDF2, Argon2id, bcrypt; signed tokens; time-based one-time passwords
geom, shape, paint,
scene, pdf, raster,
font, image
A device-independent 2D drawing model, and two backends that render the same scene: PDF documents and anti-aliased raster images, with embedded TrueType text
properties, log,
metrics, regex,
schedule, locale*
Configuration, levelled logging, Prometheus-format metrics, regular expressions, cron and interval scheduling, system locale data

* Experimental: less real-world use than the rest, so treat a gap or a protocol-conformance bug as more likely there.

Building and running

$ make
$ echo 'fn main() { print("hello, ziman") }' > hello.zm
$ ./ziman hello.zm
hello, ziman

Developed and tested on Linux (x86-64). Needs a C11 compiler, make, and development files for SQLite, OpenSSL 3.2+, libxcrypt and zlib. macOS and the BSDs use the same POSIX interfaces and will probably build, but nobody has tried. Windows needs WSL or a container.

Manuals

Examples

One runnable program per library module, each stored with its expected output and checked by the suite — so an example that drifts from the library fails the build rather than misleading you.

Beyond the examples there is a whole application: health-record-backend-ziman, an HTTP backend with 27 repositories over SQLite and PostgreSQL, used as the language’s North Star and run by its test suite on every change.