LEVIATHAN v0.1 · in development

Toolchain

Compiler & Projects

The toolchain is two binaries, not one: trident resolves a project and writes a build plan, and leviathan compiles it. This page covers the manifest, the dependency model, the compiler's flags, and the native backends that turn source into an executable.

Two tools: trident & leviathan

Leviathan splits its toolchain into two separate binaries, following the cargo/rustc, MSBuild/csc, SPM/swiftc norm:

trident
the package manager / build driver — the front door users run. It owns the project manifest (trident.toml), resolves the dependency graph, and emits a fully-resolved build plan.
leviathan
a pure compiler. It compiles the source set a plan names. It does not parse the manifest, resolve dependencies, or know a registry exists.

The boundary between them is a build plan — by default build/plan.lvplan. trident writes it; leviathan --plan <file> reads it. The plan is machine-generated, never hand-authored: it lists every resolved source (absolute path plus moduleId/origin), the explicit entry mode, and the module adjacency that drives phantom-dependency enforcement. leviathan never opens a trident.toml.

trident commands

trident build [dir]      # resolve trident.toml -> write plan -> leviathan --plan --build-native
trident run   [dir]      # ...compile and execute (tree-walk oracle)
trident check [dir]      # ...parse + resolve + type-check, no execution
trident emit-llvm [dir]  # ...emit LLVM IR
trident plan  [dir]      # resolve + write the plan only (no leviathan invocation)
trident --version        # trident's version, then leviathan's

Common flags, shared across the subcommands above:

FlagMeaning
--out <path>output path for the built artifact
--target <triple>target triple for cross-compilation
--opt-level <0|2>optimization level (--release is shorthand for -O2)
--plan <path>override the build plan location
--leviathan <path>override compiler discovery

trident locates leviathan in order: --leviathan$LEVIATHAN → its own sibling directory → PATH. Dependency-management subcommands — add, remove, update, lock, fetch, why — never invoke the compiler at all.

The manifest: trident.toml

TOML, fixed filename trident.toml (Cargo-style — not name-parameterized), discovered in the project root. trident owns this format exclusively; a small hand-rolled reader keeps the toolchain dependency-free — no external TOML library.

name    = "app"
entry   = "main"                # a function name, or a file ("run.lev")
sources = ["*.lev"]             # globs expand alphabetically; explicit lists too
assets  = ["views/**", "schema.sql"]   # comptime import() targets; optional
version = "0.1.0"               # optional
out     = "app"                 # optional

[[dep]]                         # repeated array-of-tables; omit if no deps
path    = "jsonlib"
as      = "Json"
version = "1.0.0"
dev     = false
FieldMeaning
nameproject name (metadata; default output name)
entrya function name (gather everything, call it) or a file name (that file's top-level statements drive the program). trident classifies which and records it in the plan — the compiler never sniffs the extension.
sourcessource paths relative to the manifest, glob-expandable ("*.lev")
assetsdeclared build inputs for comptime import(): literals, flat globs, or ** recursive globs (unlike sources' flat globs). Hashed by trident and carried in the plan. Absent means no assets; a glob matching zero files is a warning, a literal naming a missing file is an error.
[[dep]]dependency entries; omit for none
version, outoptional metadata
Project of one

A bare file with no manifest is a legitimate project of exactly one source. leviathan --imports/--graph/ --namespaces/--why synthesize a one-file map so the same tooling applies uniformly whether or not a trident.toml exists.

--namespaces and --why buy back the discoverability the by-name, path-decoupled namespace model otherwise costs: because imports are by name rather than by path, --namespaces is the symbol index (every namespace → its opening files and members) and --why <name> [in <file>] answers "where could this name come from, and which candidate wins here?" For teams who want Go/Java-style folder tidiness anyway, --lint-namespaces is an opt-in, off-by-default check that a file opening a namespace sits in a directory whose path is a case-insensitive suffix of that namespace's ::-segments — it prints per-file ok/WARN lines for CI, and never applies to dependencies.

Dependencies

Dependency resolution is entirely trident's job; leviathan only ever sees the resolved sources the plan names.

[[dep]]
path    = "jsonlib"
as      = "Json"
version = "1.0.0"
dev     = false
path
a local directory containing its own trident.toml, gathered recursively into the same whole-program compilation unit — a dependency is just more source. A path that is not a local directory (e.g. github.com/thaden0/json) is a VCS module, which requires a version.
as
aliases the dependency's exported namespaces into a synthesized local namespace, so uses Json; reaches a dep declared as = "Json" without knowing its internal namespace name. trident materializes the alias as a real source file under the build dir — the plan carries only on-disk paths.
dev
a development-only dependency, excluded from the shippable-artifact modes (build, run, emit-llvm) but visible to check.
Phantom-dependency prevention

A file may only uses a namespace belonging to the project itself or to a direct [[dep]] — reaching a transitive dependency's namespace without declaring it directly is a compile error (pnpm/Cargo-style strictness). trident decides the adjacency (the plan's edge rows); leviathan enforces it.

The leviathan compiler

Run directly, leviathan takes a file (or, via --plan, a whole resolved project) and a flag choosing what to do with it — parse only, execute, dump an intermediate form, or compile to a native artifact.

leviathan --build prog file.ext   # source -> native executable, one step
leviathan file.ext                # parse + resolve + type-check, dump class shapes
leviathan --run file.ext          # ...then execute (tree-walk oracle)

Run & inspect

FlagEffect
--run file.extexecute on the tree-walk oracle
--ir file.extlower to bytecode IR and execute that
--ir-verify file.extIR execution plus ownership soundness verification
--mem-verify file.extreachability oracle: cross-checks live/dead heap objects
--ownership file.extdump the ownership/escape analysis report
--resolve file.extstop after resolution
--tokens file.extdump the token stream

AST & rule stage

FlagEffect
--ast file.extdump the AST (pre-rule-stage)
--expand file.extre-emit the post-rule-stage tree as compilable source (round-trips)
--ast-after-rules file.extstructural AST dump after the rule stage (comptime folded)
--no-rules file.extcompile with the rule stage disabled
--comptime-budget Noverride the comptime step budget
--reentrant-budget Noverride the reentrant-rule round cap (default 8)

Native output

FlagEffect
--emit-cpp file.extnative backend: emit a C++ translation unit (compile with g++)
--emit-llvm file.extemit LLVM IR
--native-obj out.odirect object emission via LLVM's TargetMachine, no external llc
--build-native outsource → linked executable in one step (LLVM path)
--emit-elf out file.extfrozen pure x86-64/ELF backend, zero deps — see Backends
--build out file.extsource → native executable in one step (emit-C++ + g++)

Project-aware (also synthesized for a bare file — see project of one)

FlagEffect
--plan build/plan.lvplancompile a whole project from trident's resolved build plan
--imports file.extdump the file → imports provenance map
--graph file.extdump the uses include graph and build order
--namespaces file.extlist every namespace, the files that open it, and its members
--why <name> [in <file>] file.extwhere a bare name resolves from; which candidate wins for a file
--lint-namespaces file.extopt-in folder≈namespace convention check (off by default)
--assets file.extlist every comptime import()-consumed asset (path, bytes, hash, module)
Whole-program, always

Compilation is whole-program: all source gathers into one compilation unit, though namespace boundaries persist. Diagnostics carry source spans and do not stop at the first error — a build reports everything wrong in one pass rather than dribbling errors out over repeated runs.

Backends

Four engines execute Leviathan programs today: oracle IR emit-C++ LLVM — plus a frozen fifth kept only for differential testing. Three native backends consume the same IR; only entry-reachable functions are emitted, and out-of-coverage constructs fail with a diagnostic rather than producing a silently wrong answer.

LLVM
the primary, portable AOT backend (built against LLVM ≥ 18). Emits LLVM IR (--emit-llvm) or a direct object file (--native-obj), linking a portable C runtime plus a platform floor. Covers the full implemented language: objects, collections, closures, exceptions, the event loop, natives, async/await, sockets, HTTP. --build-native out goes straight from source to a linked executable.
emit-C++
always available (--emit-cpp); prints a freestanding C++ translation unit with an embedded runtime, compiled with g++ -O2. Covers the whole language except the event-loop/system layer (sockets, timers, async, file/stdin I/O) — those stay interpreter-bound for now.
Pure x86-64 / ELF
our own machine-code emitter and ELF writer — no g++, no assembler, no linker, no libc — talking to the kernel through syscall only. It once compiled the whole language, but is now frozen: kept as the differential-testing anchor and a possible zero-dependency bootstrap seed, and deliberately never extended further (e.g. it lacks the transcendental math methods the other four engines all have).
Not a project target

The pure ELF backend is reference-only. It is not something a trident.toml-driven project builds against — LLVM is the AOT backend for real artifacts.

Whole-program & ownership

One semantic IR underlies both interpreted execution paths. The tree-walking evaluator is the semantics oracle; the bytecode IR (a register machine) is the lowering target the native backends share. Both run against one runtime value model, and a shared corpus of real programs with expected outputs must produce identical output on every engine — differential testing across all five holds the implementation honest as it grows.

--ownership runs an interprocedural escape analysis over the IR, classifying every allocation site into one of three tiers:

ClassMeaning
scope-ownedprovably dies with its frame — freed at scope exit with zero runtime bookkeeping
ownership-transferredtransferred by return; the caller's scope inherits the free obligation
refcount tierstored in objects/collections, captured, thrown, or passed to a retaining/unknown callee

--ir-verify executes the program while checking the analysis empirically: every scope-owned allocation is verified dead at frame exit (weak-reference liveness), and any survivor is a soundness violation that fails the run. Over the corpus, 85–90% of allocations are deterministic — scope-owned or transferred — with zero violations. This same per-frame arena tier is what the pure ELF backend and, as of parity work on LLVM, the LLVM backend both use for scope-owned value-struct allocations; escaping allocations fall back to retain/release over a real free-list allocator.