NEXT.md prepends, so its second section still opened with "Read this first" while
pinning a tree six lanes behind and listing as live a queue most of which has
landed. It keeps its contents; what it gains is the sentence saying when it
stopped being where the tree is, and the survey count it quotes measured again on
this tree -- 103 MATCH, 0 DIFFER, 38 skipped.
The two generics entries get the file's own strikethrough treatment, which their
siblings already had: the {K V} catch resolved exactly as it predicted it would,
and there are five predicates rather than four.
Also here: conditions.org gains ArithError beside BoundsError, with the split
between the two runtime conditions that offer retry and the two that deliberately
offer nothing; and tast.ml's header stops naming a tree-walking interpreter among
the backends that consume the typed IR.
A consistency sweep, run by checking claims against the compiler rather
than reading for style. Every edit here corrects something that is false
now, or adds something the page had no way to say.
`web/index.html` was the worst of it, and the worst of that was control
flow: the page said there is no `loop`/`recur` and no `break` or
`continue`, and printed the refusal message for `break` as evidence. All
four are built, with loop labels. A live code example called
`index-of-i32`, which no longer exists and would not compile. The prelude
table was the pre-generics per-type families, a paragraph said there is
no `println` two paragraphs after calling `println` the compiler's, and
`sqrt-f32` was "the one `declare` in the file" when there are five. The
"Not implemented yet" table listed `Vec`, `Map`, `Handle`, `Fn`, `fn`,
unions, `defmacro` and quasiquote, all of which check; what is actually
left is `Result`, `try`, a quoted symbol as a value, `errdefer`, `await`,
`handler-case` and the restart-stack readers. Restarts take parameters
(`(invoke-restart 'use-value 21)` answers 42), `defer` in a `let` is
allowed, and there is both an allocator and a `context`.
Generics is a new section, because nobody had documented the syntax. The
brief for it was wrong in one place and the corpus settled it: `$t` goes
in *every* type position including the return type, and bare `t` is the
type-name argument in expression position — `(vec-new t)`, `(t x)`. It
says what a type variable is move-only by default means, since that is
the rule a reader hits first and it is not Odin's.
`FLAN_RAYLIB_H` is gone from every doc that claimed it still decided
something. The passages that say "this used to be opt-in" are kept and
labelled; the ones that said "this is opt-in" are not. `plan.org` had
`{string i32}` in the type list and four predicates where there are five.
`conditions.org` described `errdefer`'s behaviour without saying it is
refused by name. `REFERENCES.md` pointed at the gitignored copy of the
raylib header rather than the committed one, which is the exact trap that
made committing it necessary.
Found and not fixed, because it is not documentation: `vendor/raylib/headers`
still says a build reads it "when the variable happens to be set", which
contradicts the section below it in the same file and is false — moving
the header makes every build fail by name.
If a frame mutates a global and then signals, taking a retry re-runs the
mutation. Control resumes at the restart-case and runs forward; nothing is
undone. Common Lisp has the same property and offers no help either, so this
is written down rather than fixed.
The discipline is that the author chooses where the retry boundary is: a
restart-case above the mutations re-runs them, one below re-runs only what
follows. Put the restart before anything mutates, make the retried section
idempotent, or snapshot what will be re-applied.
It matters more here than in most Lisps because the intended use is a game
loop, and a bad index signalling BoundsError rather than ending the process
made abandoning and retrying a frame an ordinary thing to do.
conditions.org and web/index.html already carried the mechanical half as a
one-line gotcha; those are rewritten in place rather than gaining a second
bullet beside them. spec-conditions.md takes it in section 5, which already
enumerates what a transfer does and does not do. No numbered case changed
meaning.
The slot after a defn's parameters is unconditionally a type. Parse.decl no
longer takes a set of type names, and is_type_form, qualified_type, types_in,
declared_types and prelude_types are gone with the pre-pass that fed them.
What they were for: (Option f64) and (Some 1) are the same s-expression, so the
parser decided which it had by looking the head up in a set of the file's own
type names. Sound -- one top-level namespace means a name cannot be both a type
and a value -- and brittle, because the set had to be complete. It was wrong
twice in one day, the second time parsing (defn f [] (Rune {.code 65}) (bar))
as a function returning a Rune with a one-form body, silently, in every file in
the language.
Two things fall out. A type the parser could not have known -- a struct
declared further down the file, rl/Vector2 behind an unresolved alias, a
prelude type -- never needed recognising, only placing. And a mistyped type is
a mistyped type: (defn f [] f65 0.0) reaches the resolver's near-miss check and
says did you mean f64, where it used to be read as the first form of the body
and reported as an unknown name.
Unit is written (). The old spelling is refused with a message naming the new
one, the rule the colon-to-dot change followed. Internally it is still
Tname "Unit" and Types.Unit, so the resolver, the shim and the emitter did not
change; Cimport still builds Tname "Unit" for C's void without going through
the parser. Types.to_string prints () though -- that printer prints what a
person would write for every other type it knows, [i32], {K V}, (Ptr T), and
Unit was the odd one out once the source spelling moved.
Dropping prelude_types removes one of the two reasons Macro.reduce may only
drop defns: the memoised set a bootstrap build could have poisoned is gone, so
the remaining reason is the plain one.
§4 meets §3, and the answer a reader will assume is the other one. An
inner (use-value [s string] ...) shadows an outer (use-value [v i32] ...),
so an i32 is refused there and the outer clause that would have taken it
is never consulted. Searching outward for a frame whose signature fits
would make which restart runs depend on the arguments, which is overload
resolution on a dynamic stack.
Also: neither of the new guards is a bounds check, so --no-bounds-checks
does not remove them. A wrong index is a wrong answer; a transfer into a
clause whose parameters were written to a different layout is not.
The language half of §3's parameters is in; the half that makes it worth
having is not. A break loop chooses by position and has nothing to fill a
clause's parameters in with, and that is now the top item in NEXT.md,
spelled out end to end — the accessors the frame can already answer, the
signature on the wire, and the one store that has to happen before the
channel is aimed.
§3 asks for a clause's report string to be settled before parameters and
it was not. The field is cheap and so is the accessor; the only thing
that would read either is the break loop's listing, which lives in the
agent and the daemon, so it would have shipped as a field nothing read.
It belongs with the editor half, which is changing that listing anyway.
spec-conditions.md §4 gains the rule the shadowing bug was hiding: a handler
matches by name, a debugger identifies by position, and the two are not the
same question. With it, the snapshot — a position means nothing against a
stack that moves — and the fact that a visible restart may still be
unreachable, which §6's explicit lowering makes possible.
§3 records the open one: a clause should carry a report string.
is what invoke-restart needs and not what a person reading a list needs. It
wants settling before restarts with parameters, which is where a bare name is
least sufficient.
conditions.org had the break loop under "Not yet", which it has not been for
some time, and now says why find-restart and compute-restarts still are: they
are blocked on a Restart type and a list to return one in, not on effort.
The cheatsheet was a document. It should fit on a screen: the syntax, what is
missing, the traps that are not in any error message, and the three ways it
stops. What was cut is either in spec-conditions.md or in the compiler's own
refusal, and both say it better.
conditions.org is how to drive what is built, as against spec-conditions.md
which is what it should mean and NEXT.md which is why it is shaped that way.
Every refusal message in it is verbatim rather than paraphrased, because the
reason is the thing worth knowing and a remembered approximation of it is how a
cheatsheet starts lying.
conditions-play.flan is the program to poke at. It loops rather than exiting so
flan dev can attach to it, and it is deliberately two frames deep with a defer
in the middle, so that redefining probe or fetch from Emacs and watching the
next pass through run-once shows the transfer crossing something.
It also carries the two gotchas that are not in any spec or message: a handler
closes over nothing, and invoking a restart re-runs whatever sits between it
and the target - which is what "restarts go at the resync point" is actually
about.