These documents are the language. The compiler and the reference interpreter share no code; what they share is this specification, the conformance corpus, and the protocol they are compared through. When the two disagree about a program, the specification is what gets read, and sometimes amended.
Every document carries clause anchors in brackets, spelled like
[mem.region.create.3]. A diagnostic cites one, a trap cites one,
a corpus program declares which ones it conforms to, and the anchor index is
checked against the prose so a citation cannot point at a clause that was
renamed. Files are served as plain text.
The documents
- 01. Surface Grammar (81 KiB)
- Lexing, the mode stack that lets strings nest inside strings, the
expression grammar and its precedence tiers, statement and item forms, and
the ambiguity rulings. Status: normative,
grammar/1, declared at v0.1.0 and held additive-only through the 0.1.x line; at v0.2.29 it is stillgrammar/1— a removal or narrowing would be agrammar/2act, and none has been taken. Every change to it since has widened it, which is the additive direction. v0.2.11 gave the pattern grammar a range form,[gram.pat.range]:lo..hiandlo..=hiover integer andcharliterals are patterns wherever a pattern goes, both ends literal because an open end is how a slice is spelled, overlap legal with the first arm winning, an arm nothing can reach E0802 and a range no value can match E0815. v0.2.13 added three forms at once — the list literal,[gram.expr.list], with position settling the clash against indexing so[10, 20, 30][1]needs no lookahead; theerroralias item,[gram.item.error], witherrorstaying contextual solet error = 1still parses; and the nullary variant pattern,[gram.pat.nullary]— andthenjoined the contextual table. Every program that parsed before still parses. v0.2.15 widened it once more and wrote two refusals down without moving the line:FORMAT_SPECis a production now — fill, align, sign, width, precision and type — where the grammar had carried a colon and a pointer to the types document, while[gram.expr.variant]and[gram.type.start]give anchors, and one of them a machine-applicable fix, to two inputs that were already refused. The accepted language is the same; the diagnostics moved. v0.2.17 widened it by one production: the right-hand side of a plain=whose place is a container element may be spelledtake—xs[i] = take v,m[k] = take v— and nowhere else in an assignment may a mode appear ([gram.expr.assign]). It ispush(take x)'s twin and means what that spelling means: the stored value moves, where the plain store now copies. v0.2.22 closed the attribute set without a production moving:[gram.item.attr.set]lists the attributes a pass reads, and one wolf does not know, or knows and does not implement yet, isE0817by name where it used to compile and mean nothing;[gram.item.attr.cfg]evaluates#[cfg(target = "…")]against the build's target, which was ignored, so target-gated code had compiled for every target. v0.2.23 widened the grammar by one production,extern_let_item:extern "c" let NAME: *Tnames a symbol the link defines, with no initializer, and the parser reads avar, aconstor an initializer there only so the checker can refuse each by name (E0821). The attribute table gains#[repr(c, packed)]and#[repr(c, align(N))], and a representation that cannot be laid out as written isE0820. At v0.2.29 the document and its EBNF are v0.2.23's, byte for byte: every release since wrote its clauses into the memory, concurrency, ABI, conformance, package, types and OS documents, and the spellings v0.2.26 added —-> never,!on an integer andcopy region { … }— parse under productions the grammar already had. Anchors[gram.*]. - 02. Memory Model (131 KiB)
- Regions, ownership tiers, moves, freezing, the shared and handle edges,
provenance, and the closed list of rows that are undefined behavior with
the optimization each one licenses. The interpreter implements this
document as a dynamic machine; the compiler implements a static
approximation of it. v0.2.11 added
[mem.str.imm]: astrnever changes after it is built and a slice of one is a view, so there is no place on the left ofs[i..j] = …— a rule rather than a thing the backend has not got to, and E0416 says so. v0.2.14 added[mem.region.proc]: a spawned proc owns its ambient region, so bytes built inside it cannot be handed out of it — E1010 — and the fix the diagnostic prescribes is to build them in the spawner and pass them in. v0.2.15 was the first release that changed what a program you already wrote means, and this is the document that says why. A builtin container store copies its element, at[mem.tier0.move.3]'s cost: keeping the caller's handle alive inside the container was[mem.tier0.excl.1]violated with a builtin as the callee — two writable paths to one value, nothing spelled, nothing reported.push(take x)is the spelling that hands the value over instead, and a store is the onereadposition where a call site may name a mode at all; the same clause ([mem.tier0.mode.read]) rules that a move out of areadparameter is lent and may not outlive the call. Nothing warns, because there is nothing wrong with the new answer. v0.2.16 was the largest change this document had taken since, and the part of it a reader meets is a correction rather than an addition:moveout of a place whose type is copyable had recorded no move at all, so the place stayed readable and printed the value that had been moved away, on every tier. The document is explicit now that the operator empties the place whatever the type, and E1001 answers a read of it afterwards. Two clauses arrive beside it — a region's root ([mem.region.root]) and what an immutable value returned out of a region means ([mem.region.imm.ret]). v0.2.17 carried the store's half of v0.2.15's rule to the index:xs[i] = vandm[k] = vcopy a non-Copyvalue in and leavevlive, exactly aspushdoes, andtakeis the spelling that hands it over ([mem.region.edge.elem]). Beside it,[mem.model.place.rhs]wrote down the order every machine already kept: a store evaluates its right-hand side first and mints its place afterwards, so a right-hand side that grows the list it stores into cannot leave the store aimed at the old buffer. The order of the place's own operands against the right-hand side was not ruled by it, and the machines were not agreed there. v0.2.18 ruled it and answered two questions the element had left open. The place's operands come first, left to right and outermost first, then the right-hand side, then the address, then the store — soxs[idx()] = val()callsidxfirst on every machine, the checked one included, which had calledvalfirst.[mem.model.place.elem]says which elements of a container are distinct places: two integer-literal indices with different values, two literalMapkeys, an element against its container'slen; everything else is one place, and a store revives a moved element only through an index that surely denotes it, a key local of any copyable type unwritten since the move included. And[mem.tier0.mode.mut]gains the sentence two silent wrong answers were missing: amutparameter is initialized at every return of the function, so moving out of one and returning without storing a value back isE1001. v0.2.19 carried the distinct places from moves to claims. Exclusivity, borrows and iteration read[mem.model.place.elem]per element now, soadd2(mut xs[0], mut xs[1]),add2(mut g[0][1], mut g[1][0])andbump(mut xs[0], xs.len)hold two claims that cannot meet, and every shape the clause calls one place — a run-time index, the same literal twice, a path and its prefix — is still refused. And the clause names what reads a container's header:len,countandis_empty, and nothing else, so each is allowed beside a moved or claimed element, while every other method and a slice read the whole container. v0.2.20 made arguments two-phase,[mem.tier0.excl.4]: within one call a later argument may read what an earliermutargument claims and may not write, move, re-claim or lend it, sogrow(mut xs, xs.len)runs and a write to the claimed place inside a later argument isE1002. The same release let one call's two claims reach two elements through an offset or a loop index the clause proves distinct. v0.2.21 wrote down when anerrdeferruns: when its scope is left on the error path, and a block whose own value is an error has left that way, exactly as a function has. v0.2.22 made aforbinding carry its iterable's sites, asxs[i]does, so a piece of a region-builtstrheld past the region by a loop isE1010— the compiled tiers had printed it from freed bytes — andcopy sof a region-builtstr, which shares its bytes, is held to the same rule. And[mem.unsafe.sig]says where a*Tmay stand: in a module-private signature, whose every caller is inside the audit granule, and on either side of the C membrane, never in apubone. v0.2.23 converted integers and pointers both ways ([mem.prov.expose]), prefix*pisp[0]by another name, and on the freestanding target an address no allocation owns is the platform's ([mem.prov.device]), where a hosted target keeps it undefined.[mem.unsafe.volatile]definesp.read_volatile()andp.write_volatile(v): one aligned access of the pointee's width, never elided, split, merged or reordered against another, on a pointee that is one fixed-width integer orbyte, anything elseE1307. And[mem.static]gives modules state: every initializer is evaluated at compile time into the image, one that reads avaror needs run-time code isE0705, and avaris raw-tier,E1301outsideunsafe. v0.2.24 made alignment part of a raw access,[mem.unsafe.raw.4]: an ordinary access through a*Tat an address that is not a multiple ofT's alignment is undefined, row L4, which licenses the compiled tiers to emit every raw access aligned, with no check and no split, and a packed struct's field is not that row, the struct's alignment being one byte. v0.2.25's[mem.static.2]says what a call does to that memory on every machine: a call may write any module state and any storage a raw pointer or anextern "c" letreaches, so no value read or stored before a call stands for that memory after it, which native and release had broken. At v0.2.26[mem.region.copyout]gives a region block one way out:copy region { … }deep-copies the block's value into the region it was entered from and then frees its own, and every other escape is stillE1010.[mem.region.escape]makes a call to a function returning astran allocation site where it is made, which closes a read of freed bytes on the checked, native and release tiers.[mem.static.4]reads another module'spubitem asm.K, avarso named raw-tier;[mem.unsafe.raw.5]stores one field of a raw element at its C offset, touching no other byte; and[mem.list.bytes]gives aList[byte]thefindandcountastrhas. At v0.2.28[mem.region.escape]came to read a call's signature for what its callee can keep: amutargument, the receiver included, or a non-Copyresult shares the bytes of every other argument that can reach it, so a string built in a region block and handed to a callee that may keep it in an outer list isE1010at the call, where only the direct store had been refused; acopyof an aggregate shares its strings and aMapkeeps the key it is written through, so each of those is held to the same rule. The callee's body is never read, so some correct calls are refused too, and the clause names the edit that is sound at the call. Anet_readresult is an allocation site where it is read. Anchors[mem.*]. - 03. Concurrency (53 KiB)
- Structured tasks, supervised procs, channels with region transfer,
select, the memory model's happens-before rules, and the deterministic
schedule a seed selects. Written in the shape of the Go memory model
document, with the advice in front of the formalism. At v0.2.11
[conc.chan.close]finally typed what it had described for two:sendis() ! {closed, cancelled}, the same two tags the receive side carries, because a send blocked on a full buffer is a cancellation point too. v0.2.15 added the first combinator that is a scope:[conc.task.par]definesxs.par(f)by desugaring to ascopeofspawns, so it adds shape and no semantics. Results come back in input order whatever the schedule ([conc.task.par.order]), the tasks join inside the call, a write to captured enclosing state is E1101 at the write with theparnamed as the spawn site ([conc.task.par.capture]), and the index range is cut into contiguous chunks — never a task per element ([conc.task.par.chunk]). The cost clause,[conc.task.par.cost], promises no speedup and says so in the document: a list shorter than the worker count, or anfcheaper than a spawn, can make it slower thanmap, and both are conforming. v0.2.16 gave the two handles names a signature can write.[conc.task.scope]has said since the first edition that a function which spawns into its caller's scope takes the handle as a parameter, and until v0.2.16 there was no way to spell that parameter at all — the capitalised names were unknown and the lowercase keywords were refused in type position.ScopeandProc[T]elaborate in a signature now, the way a channel does, and the handle carries the completion type rather than standing bare. Beside it a proc talks back:[conc.proc.join]blocks until the proc exits and answers the value it produced, or the tag for the class it failed in, where before a supervisor could read only the class and the output.[conc.proc.handle]and[conc.proc.arg]are the clauses under it. The playground has the other half of that story: the corpus program that writesProcin a signature was the one the tab refused at v0.2.16's pin, and the interpreter it runs has known the name since. v0.2.24 grew it more than any other document, with atomics on raw pointers:[conc.mm.atomic.raw]defines a load, a store, a swap, the read-modify-writes add, sub, and, or and xor, and a compare-and-swap on a*Twhose pointee is a fixed-width integer, insideunsafe, each with an order written at the call ([conc.mm.atomic.order]), and[conc.mm.fence]definesfence(o), of whichfence(Order.seq_cst)is the one a safe program may spell. A pointee that is not an integer isE1308, an order the operation does not admit isE1309, and the checked machine runs every order asseq_cston its one task ([conc.mm.atomic.raw.5]). Since v0.2.28 the checked machine runs the concurrency tier it had refused by name —scope,spawn, procs,select,when, closures andxs.par(f)— one task at a time under the deterministic scheduler a seed selects ([exec.checked.task],[exec.checked.sched],[exec.checked.race]). And the call rule[mem.region.escape]states carries to a channel: a callee handed a channel that can carry bytes, and everyspawn procargument, must outlive the frame, as a sent payload must ([conc.chan.payload],[conc.proc.arg]). At v0.2.29[conc.task.order]restates what a parked task costs the host. Compensation has no cap of its own: the native runtime holds a thread for every task parked in a wait, so a program that parks a task for each connection holds as many as the host lets it start, and a thread the host refuses is reported once on stderr. A parked task costs its thread and no cpu, a wait in the reactor, a channel operation or awhensleeping until its completion, its deadline or a cancellation with no system call meanwhile;os_signal_waitis the wait that still polls, and the clause says how often. Anchors[conc.*]. - 04. ABI (38 KiB)
- Layout, calling convention, and the foreign boundary, differentially
fuzzed against platform C compilers. It is the document v0.2.22 grew
most, and v0.2.23 grew it most again. v0.2.22's
[abi.target]and[abi.target.none]define the freestanding target: an object with nomainshim and no runtime, one trap hook the program supplies, the code generation a kernel needs, and everything that needs the hosted runtime or an allocator refused by name.[abi.asm]covers the assembly a manifest lists and the roster a call into it must name.[abi.c.export]and[abi.c.import]are the two directions across the C membrane — anexport fnis a C function, and a hand-declaredextern "c" fnis callable insideunsafe— and[abi.layout.c]holds a#[repr(c)]struct to C's layout through a raw pointer, where the compiled tiers had packed it. At v0.2.23[abi.layout.packed]and[abi.layout.align]lay out#[repr(c, packed)]and#[repr(c, align(N))]as C does, and[abi.layout.query]answerssize_of,align_ofandoffset_ofat compile time; a packed field lent isE0819.[abi.link]places code and data with#[section(".name")], names a symbol the link defines withextern "c" let, and leaves the image to the consuming build's linker script.[abi.interrupt]says how an interrupt reaches wolf, which has no interrupt calling convention: an assembly trampoline saves the registers and calls anexport fnwith the frame. v0.2.24's[abi.target.none.alloc]lifts the freestanding target's refusal of allocation:List,Map, interpolation, a capturing closure andregioncompile againstlibwolf_rt_none.a, the region runtime built without the standard library and shipped besidewolf, over awolf_allocandwolf_freethe program supplies, whilePool,freeze, channels,spawnandparstay refused by name. v0.2.27's[abi.target.none.ambient]gives each of a kernel's threads its own ambient region. Anchors[abi.*]. - 05. Conformance (34 KiB)
- What makes the rest of it testable: clause anchors, test tagging, the
closed trap vocabulary, and coverage reporting. The interpreter reads only
the specification and the corpus, and this document is part of that sealed
interface. Its own register of anchor namespaces has been wrong in both
directions, and is checked in both now. At v0.2.6 it named seven while the
documents were publishing anchors in eleven, and the four unlisted ones
were appended. At v0.2.7 the mirror closed: 07-schedule-points.md
had been declaring seven
[sched.*]anchors that no register carried, so no extractor read the document and no gate anywhere held an opinion, while the runtime cited four of the seven from live scheduler code.schedis registered now and its anchors are published, 424 to 431. Neither change renumbered anything, because a published anchor is never moved and the citations that would have had to move number in the thousands. At v0.2.16 it ruled the exit status, which had been left to habit for every class but the trap:[conf.exit]makes a rejection 2 and a refusal 4 on every implementation, where the compiler had spelled both 1 — and 1 is what a program that ran and returned an error out ofmainexits with, so three outcomes had been collapsed onto one number. The trap keeps its per-implementation numbers, because those are forced by a platform convention and these are forced by nothing. The status is not injective and the clause says so: the guarantee is that a rejection or a refusal never exits 0 or 1, and a harness that needs the exact answer reads the record.[conf.exit.class],[conf.exit.refused],[conf.exit.static],[conf.exit.collide]and[conf.exit.test]are the clauses under it. Anchors[conf.*]. - 06. Differential Protocol (18 KiB)
- The two-implementation contract, and the only shared artifact between
the tracks. It defines the observation record: the phase reached, the
verdict, the diagnostics as code and span with no message, and the output
digest. Diagnostic wording is deliberately excluded, because wording is a
per-implementation concern and comparing it would manufacture
disagreements. The playground can show you a record for any program you
run. At v0.2.16 the record gained a trap's own words and where
it happened (
[proto.record.trap]), and a passing comparison gets a shape of its own ([proto.record.pass],[proto.cmp.pass]). The message is still never compared, because wording is a per-implementation concern; it is carried so a reader of the record can see what the machine said. v0.2.17 let a diagnostic say which file it is in ([proto.record.diag]): a span from a sibling module used to read as an offset into the entry file, a wrong place given silently, and the record carries afilestable and afileindex on each diagnostic — only when some diagnostic lies outside the entry file, so every other record is byte-identical to what it was. v0.2.22 made a refusal carry one code on every lane ([proto.record.first]): the diagnostic at the earliest byte offset is first, and at one offset the earlier phase, where the record and the screen had been able to name different codes for one file. Anchors[proto.*]. - 07. Schedule Points (11 KiB)
- Where a schedule can make a decision, and the numbering a replayable
counterexample uses. Normative for shape and vocabulary, and append-only
from here. Its
[sched.*]anchors are published in the index as of v0.2.7; before that they were declared here and carried by no register, so a program citing one was rejected for citing a clause that existed. Anchors[sched.*]. - 08. Packages (7.5 KiB)
- The package layer's formats, under one covenant: reading the dependency
graph never executes anything — no build scripts, no hooks, no fallbacks.
The manifest is data, resolution is minimal-version, and the registry
entry's shape is fixed before any hosted registry exists to serve it.
Since v0.2.26 a package's capabilities are what its code
reaches: a host builtin named in its own files charges the capability
its family carries, as an import of
std.fsalways did, a std module charges its importer for what its own code reaches, andE1504refuses whichever one the manifest does not declare;wolf auditderives the same set from source, with a reason per capability. v0.2.27 counted a C declaration among the reaches: a bodylessextern "c" fnor anextern "c" letcarriesffiwhere it is declared, called or not, asimport ccarries it at its line. Anchors[pkg.*]. - 09. Constant-Time (8.9 KiB)
#[consttime]as a verified contract: secret-tainted data cannot decide a branch, index memory, pick a call target, or feed a variable-time instruction, refused fail-closed on every lane in every build mode. Anchors[ct.*].- 10. Types (97 KiB)
- The numeric-literal typing rules written down in one place: how a bare
literal takes its type, int adopting float in mixed arithmetic, and the
casts that trap instead of wrapping. It is also where
byteis declared — the 8-bit unsigned octet the toolchain gained at v0.2.4, with the clauses for its cast in both directions, the widening its operators do before they compute, and what an interpolation hole prints. It is the document v0.2.13 grew most:[type.list.lit]types a list literal from its elements or from the type expected of it,[type.range]makesrange[int]andrange[char]types in every position withendalways exclusive, and[type.err.alias]makes an alias the same type as the row it names, both ways, a cycle being E0610. v0.2.11's clauses are still here:[type.unit]writes down what a unit context is — a loop body, an else-lessif, a unit function — and rules that a!()tail in one is a warned discard rather than a mismatch, with the counter-case for a task closure whose scope consumes the row.[type.fn.ret]checks a body's tail against the declared result and[type.row.operand]refuses a bare row as an operand; both were already enforced and neither was written. It is the document v0.2.15 grew most, as it was v0.2.13's: a method call is defined as the function it is ([type.method]—xs.map(f)ISlist.map(xs, f), resolved in the receiver's home module), the combinator set is closed at ten functions plus one builtin ([type.comb.set],[type.comb.builtin]) with what each one copies written down ([type.comb],[type.comb.eager]),takeis the ownership verb and never a method name ([type.method.take]), a type parameter binds once ([type.generic.bind]), and the interpolation format specifier gets the clause the grammar's new production points at ([type.interp.spec]). v0.2.16 gave it one clause,[type.err.alias.qualified]: an error alias named through the module that declares it is the same type as the row it names, exactly as the unqualified spelling is. v0.2.20 wrote[type.row.else]: anelsehandles its scrutinee's own row and nothing a nested call raised. v0.2.21 gave the row its two remaining clauses.[type.row.match]splits amatchover a fallible value into two halves — an arm naming a tag of the row handles the error, any other pattern handles the value, both must be covered, and a tag that is also a variant of the value's type isE0816— and[type.row.defer]refuses a?inside a deferred expression,E0611. And[type.numlit.value]settles a literal written at a binding with no annotation: it takesi32there, and a later use no longer widens it. v0.2.22's[type.numlit.cast.narrow]makes an integer cast keep its value or trapoverflowon every machine. A change of sign is a narrowing cast — native and release had reinterpreted the bits, so-1 as u64printed-1— and truncation is spelledv as wrapping[T] as T. At v0.2.23[type.unit.context]'s list of unit contexts has no exception: an else-lessifthat is itself the tail of a fallible function discards its branch's raise, warned, where every machine had handed it to the caller with no warning. At v0.2.26[type.fn.never]named the bottom type: a function declared-> nevernever returns, a call to it fits wherever a value is wanted, and a body that can reach its end or holds areturnisE0401; and[type.int.not]makes!on an integer its bitwise complement at the operand's own width,-n - 1, total, where it wasE0409. v0.2.27's[type.wrap.div]makes/and%on awrapping[T]divide the values the operands hold, sowrapping[u64]'s(2^64 - 1) / 10is1844674407370955161, and an unsigned or wrapping integer prints as the value its type holds; the compiled tiers had printed awrapping[u64]as signed and refused or trapped its division. Anchors[type.*]. - 11. OS surface (157 KiB)
- The operating-system surface the builtin tier exposes. v1 covers signal
reception and OS randomness — the latter with no fallback and no seeding,
so failure traps honestly. The socket section opened at v0.2.4 with
unix-domain streams in the TCP pair's shape and the error rows that tell
an unserved host apart from a bad path; at v0.2.5 it gained
what a serving loop needs — one wait over a whole set of handles
(
[os.net.wait]), a listener several processes may hold at once ([os.net.listen.opts]), descriptors handed down across a spawn by position ([os.proc.inherit]), and the count of cores this process may be scheduled on ([os.cpus]). At v0.2.6 it stated what those hands actually see when a connection arrives ([os.net.accept]): a wake is not a connection, so the hand that loses the race carries no answer and waits again against the budget its deadline armed, in the runtime's wait rather than the kernel's. At v0.2.8 it stated when the syscall happens ([os.net.io]: a parking call tries the syscall before it waits, and waits only when the kernel says it would block), and gained three calls that name what a serving loop was doing by hand — the Nagle switch every stream is handed with ([os.net.nodelay]), the gathered write ([os.net.writev]), and one metadata read on an open handle ([os.fs.fstat]) in place of a path stat per fact. The first of those moved a cost and no row; the other three were new surface, and every one of them serves every tier-1 host. v0.2.14 added one call beside the gathered write,[os.net.writev.head]: astrhead handed towritevwith noList[byte]built for it. At v0.2.15 the document stopped being about sockets alone: it gains the host a program is running on and the signals that host can deliver ([os.host],[os.host.sigs]), the path of the running executable ([os.proc.exe]), what a spawn does with the streams it is not handed ([os.proc.spawn], beside[os.proc.inherit]'s existing rule), and the two filesystem calls a program writing files needs before it needs anything else — path composition and removal ([os.fs.path],[os.fs.remove]). v0.2.17 ruled which paths those calls take ([os.fs.path.domain]): every path the host lets the process reach — absolute, relative, climbing, built at run time — with what the host refuses coming back as the host's own row. An implementation that serves the tier over a narrower set declines the rest by name, never by a row, and judges the boundary on the resolved path, not its spelling. The compiler already served the whole domain; lupin 0.1.40 was the first release of the interpreter to serve, at a terminal, a path that climbs out and back in. At v0.2.22 a program could name an offset for the first time:[os.fs.seek],[os.fs.tell]and[os.fs.read_at]move, report and bypass a handle's cursor, with a new tag,unseekable, for a pipe, a socket or a terminal, because a program falls back to reading forward there. And[os.fs.std]makes the process's standard streams the handles C gives them, so the first handle an open answers is no longer the same number as standard input. At v0.2.26 those streams carried bytes, the byte and text calls serving 0, 1 and 2 in program order withprint; a child's descriptors are the program's to set ([os.proc.fds], with the two ends[os.proc.pipe]answers); the working directory moves ([os.fs.chdir]); a descriptor says whether it is a terminal ([os.fs.isatty]); one call copies between two handles the fastest way the host offers ([os.fs.copy]); and the host's error number travels beside theiorow, never on it ([os.fs.error]). v0.2.27 let a program survive a signal and own a terminal — a meaning's disposition ([os.signal.disp]), a queue it can poll without waiting ([os.signal.poll]), a job spawned into its own process group and how a child ended ([os.proc.job],[os.proc.status]), and the terminal's foreground group and mode ([os.term],[os.term.mode]); become another program and remove a variable ([os.proc.exec],[os.env.unset]); read the full stat record, a link and a typed listing ([os.fs.stat],[os.fs.readlink],[os.fs.readdir]); and it ruled a repeated json name last-wins ([os.json.dup]). Since v0.2.28 a program changes a file as well as reads one: its mode, a creation's permission bits, a symbolic or a hard link, its times, its length and the process's umask ([os.fs.mode],[os.fs.create.mode],[os.fs.symlink],[os.fs.link],[os.fs.times],[os.fs.truncate],[os.fs.umask]), each judging its arguments first, then answeringunsupportedby name where the host has no such operation. At v0.2.29 the signal set holds every signal POSIX names ([os.signal.set]): the meanings a program may listen for, ignore, wait for and poll, and the ones it may only send,KILLandSTOPamong them, and every call that takes a set answersiofor an empty one or for a bit that is not a listenable meaning. A published meaning keeps its bit in every later release ([os.signal.set.stable]). A program reads the meanings ignored now, at start the ones it was started ignoring ([os.signal.ignored]), signals a process by its id ([os.signal.send]), asks the host's number for a meaning ([os.signal.number]) and names its own id and its parent's ([os.proc.self]), and a poll lets pending signals be delivered before it answers, so it never trails one ([os.signal.poll]). Each of the new calls is hosted: the freestanding target refuses it by name. Anchors[os.*].
Extracted artifacts
- grammar.ebnf (11 KiB)
- The grammar, extracted from document 01 by a command and checked in CI to be in sync with the prose. The book's grammar appendix and the editor highlighting both come from this file, so there is one lineage rather than three transcriptions.
- anchors.json (26 KiB)
- The clause-anchor index: every stable anchor the documents define. Both implementations validate their citations against it.
- README.md
- The directory's own note on which sprint authored what.
Reading it against a program
The conformance corpus is the specification's working end. Each program states its own expected outcome in a header directive and lists the clauses it conforms to, and CI in the compiler repository checks the claim on both implementations. The playground loads thirty-odd of those programs with their headers intact, which is the shortest path from a clause to a program that exercises it.