Skip to content
zane-langPublic

About

The spec for the Zane programming language.

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

1 watching

Forks

The Zane Language Specification

This repository is a prior art publication for the Zane programming language. The techniques described in these documents were conceived and developed by the author and are dedicated to the public domain under CC0 1.0 Universal. Anyone may use these ideas freely, for any purpose, without restriction or attribution. No patent claims are made by the author.


Repository layout

spec/             ← the specification itself (start here): what the language does
stories/          ← design stories: how each part of the spec came to be
contributing/     ← style guides for writing spec docs and stories docs
memorybench/      ← reference C harness that models the memory design
langbench/        ← compiled Zane programs timed against the same programs in other languages

The rendered results of both benchmarks' pinned runs are published at https://zane-lang.github.io/spec/ and are redeployed whenever one of their pages changes on main. memorybench/ measures a C model of the memory design (spec/memory.md); langbench/ measures what a released compiler builds.

Specification documents

The specification lives in spec/ and is organized by topic. Each document has a single canonical home for its rules; everything else cross-references.

Foundations

Document Purpose
spec/foundations.md The few cross-cutting commitments the whole spec rests on: captured intent, staged compilation, fixed layout, opt-in identity, and strictness-as-performance — start here

Reference documents

Document Purpose
spec/syntax.md Canonical surface syntax for every construct
spec/lexical.md Case-sensitive parsing, identifier formation, the casing-determines-kind rule, and numeric and string literal forms
spec/glossary.md Canonical names for recurring concepts and coined spec terms

Type system and runtime model

Document Purpose
spec/types.md Fundamental types, value/reference types, fields, constructors, implicit conversions, and type/alias declarations
spec/adt.md Enums, variants, the struct/variant symmetry, pattern matching, match, and enum maps
spec/functions.md Methods, free functions, subscripts, overload resolution, function values, and lambdas
spec/generics.md Unified type parameters, <> type expressions, constructor calls, and the Array, ArrayRef, and List container primitives
spec/memory.md Ownership, settled and roaming owners, references, and arena layout
spec/lifetimes.md Lexical lifetime rules, moves, scope rules, and deterministic destruction
spec/effects.md The effect model, mut, read-only bindings, what each kind of verb may write, and capability wiring
spec/concurrency.md Implicit parallelism, spawn, water-tower lifetimes, and concurrency safety

Programs and program structure

Document Purpose
spec/control-flow.md Block arguments, the three control-flow intrinsics, the core branching and repetition built on them, and 1-based ordinal rules
spec/operators.md Operator set, derived operators, precedence, Boolean algebra, and loose forms
spec/error-handling.md Bifurcated return paths, ? handlers, and abort/resolve semantics
spec/packages.md The lib/, bin/ and test/ project layout, library packages and their subpackages, file-scoped imports, explicit member access, package visibility, package-scope state, and the program and test packages that hold main
spec/dependencies.md Package identity, manifests, test dependencies, version pinning, fetching, and caching

Design stories

The spec states what the language does; the why lives in a parallel set of stories docs under stories/, one per spec document. Each reads as a history — a handful of thematic chapters that recount, in the order the thinking moved, the situation that forced a choice, the roads not taken, and the costs accepted — kept separate so the spec stays terse while the reasoning has room to breathe. They are appended to as the design evolves, so each is the record of how its part of the spec came to be, not just why it is the way it is now.

Document Tells the story behind
stories/foundations.md spec/foundations.md — the bets behind captured intent, staged compilation, strictness-as-performance, and the collapse of value/reference into one # axis
stories/syntax.md spec/syntax.md — why a declaration names its type and writes the name before it, the one shape that turned out to cover constructors, constants, and function values, the two signature orders and the form rejected for a named verb that became the lambda variable, the word and separator budget a line is held to, why every commented line carries its own marker, the unnesting pipe that split what it unnested and left once a { } could trail, why a reference document has a story at all, why intrinsic names the whole @ space rather than any one namespace in it, and the list of callbacks that asked for braces, put the subscript on trial against a getter and a borrow, and stayed inside the parentheses
stories/lexical.md spec/lexical.md — casing as a hard-error kind signal and the <> disambiguation it buys, why field privacy lives in the name, first reaching across packages and then held to the package that declares the type, a delimiter for each kind of separated thing, and the axis that turned out never to have held — replaced by letting the bracket pick the separator, and the map literal that stood alone with no token in front of it, which retired the reassurance the bracket rule closed on and made banning the empty literal the thing that settles every brace, the audit that followed, which found the enum map wearing the wrong bracket all along, and the semicolon added to statements so that a brace could take over ending them, and separating string literal structure from consumer interpretation
stories/types.md spec/types.md — the two-axis value/reference model, why # marks a distinct type rather than a box, constructing a type by what it is, confining # to the body forms, why every user-defined type must be named, dropping the tuple mould once a named struct does the job better, adding named constructors, naming the enum's shape the peer mould, making Unit a real fundamental unit type, and the concept-typed lowering exception that grew from one legal line into container-wide coercion before it was taken back out, and the literal that had no way into storage — the storage primitives that construct explicitly from their literal's concept, the literal concepts renamed to what other languages call their literals, and the string primitive that became an owned value when interpolation met the implicit-constructor source restriction, and reading a number from text as a method that aborts, since a constructor cannot
stories/adt.md spec/adt.md — splitting enum from variant against the hype, the shared struct body, escaping the matcher machine with case overloads and the turn to a central match block, matching variants rather than patterns, keeping enum data outside the members, reducing a match group to sugar for one arm per case, building a variant by naming a case rather than calling a constructor, the bindings that existed only to be pointed at, and making a recursive member an owned child boxed through a hosting handle rather than an &, and the sum that could not contain itself until a value copy became deep, and why a variant payload cannot originate a guest even though the whole reference variant can, with displaced hosting payloads floating within the same owner
stories/generics.md spec/generics.md — the parameter model, the <>/() split, size-in-the-type, the deferred features, why a language without pointers needed a second, dynamically sized container primitive, and why an integer literal and a decimal literal carry different concept types, with the number parameter taking the integer one, and why a wrong-kind type argument is reported where the type was supplied
stories/dependencies.md spec/dependencies.md — URL identity, the manifest/resolution split, prebuilt distribution, symbol-rewriting, the browsable global cache, the package-graph acyclicity rule, opt-in remapping, why core became a bundled implementation package, the floor that finally made it an ordinary one, moving prebuilt objects to Release assets with committed hashes, and recording where each dependency's code comes from — a release, a source build, or a local path — beside a lock file renamed zane-lock.coda, the manifest field that says whether a project is a library or an application, and the zane command that launches the pinned compiler without being pinned itself
stories/memory.md spec/memory.md — the no-GC-no-lifetimes goal, the move problem and the anchor, lazy backpointer creation, the indexed heap table, the rooted-guest rules and the host/guest terminology split, the collapse to one value/reference axis with a borrowed subject, the shift to segmented chunked bump arenas, the split into fixed-size and dynamic regions with anchors moved to a runtime-global recyclable pool, taking the bare symbol away as a guest source, the three passing modes that split out of it, giving both back when the ban cost more than the question it closed, and the dynamic region taking the boxes, each asking for exactly its own size, once recursion became hosting, and what a copy is for — the deep value copy that lets a value type recurse, and the reference-field ban that survived it, and the stable-place boundary that excludes subscripted paths and variant payloads from guest sources, and same-owner floating when contingent hosts disappear, what a move does to guests of the moved struct's fields, strings keeping their dynamic bytes without reference identity, the settled/roaming split that stopped guestable hosts from moving, the borrow returning as the bare spelling of a reference parameter, guests becoming plain offsets once anchors, backpointers and forwarders had nothing left to do, settled overwrites staying in place with only an escape relocating, and ArrayRef giving many settled objects a home, and dropping the verbless guest for reference and host for owner, with the store rule's lifetime becoming the scope, then a call that may not write what it borrows, a destination located after its value, no growable list of references, and overlap through a reference checked by the caller that knows where it points, and the bound on the fixed-size region that the root manifest sets once a scope's slots became one frame in a contiguous range, and native addresses replacing segmented offsets and their 32 GiB ceiling
stories/lifetimes.md spec/lifetimes.md — lexical scope in place of a borrow checker, what may be moved, the declaration-block rule that kills flow analysis, downgrade instead of use-after-move, parameter-rooted returned guests, why each strict rule is the minimal guard against one specific memory corruption, narrowing a returned guest's root to a guest parameter once borrows arrived, the root rule going back to "any parameter" once they left, the scope check that fired once at wiring and could be outrun by a later move, and the two lifetimes hiding behind that fix — a block and a hosting tree — which collapsed the raise enumeration into one owner comparison made at every store, paid for with a signature that records where a parameter comes to rest — and the stricter init-as-empty-template design that would have needed no signatures at all, kept on record with the two costs that turned it down, and the later distinction between an owner's lifetime and an interior place stable enough to originate a guest, plus same-owner floating when that contingent place disappears, and a moved host becoming spent rather than a guest, the three contradictions found by running the examples as compiler tests, and the parameter that lived at the call site going away once nothing guested could move, a call through a function value taken to store every reference, and package constants becoming return roots while an &T parameter stopped being a store root
stories/effects.md spec/effects.md — inferring effects instead of annotating them, subject-scoped mut, capabilities in place of ambient I/O, the four-level ladder and the Total-Pure/Pure split, what deliberately is not an effect, and mutation through a borrowed subject, and where the first capability comes from: the runtime and console as objects only the root package reaches, and a mutating call as a write, which retired the four-level ladder, and the console moving into core once field privacy had put every verb on text there, and the runtime's arguments as the first read of capability-backed state, added so that a benchmark could have an input the compiler cannot evaluate ahead of time
stories/concurrency.md spec/concurrency.md — the parallelism/concurrency split and the refusal of async coloring, why spawn marks only a call, water-tower lifetimes, signature-based safety without locks, value-typed mutation closing the aliased-write gap, and the two safety rules that turned out to enforce less than they claimed
stories/error-handling.md spec/error-handling.md — the two-doors model and why failure is control flow rather than a Result value, resolve as expression-substitution rather than assignment, typed abort paths and the deliberately-absent propagate operator, keeping abortability orthogonal to effects, and explicit path values through Unit
stories/control-flow.md spec/control-flow.md — guard as an active exit that opens no scope of its own, doing without while behind a written loop bound, one-based counting after the loop that forced the question, why control-flow contracts use fundamental semantic types, and then the move that took branching and repetition out of the language entirely: the versioning pressure behind it, the block that captures because it is not a value, the capture spawn would have broken, the two intrinsics stated over storage primitives, the one exit a call could never become, and the exit that took no condition after all — a second bool nobody should have had to declare, and the "one level shallower" objection that turned out to be the mechanism — guard re-declared as four lines of core, and the block that stopped yielding a value once core found no intrinsic to hand one back
stories/functions.md spec/functions.md — pulling methods out of the type body and the verb model that revealed, mutation made visible with :/!, overloading on parameter shape alone, why callables are call-only while self-typed lambdas are values, why every return carries an explicit value, dropping the inherited word "receiver" for subject, and what this kept once private access moved to the package
stories/operators.md spec/operators.md — the fixed vocabulary worth overloading, ~ as the universal flip, laws enforced through derived operators, grammar-only grouping, home-package coherence, the keyword that was neither operator nor function and became Bool's share of the existing set, and the loose form that gave conjunction a tier below comparison
stories/packages.md spec/packages.md — the directory as namespace and compilation unit, declarations as move checks, explicit qualified access through $, keeping mutable state inside values so the effect model can see it, and the import forms that let a file state its own spelling once core became a package everyone writes, and the manifest field that names a package once every project's source sat in src/, a library's tests living in test/ as packages that import what they test the way a consumer does, the tree of nested packages that a shared dependency broke and the lib/, bin/ and test/ layout that replaced src/ along with the manifest's name and kind, and the subpackages that give a large library package internal structure only its parent reaches

Contributing

Style and structural conventions live in two sibling guides; read the relevant one before editing or adding a document:

Markdown formatting

Every heading in every Markdown file here — spec/, stories/, contributing/, and this README — carries a blank line above and below it, at every heading level. The file's own edges are the exception: a title on line 1 has nothing above it to separate it from, and a heading that ends a file has nothing below. That is markdownlint's MD022, and the committed .markdownlint.jsonc is the canonical statement of it. It is also the only rule this repository has taken a position on: everything else markdownlint ships is switched off there, so a clean run means the headings are right and says nothing about the rest. Check a change with

npx markdownlint-cli2 "**/*.md"

A workflow runs that same command on every pull request. It names no rules itself — it finds the config the way the local run does.

About

The spec for the Zane programming language.

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages