Ce document de référence est rédigé en anglais, la langue de travail du dépôt. Les pages du site — le tour, les guides « je viens de… » et la bibliothèque — existent intégralement dans les deux langues.
Keal holds itself to a small set of non-negotiable rules; a change that respects them is welcome from anyone. This file is the whole procedure.
cargo build --release # the Rust toolchain (the oracle)
cargo test --release # the whole suite, native tests included
./bootstrap.sh # the self-hosted compiler, to its fixed point
A JDK and a C compiler unlock the interop tests; without them those tests skip, they do not fail. keal doctor lists what this machine has, next to the versions the interop suite was last verified against — Keal pins versions, it does not vendor toolchains: compilers are hundreds of platform-specific megabytes that every OS packages better than a language repository can, and a differing version is not an error, just a fact cargo test --release settles.
src/lexer.rs, parser.rs, checker.rs, cbackend.rs, and constfold.rs / macros.rs for the two passes that rewrite the tree) and in the self-hosted twin (selfhost/lexing.keal, parsing.keal, checking.keal, cbackend.keal, constfold.keal, macros.keal), and the four dump commands must agree byte-for-byte over the whole corpus: ``sh keal tokens f.keal ↔ keal selfhost/lexer.keal f.keal keal ast f.keal ↔ keal selfhost/parser.keal f.keal keal types f.keal ↔ keal selfhost/checker.keal f.keal keal cgen f.keal ↔ keal selfhost/cbackend.keal f.keal ``
The suite runs this comparison over every .keal file in the repository — programs, examples, and the error corpora, where the diagnostics must match too, exit codes included.
leaks --atExit on the native binary says zero.src/prelude.keal or src/runtime.c, rebuild (cargo build --release) and regenerate selfhost/preludesrc.keal / selfhost/runtimesrc.keal (each wraps the file in a raw-string function; the header comment in each says so).selfhost/loader.keal). A node added without a line there defaults to file 0, which is the prelude — so a diagnostic blames <prelude>, and a type written in that node resolves against a file that declares none of the program's classes. This has been shipped twice: once on a catch clause's handler, once on a macro declaration. When you add a field that holds a node, add the line that stamps it.tests/programs/ — self-checking programs (assert, silent, exit 0), run on both interpreters. tests/native/ — programs with printed output and an .expected snapshot, run on all three engines. tests/selfhost/parse-errors|type-errors|errors/ — programs that must be refused, named for what they prove. tests/fuzz/fuzz.py <keal> <count> [seed] — the differential fuzzer; run a few thousand programs when you touch the checker. * UPDATE_EXPECT=1 cargo test --release rewrites snapshots. Removing a refusal is new behavior, and the easiest kind to ship untested — nothing was added, so nothing looks like it needs proving. But a refusal is load-bearing while it stands: it was keeping programs out, and some of those programs were being kept out of trouble. Letting a lambda capture this was right and is not in question; it also made the reference cycle in tests/audit/closure-cycle.keal writable in one line, in the exact shape the feature exists for. Nobody could have written that cycle the day before.
So: a refusal removed is a burden of proof moved, not lifted. Ask what the refusal was preventing besides the thing you wanted, and write the test for that before the one for the feature.
open() and every subprocess call in site/*.py names encoding="utf-8", and every write names newline="". Python takes both from the machine's locale otherwise, and a machine whose codepage is cp1252 will mis-decode the compiler's own output into a page and exit 0 — the failure that does not fail. The same rule is why a diagnostic never embeds std::io::Error's text: it is the operating system's sentence, in the operating system's language.日本 listed 日本 back, while what sat on disk was 日本 and no other tool on the machine could open it. Every test written in Keal agreed with the bug. So: where the same code is on both ends, put something else on one of them. tests/programs/filesystem.keal has a shell create the directory and asks listDir what it sees — runCommand is what made an outside witness reachable from inside the corpus. kealdoc_matches_snapshot compares bytes and then READS the page back, checking its tags balance and that a doc comment's < arrived escaped; breaking the generator and updating the snapshot to match is caught by the second half and not the first. Most snapshots here are already witnessed without anyone planning it — the diagnostics are compared against the self-hosted compiler too, the bindings are compiled and run, the layouts are used by native programs that would crash on a wrong one. Look for the ones that are not. The other half of the same rule: a test must assert the answer, not the fact of an answer. tests/audit/closure-cycle.keal exists to tell two objects apart — one whose closure captured this and one whose closure captured a value — and the harness checked only that the audit had spoken. It would have stayed green if the audit stopped finding cycles altogether. It now names which of the two is one.
Watch for this hardest where a program cannot check itself. The audit reports after the last statement, so no assertion inside the program can ever read it: by the time the answer exists there is no program left to do anything with it. A version of such a file ending in assert(true, "no leaks") looks like a test, passes forever, and verifies nothing. Something outside has to read what it printed.
-- note: with the fix). Comments state constraints, not narration. Costs and limits go in the docs, not under the rug: see docs/types.md (the type rules), docs/memory.md (the memory model), docs/drop.md (unwinding and deinit), docs/threads.md (the actor plan), docs/interop.md (the boundary).src/ the Rust oracle: lexer, parser, checker, interp, VM,
cbackend, runtime.c, prelude.keal, tools (doc, bindgen,
jbind)
selfhost/ the same compiler, written in Keal, held byte-identical
lib/ libraries beyond the prelude (the JVM gateway)
tests/ the corpora described above
examples/ runnable programs, including the interop demos
docs/ the design decisions, honestly stated
Fork, branch, keep the commit story readable (one concern per commit), and make sure the three commands at the top are green before opening a pull request. A PR that changes semantics should quote the rule in docs/ it follows — or start by proposing the doc change, which is often the real discussion.