Language reference: a beginner's book that quotes the sources - #69
Open
Russoul wants to merge 6 commits into
Open
Language reference: a beginner's book that quotes the sources#69Russoul wants to merge 6 commits into
Russoul wants to merge 6 commits into
Conversation
Russoul
force-pushed
the
language-reference
branch
from
August 27, 2026 19:40
a32e556 to
472c80c
Compare
A third rendered page alongside the specs and the sources: a tutorial and reference for the SURFACE language, 36 chapters across seven parts, four written and the rest outlines (badged as such, counted in the page's progress line). tools/render-reference.py renders docs/reference/*.md — a small fixed Markdown subset — to one self-contained page, reusing render-specs.py's palette and the LSP's own token classes so a snippet reads exactly like the same line on the sources page. Chapter ORDER lives in PARTS, files are named by slug, and cross-references are anchors, so inserting a chapter renumbers nothing and can rot no link. The book QUOTES the corpus rather than copying it, and --check enforces that: every ```nova block must occur verbatim in src/nova or a golden's input, every ```report block in a golden's expected output, every cited path must exist, and every rule-shaped citation must name a rule the specs still define. A golden test (tests/nova/docs) and a CI step run it, so the book fails the suite when the language moves under it.
The audience changes from "you have used a proof assistant" to "you have written functional programs and read mathematics, and have met neither dependent types nor a proof assistant". The spine is rebuilt around that reader: 45 chapters in nine parts, teaching the language before the theory, with the Agda comparison moved out of the opening and into its own late, optional part (a map, reflection-vs-J, and the coherence chapter that was always an Agda-facing piece). Five chapters are written for the new reader: Introduction, Proofs and programs (propositions-as-types from scratch), Installing and running, Your first file, and Reading and typing Nova. Equality still carries the old pitch and is badged %revise — a new status marker beside %stub, so the page reports what is finished, what is planned, and what is stale. Chapter 4 walks a file that fails twice before it is accepted, because the obligation loop is the thing no summary conveys. Four goldens under tests/nova/docs pin its file and its three transcripts, so a change to the checker's output breaks the chapter rather than stranding a reader mid-walkthrough. Chapter 5 is built from a census of the corpus: Nova code uses exactly 32 non-ASCII characters, twenty of them the language, and a --check pass now verifies that every one of them appears in the chapter.
main moved the gates and the Pages artifact into the flake, which silently dropped the reference from both: nix/packages.nix's site rendered specs and sources but not reference.html, so the landing page's first card would have 404'd on the next deploy. The render is back in `site`, and `--check` is a flake check of its own — it needs no Idris build, so it reports in seconds, alongside spec-rules. Chapter 3 now opens with two routes instead of one. Nix leads: `nix run . -- elab file.nova` cannot fail halfway through bootstrapping a compiler, which is the failure mode a newcomer is least equipped to diagnose. pack follows, for readers already developing in Idris. The book's commands become toolchain-neutral `nova elab file.nova`, with one callout giving the four spellings, and `nix flake check` joins ./test.sh as the way to check your work.
Definitions, let, operators and modules — the mechanical chapters a
reader needs before Part III can teach anything. Each claim was run
against the checker rather than taken from the spec, which is where
the content came from:
* a `type` abbreviation is opaque like any other definition, and the
error names the remedy ("head exposure blocked for Pair — cite
Pair.unfold");
* an operator with no fixity used infix is not a parse error, it is
three atoms juxtaposed and a baffling type error;
* operators overload across modules and resolve by type, provided the
overloads agree on fixity — the loader says so if they do not;
* imports are qualified by default and NOT transitive for visibility,
while the lemma store does travel the closure;
* import cycles are refused by the loader before any checking.
The precedence table is the corpus's own, and a scripted cross-check
confirms every fixity the corpus declares appears in it.
The built-in types, pairs, sums, defining your own types, recursion and defining equations. Part III is where a functional programmer meets the two things that do not transfer, so both are given a chapter of their own rather than a warning in passing: `𝟘`/`𝟙` earn their place by computing types (isZero), and recursion opens with the fact that a definition's name is not in scope in its own body. Findings from running everything, which shaped the chapters: * `⨯` and `→` share a precedence level and associate right, so `ℕ ⨯ 𝟙 → 𝟙 ⨯ ℕ` is a PAIR type and a λ against it fails oddly. It is now a named trap in the pairs chapter. * η for pairs and functions is licensed, not automatic: without `sigma.eta` / `pi.eta` you get an obligation reading `p .π₁, p .π₂ ≐ p`, which is baffling until you know it wants a licence rather than a proof. * Clauses outside the structural fragment are not rejected — the macro declares the CONTRACT instead (the function, each clause as an equation, and uniqueness) and you owe the three. That behaviour is the best explanation of what the clause form is for, so the chapter is built around it. Four new fixtures under tests/nova/docs pin what the corpus cannot supply: a library has no standalone demonstration of `()` or of a first user-defined List. 166/166 tests.
… missing Thirty commits landed upstream, several of which contradicted the book. The quotation gate caught the mechanical drift (eight transcripts, two snippets); reading the commits caught the rest: * ⨯ (U+2A2F) became × (U+00D7) and ⨯ is now unrecognised, so chapter 5's look-alike table had the advice exactly backwards. * × split in two and moved below ⊎, which DELETED the precedence trap chapter 11 was built around: `A × B → C` now reads the way everyone expects. Replaced with the real table, and the binder form's maximal body, which is the remaining surprise. * Every non-ASCII token gained an ASCII spelling, so chapter 5 no longer opens by telling a newcomer they must type Unicode. * Holes came back. `?x` is inert, reported with its telescope, and hoverable — chapter 4 said flatly that no such syntax existed. * VS Code and neovim clients ship from the flake; chapter 3 said there were none. * Mixed associativity at equal precedence is now a parse error. * `at:` lines carry file:line:col, and parse errors render a caret. Outline audit against the grammar, the CLI and the spec's sections found three user-visible features with no home, all from this batch: refinement, item recovery, and in-place elimination — `nova eliminate` case-splits a hole and prints the file back, which is the other half of hole-driven development. Holes are now their own chapter beside a narrowed Reading the report, and the tooling outline lists every command and gate script. 46 chapters. One self-inflicted bug fixed: regenerating every fixture at once had overwritten the snippets golden while the check was red, baking its own failure in as `expected`. Its run script now says why that file is written by hand.
Russoul
force-pushed
the
language-reference
branch
from
September 1, 2026 12:43
472c80c to
0990725
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A third rendered page alongside the theory specs and the syntax-highlighted sources: a tutorial and reference for the surface language. It assumes a functional programmer who reads mathematics, and assumes nothing about dependent types or proof assistants. Unlike
docs/*.txtit is not normative — it teaches the language and links into the specs.46 chapters in nine parts; 18 written, 28 outlines. The page reports that itself, in a progress line, and badges every unfinished chapter — so it is honest about its own state rather than looking finished and being hollow.
What is here
docs/reference/*.md— the book. Parts I–III are complete (Orientation, Writing Nova, Data and recursion), plus The discharge engine and Types without coherence from later parts.tools/render-reference.py— renders it to one self-contained page, reusingrender-specs.py's palette and the LSP's own token classes, so a snippet reads exactly like the same line on the sources page.tests/nova/docs/— nine goldens: the quotation check, and eight fixtures pinning the worked examples.nix/packages.nixrendersreference.htmlinto.#site, and--checkis a flake check of its own.Structure
let, operators, modulesusing, recipes, pitfallsThree decisions worth a reviewer's attention. Recursion is introduced as a surprise — "there is no general recursion, and the name of a definition is not in scope in its own body" is the first thing that ambushes a functional programmer, so eliminators arrive as the answer rather than as a primitive. The Agda comparison is late and optional, because a reader who has never used a proof assistant has no comparison point; chapter 1 points those who do straight at Part VII. And chapter 4 fails twice on purpose before the file is accepted, because the obligation loop is what no summary conveys — one failure carries a
hint:(a licence is missing), the other does not (the mathematics needs an induction), which hands a beginner a triage rule from their first obligation.The book quotes the sources; the suite enforces it
The reference does not copy the corpus, it quotes it, and
render-reference.py --checkverifies every quotation:```novasrc/nova/*.novaor a golden'sinput.nova```reportexpecteddocs/*.txt,src/nova/*.nova,tools/*pathsel-reflect,prop-lift, …)render-specs.pybuilds from the specsMatching is by blank-line-separated chunk: a snippet may elide whole items, but each chunk must appear as a contiguous, dedented, verbatim run of lines. Golden inputs count as a source because a deliberately failing example cannot live in the accepted corpus.
```nova-sketchis the escape hatch for genuinely illustrative code — grammar skeletons and error cases — highlighted, exempt, and visible as an exemption in the source. Currently 98 quoted snippets, 17 illustrative.Chapter 5 is built from a census of the corpus — Nova code uses exactly 32 non-ASCII characters, twenty of them the language — and a check verifies that every one still appears in the chapter.
It has already caught real drift, twice
The
Prfretirement. The gate flagged two drifted snippets; reading the commits turned up four further stale claims in prose that no gate could catch, including adatasignature using a retired spelling and advice to writeEl ℕfor external domains that had become exactly backwards.The last thirty commits. The gate caught eight transcripts and two snippets. Reading the commits caught the rest:
⨯became×and⨯is now unrecognised, so chapter 5's look-alike table had the advice inverted;×moved below⊎, which deleted the precedence trap chapter 11 was built around; ASCII fallbacks mean chapter 5 no longer opens by telling a newcomer they must type Unicode;?xholes returned, contradicting chapter 4 outright; and VS Code and neovim clients now ship from the flake.That is the argument for the gate: mechanical drift is caught automatically, and the prose it cannot check is at least localised for a human to re-read.
Outline audit
Checked every production of the surface grammar, every CLI command and every section of
NovaElaboration.txtagainst the chapter set. Three user-visible features had no home, all from the recent batch:nova eliminate <file> <line>:<col> <var>fills a hole by eliminating a variable of its context and prints the file back, turningλn. ?goalintoλn. (ℕ-elim ?goalZ (n ih. ?goalS) n).?p : x ≡ y ∈ Arather than as elaborator scaffolding. Holes you wrote are never guessed at.Part VI now has Reading the report and a new Holes and in-place elimination; the tooling outline lists every command and gate script.
Other things worth knowing
PARTSin the script and files are named by slug, so inserting a chapter renumbers nothing.expectedis a hand-written success line; itsrunscript says why, after regenerating it from a red run once baked a failure in.%revisebadge rather than pretending otherwise.195/195 tests, six flake gates.