Merge branch 'master' into worktree-agent-acca0e216df6ca366
This commit is contained in:
commit
22401a6521
143
CLAUDE.md
143
CLAUDE.md
@ -1,125 +1,54 @@
|
|||||||
# Working in this repository
|
# Working in this repository
|
||||||
|
|
||||||
For any agent working here, including a lane in its own worktree. These are
|
Standing rules for any agent here, including a lane in its own worktree.
|
||||||
standing rules, not preferences. Where a rule has a reason, the reason is given
|
|
||||||
once — the rules without one are the ones that have already cost something.
|
|
||||||
|
|
||||||
## Never
|
## Never
|
||||||
|
|
||||||
- **Never touch anything under `/home/joe/Development/fnm/`.** That is the
|
- **Touch anything under `/home/joe/Development/fnm/`.** The author's live
|
||||||
author's own working copy of the falling-sand game, open in an editor with a
|
working copy, with an editor session attached. This repository's `sand.flan`
|
||||||
live session attached. `fnm/flan/sand.flan` is not the same file as this
|
is a different file and may be edited normally.
|
||||||
repository's `sand.flan` and is never to be read-and-written-back, moved or
|
- **Execute `examples/*.flan`, `sand.flan`, or anything linking raylib.** They
|
||||||
edited. The copy in this repository is ordinary tracked source and may be
|
open a window that strobes the author's desktop. Compile, emit, diff — never run.
|
||||||
edited like any other file.
|
- **Run `dune clean`.** It escapes a worktree and deletes the main checkout's
|
||||||
- **Never execute `examples/*.flan`, `sand.flan`, or anything that links
|
`_build`. Run `dune` with `--root .` from inside your own worktree.
|
||||||
raylib.** They open a window on the author's desktop, which strobes it.
|
- **Kill a `flan dev` daemon you did not start.** The author keeps one attached
|
||||||
Compile them, emit them, diff them — never run them.
|
to their editor.
|
||||||
- **Never run `dune clean`.** It walks up out of a worktree and deletes the main
|
- **Put `Co-Authored-By`, a `Claude-Session` trailer or any watermark in a commit.**
|
||||||
checkout's `_build`, which takes the author's `flan` binary with it. This has
|
|
||||||
happened. Run `dune` with `--root .` from inside your own worktree.
|
|
||||||
- **Never kill a `flan dev` daemon you did not start.** The author keeps a live
|
|
||||||
one attached to their editor all day. Orphans from finished lanes are fair
|
|
||||||
game; anything whose cwd is under `fnm/` is not.
|
|
||||||
- **Never put a `Co-Authored-By`, a `Claude-Session` trailer, or any other
|
|
||||||
watermark in a commit message.**
|
|
||||||
|
|
||||||
## Commits and branches
|
## Commits
|
||||||
|
|
||||||
`master` is the trunk and tracks `origin/master`. Lanes work in their own git
|
`master` is the trunk. Lanes work in their own worktree and never merge or push;
|
||||||
worktree off it and do not merge or push — merges are resolved by the session
|
the dispatching session merges. A commit message is one declarative sentence
|
||||||
that dispatched the lane.
|
saying what is now true, not what was done.
|
||||||
|
|
||||||
A commit message is a single declarative sentence in the repository's voice,
|
|
||||||
saying what is now true rather than what was done: "A thunk named for the order
|
|
||||||
it was minted in is a different signature after a reorder", not "fix thunk
|
|
||||||
naming bug". A body is for the reasoning, when there is any.
|
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
`dune test --root .` is the suite and takes seconds. Run it constantly; it must
|
`dune test --root .` must be green before a lane reports; grep its output for
|
||||||
be green before a lane reports.
|
FAIL, since the exit code alone has lied. `@checks` (`@page`, `@x86`, `@cells`),
|
||||||
|
`@sanitize` and `@valgrind` are slow and run once between batches of lanes, with
|
||||||
`dune build @checks` is `@page`, `@x86` and `@cells` — the reference page's
|
the author's permission, never inside a lane. ASan misses uninitialised stack
|
||||||
examples still compile and print what the page says, the hand-written x86
|
reads; `@valgrind` catches them.
|
||||||
backend still agrees with LLVM, and a `--dev` build still calls through its
|
|
||||||
indirection cells. `@sanitize` and `@valgrind` run the corpus under ASan/UBSan
|
|
||||||
and memcheck and take tens of minutes.
|
|
||||||
|
|
||||||
Those three stay out of a lane's own run. They are swept in a batch after
|
|
||||||
several lanes have merged, and the fixes are batched with them. Keeping the
|
|
||||||
default run fast is deliberate: a suite that takes tens of minutes is a suite
|
|
||||||
nobody runs.
|
|
||||||
|
|
||||||
Note that ASan does not see a stack-lifetime bug — uninitialised stack reads
|
|
||||||
need `@valgrind`.
|
|
||||||
|
|
||||||
## Evidence
|
## Evidence
|
||||||
|
|
||||||
Read the source, do not recall it. `docs/REFERENCES.md` lists the reference
|
Read the source, do not recall it — this repository and the reference clones
|
||||||
clones on this machine — Odin, SBCL, Zig, Carp, clojure-mode, CIDER, raylib and
|
listed in `docs/REFERENCES.md`. Verify a brief's facts before building on them.
|
||||||
the rest — and what each one answers. A claim in these notes that came from one
|
|
||||||
of them was read out of the clone; the ones that were recalled instead have been
|
|
||||||
wrong before.
|
|
||||||
|
|
||||||
The same applies to this repository. Cite a file and a line because you opened
|
## Records
|
||||||
it. When a brief hands you a list of facts, verify them before building on them
|
|
||||||
— a brief written from a stale tree has sent a lane a full day in the wrong
|
|
||||||
direction.
|
|
||||||
|
|
||||||
## Diagnostics
|
Keep only what the code cannot say.
|
||||||
|
|
||||||
Elm's shape: the source line, a caret, what the compiler understood, and the fix
|
- `TODO.org` — one `**` heading per item under a subsystem: `TODO` a gap, `NEXT`
|
||||||
named. Beyond that:
|
decided and queued, `WAIT` blocked (say on what), `CANCELLED` rejected with a
|
||||||
|
one-line reason so it is not re-proposed, `DONE` only when the decision is not
|
||||||
|
obvious from the code (two lines, what it rules out). On a merge conflict keep
|
||||||
|
both sides.
|
||||||
|
- The reason for non-obvious code goes in a comment beside it.
|
||||||
|
- `docs/BUILT.md` holds only reasoning that spans several files.
|
||||||
|
- No handoff files.
|
||||||
|
|
||||||
- A message is written for someone who has never used Flan and does not know its
|
## Reviews
|
||||||
history. Never "X is now Y", never "X was renamed", never our rationale. If
|
|
||||||
`defvar` no longer exists, the message says `defvar` does not exist — not that
|
|
||||||
it became something else.
|
|
||||||
- The clauses come in one order: what the compiler understood, then what
|
|
||||||
conflicts with it, then the fix — "expected i32, found string".
|
|
||||||
- Every suggestion a message prints must compile.
|
|
||||||
- An assertion about the compiler's own invariants is prefixed `internal:` and
|
|
||||||
says it is a compiler bug. A user never caused one.
|
|
||||||
|
|
||||||
## Writing
|
Every lane gets an independent adversarial review before it merges: build and
|
||||||
|
run rather than read, measure both sides of a claimed fix, and reproduce any
|
||||||
User-facing prose — the README, `web/index.html`, the Emacs manual — is plain.
|
number a lane reports.
|
||||||
Odin's website is the reference: declarative rather than second-person, the
|
|
||||||
concept defined before the mechanics, the code example after the prose that
|
|
||||||
frames it, and rationale given its own subsection rather than mixed in.
|
|
||||||
|
|
||||||
No aphorisms, no closing lines built for effect, no rhetorical inversions. If a
|
|
||||||
sentence's only content is its own style, cut it. This applies to reports and
|
|
||||||
commit messages as much as to documentation.
|
|
||||||
|
|
||||||
## The records
|
|
||||||
|
|
||||||
- `TODO.org` — every decision, question and known gap, one `**` heading each
|
|
||||||
under a subsystem heading, with an org keyword saying where it stands: `TODO`
|
|
||||||
for a gap, `NEXT` for what is queued, `WAIT` for what is blocked and on what,
|
|
||||||
`DONE` for what was settled, `CANCELLED` for an idea considered and rejected.
|
|
||||||
A lane records its decision here, as a few lines saying what was decided and
|
|
||||||
what it rules out — never the argument and never the measurements. On a merge
|
|
||||||
conflict in this file, keep both sides: two lanes recording two decisions is
|
|
||||||
not a conflict.
|
|
||||||
- `docs/BUILT.md` — why the parts that exist are shaped the way they are. The
|
|
||||||
largest and most load-bearing document here. Reasoning that will not fit in a
|
|
||||||
few lines goes here, and `TODO.org` carries one line pointing at it.
|
|
||||||
- `docs/handoffs/` — one report per finished work session.
|
|
||||||
- `docs/README.md` indexes all of it.
|
|
||||||
|
|
||||||
A `CANCELLED` entry is not housekeeping. An idea rejected without a record is an
|
|
||||||
idea that gets re-proposed, so the one-line reason is the whole value of it.
|
|
||||||
|
|
||||||
## The shape of the work
|
|
||||||
|
|
||||||
The author dogfoods the language in a separate project, hits friction, and
|
|
||||||
reports it. Work is dispatched as parallel lanes in isolated worktrees; every
|
|
||||||
lane is reviewed by an independent agent before it merges; the dispatching
|
|
||||||
session resolves the merges.
|
|
||||||
|
|
||||||
A review is adversarial. Verify by building and running rather than by reading,
|
|
||||||
measure both sides of a claimed fix, and say what you actually measured. A lane
|
|
||||||
that reports a number you did not reproduce is a lane with a number you should
|
|
||||||
reproduce.
|
|
||||||
|
|||||||
91
TODO.org
91
TODO.org
@ -501,7 +501,8 @@ capturing value. Two things for it to know: a widening thunk's environment holds
|
|||||||
code pointer rather than a GC object, and capturing a dyn stays refused until a
|
code pointer rather than a GC object, and capturing a dyn stays refused until a
|
||||||
synthesised environment has a descriptor.
|
synthesised environment has a descriptor.
|
||||||
|
|
||||||
** TODO CFn and C's calling convention
|
** WAIT CFn and C's calling convention
|
||||||
|
Decided 2026-09-25: waits with C callbacks, until a program needs one.
|
||||||
A Flan function's signature ends with the transfer channel and a C caller knows
|
A Flan function's signature ends with the transfer channel and a C caller knows
|
||||||
nothing about one, so a =CFn= is not a C callback today. Under a future
|
nothing about one, so a =CFn= is not a C callback today. Under a future
|
||||||
=--no-conditions= flag a =CFn= signature could drop the channel and reach C's
|
=--no-conditions= flag a =CFn= signature could drop the channel and reach C's
|
||||||
@ -524,7 +525,8 @@ dispatch values. With single dispatch on literal values there is no specificity
|
|||||||
question, and inheritance or multiple dispatch would create one. Unknown-slot
|
question, and inheritance or multiple dispatch would create one. Unknown-slot
|
||||||
checking needs class-typed tracking the dyn side deliberately does not have.
|
checking needs class-typed tracking the dyn side deliberately does not have.
|
||||||
|
|
||||||
** TODO update-instance-for-redefined-class, the user hook
|
** NEXT update-instance-for-redefined-class, the user hook
|
||||||
|
Decided 2026-09-25: build it after typed class slots land, shaped for the REPL — written and installed from a live session as a one-time "here is how to migrate this", without restarting. It receives the instance with the added and discarded slots and their old values, and runs at each instance's lazy migration. A hook that signals parks in the break buffer with a restart that falls back to name-matching migration.
|
||||||
Left out of v1 because name matching is the half that makes redefinition usable
|
Left out of v1 because name matching is the half that makes redefinition usable
|
||||||
and the hook is what makes it expressive. The obvious spelling is a generic riding
|
and the hook is what makes it expressive. The obvious spelling is a generic riding
|
||||||
the dispatch that exists, and the migration already computes both the added and
|
the dispatch that exists, and the migration already computes both the added and
|
||||||
@ -736,10 +738,12 @@ call site that asked for that type. The data is there — =instantiation_origin=
|
|||||||
exists and the session already uses it — and wiring it into every failure under an
|
exists and the session already uses it — and wiring it into every failure under an
|
||||||
instantiation is a lane of its own.
|
instantiation is a lane of its own.
|
||||||
|
|
||||||
** TODO Generics across a real compilation-unit boundary
|
** DONE A program is one compilation, so a generic's body is always visible
|
||||||
It works today because =Load= flattens imports before checking. A package boundary
|
CLOSED: [2026-09-25]
|
||||||
that ever becomes a real unit boundary needs the generic's body to cross it, which
|
Odin's and Zig's model: packages are never compiled separately. The cost is build
|
||||||
separate compilation cannot do — which is why C++ puts templates in headers.
|
time proportional to the whole program and no binary-only packages. If separate
|
||||||
|
compilation is ever wanted, Rust's answer is the one to take — a compiled package
|
||||||
|
carries its generics' checked bodies and the user instantiates them.
|
||||||
|
|
||||||
** DONE Collapsing the prelude buys 27 to 15, not 27 to 6
|
** DONE Collapsing the prelude buys 27 to 15, not 27 to 6
|
||||||
CLOSED: [2026-09-13]
|
CLOSED: [2026-09-13]
|
||||||
@ -824,7 +828,8 @@ the losing side meets the strict =bool= boundary and traps —
|
|||||||
=(or false (box "s"))= is the case. Whether a =bool= arm and a =dyn= arm should
|
=(or false (box "s"))= is the case. Whether a =bool= arm and a =dyn= arm should
|
||||||
join as =dyn= is the author's call and is not settled.
|
join as =dyn= is the author's call and is not settled.
|
||||||
|
|
||||||
** TODO A truthiness failure re-runs the whole failing subtree
|
** NEXT A truthiness failure re-runs the whole failing subtree
|
||||||
|
Decided 2026-09-25: fix it without changing any message — the retry reuses what the first pass settled for each subtree (memoised by node), so nested =not= is linear. Test with a deep nest that must fail fast and with the existing message tests unchanged.
|
||||||
The retry exists to keep a refused literal's message unchanged and re-runs the
|
The retry exists to keep a refused literal's message unchanged and re-runs the
|
||||||
subtree rather than the leaf, which is exponential in nested =not= depth on a
|
subtree rather than the leaf, which is exponential in nested =not= depth on a
|
||||||
program that does not type-check. Moot for anything that compiles; only the
|
program that does not type-check. Moot for anything that compiles; only the
|
||||||
@ -864,7 +869,8 @@ correct code, so any per-push flag is a false positive by the language's own
|
|||||||
semantics; the refined version needs liveness across control flow, which is the
|
semantics; the refined version needs liveness across control flow, which is the
|
||||||
flow tracking that was repealed.
|
flow tracking that was repealed.
|
||||||
|
|
||||||
** TODO Catching a use-after-release statically
|
** NEXT Catching a use-after-release statically
|
||||||
|
Decided 2026-09-25: a study, not a build — how arena memory escapes in real Flan code, and whether a sound lexical check would catch most of it. The result goes in docs/BUILT.md; nothing is built on it without the author.
|
||||||
Open, and for the first time with evidence available: the epoch trap is built, and
|
Open, and for the first time with evidence available: the epoch trap is built, and
|
||||||
there is a =Vec= to write real arena programs with, so whether the escapes that
|
there is a =Vec= to write real arena programs with, so whether the escapes that
|
||||||
actually occur are lexical can now be answered. The next thing to look at, not the
|
actually occur are lexical can now be answered. The next thing to look at, not the
|
||||||
@ -1051,7 +1057,8 @@ Every program in the corpus that compiles, has a =main= and terminates agrees wi
|
|||||||
the LLVM build down to stderr. =docs/BUILT.md=, "The hand-written x86 backend, and
|
the LLVM build down to stderr. =docs/BUILT.md=, "The hand-written x86 backend, and
|
||||||
the four measurements behind it", is the account.
|
the four measurements behind it", is the account.
|
||||||
|
|
||||||
** TODO Nothing pins the LLVM side at -O0 when the two backends are compared
|
** NEXT Nothing pins the LLVM side at -O0 when the two backends are compared
|
||||||
|
Decided 2026-09-25: the x86 parity survey compares against LLVM at -O0 only; -O2 is never its target. The acceptance suite's paired -O2/-O0 rows stay, being the check for undefined behaviour in emitted IR, which is a different question.
|
||||||
The survey builds both sides at the default =-O2=, so a construct LLVM folds is
|
The survey builds both sides at the default =-O2=, so a construct LLVM folds is
|
||||||
compared as a constant rather than as a lowering. That is how the float =%= gap
|
compared as a constant rather than as a lowering. That is how the float =%= gap
|
||||||
survived. Two things would close it: an =-O0= pass of the sweep, and something
|
survived. Two things would close it: an =-O0= pass of the sweep, and something
|
||||||
@ -1116,7 +1123,8 @@ The module links and runs. What is not proved is a collection running while a li
|
|||||||
instance of a dyn-holding struct sits in a frame of a body that module delivered.
|
instance of a dyn-holding struct sits in a frame of a body that module delivered.
|
||||||
For the next sweep rather than for a lane.
|
For the next sweep rather than for a lane.
|
||||||
|
|
||||||
** TODO A sliced string loses the trailing NUL
|
** NEXT A sliced string loses the trailing NUL
|
||||||
|
Decided 2026-09-25: both backends emit a NUL after every string literal, and a declare-c wrapper passes a literal argument to C without the copy it makes for any other string. A string is still pointer and length; no slice is promised a NUL. Rules out a NUL guarantee on every string.
|
||||||
The x86 backend emits a NUL after every string constant and the LLVM one does not,
|
The x86 backend emits a NUL after every string constant and the LLVM one does not,
|
||||||
so a =declare-c= wrapper leaning on the courtesy is already backend-dependent as
|
so a =declare-c= wrapper leaning on the courtesy is already backend-dependent as
|
||||||
well as slice-dependent. The contract is pointer and length, and nothing promised
|
well as slice-dependent. The contract is pointer and length, and nothing promised
|
||||||
@ -1127,7 +1135,8 @@ Decided 2026-09-25: emit the x86 backend's =.cfi= directives in every build. No
|
|||||||
They are correct in every build and free at runtime, and a release build is where
|
They are correct in every build and free at runtime, and a release build is where
|
||||||
a crash would most want them. One =if= in three places.
|
a crash would most want them. One =if= in three places.
|
||||||
|
|
||||||
** TODO A !DILexicalBlock per Let
|
** WAIT A !DILexicalBlock per Let
|
||||||
|
Decided 2026-09-25: waits until Flan is debugged in gdb or lldb; the break buffer, which reads the shadow stack, already answers correctly.
|
||||||
Inside nested =let=s that bind the same name, a debugger still answers with the
|
Inside nested =let=s that bind the same name, a debugger still answers with the
|
||||||
outer one. The disambiguating suffix makes both visible, which is not the same as
|
outer one. The disambiguating suffix makes both visible, which is not the same as
|
||||||
making the answer right. It needs block structure the typed IR does not carry, and
|
making the answer right. It needs block structure the typed IR does not carry, and
|
||||||
@ -1241,7 +1250,8 @@ build, because a layout that changes with a build flag can disagree silently
|
|||||||
across the reload boundary. One addition: a budget, because =retry= needs a
|
across the reload boundary. One addition: a budget, because =retry= needs a
|
||||||
handler that can make the same request succeed.
|
handler that can make the same request succeed.
|
||||||
|
|
||||||
** TODO The Vec generation word has no reader
|
** NEXT The Vec generation word has no reader
|
||||||
|
Decided 2026-09-25: remove the word, and in a dev build fill a Vec's old buffer with the dead-beef pattern when a push moves it, so a stale slice reads visibly wrong values. No slice layout change; a release build is untouched. Rules out a dev-only word on every slice.
|
||||||
It is bumped on reallocation and read by nothing. The stale-slice trap it exists
|
It is bumped on reallocation and read by nothing. The stale-slice trap it exists
|
||||||
for needs a slice that can carry the Vec's identity, and a slice is pointer and
|
for needs a slice that can carry the Vec's identity, and a slice is pointer and
|
||||||
length — so either slices grow a word in a dev build or the trap does not exist.
|
length — so either slices grow a word in a dev build or the trap does not exist.
|
||||||
@ -1254,7 +1264,8 @@ live bytes, 0 for none, that a =retry= handler raises. The failure bullet says
|
|||||||
"raises the allocator's budget" where it said "grows the arena", since no arena
|
"raises the allocator's budget" where it said "grows the arena", since no arena
|
||||||
grows. A growable arena is not ruled out; nothing here asks for one.
|
grows. A growable arena is not ruled out; nothing here asks for one.
|
||||||
|
|
||||||
** TODO The Vec header is not the size the spec fixes
|
** NEXT The Vec header is not the size the spec fixes
|
||||||
|
Decided 2026-09-25: five words in every build, the epoch included, so a release build still traps on a container whose region was released. The spec changes to match the code; nothing else does.
|
||||||
Five words in every build rather than the spec's four, and for a stated reason: a
|
Five words in every build rather than the spec's four, and for a stated reason: a
|
||||||
redefinition module is built separately from its host and nothing makes the two
|
redefinition module is built separately from its host and nothing makes the two
|
||||||
agree on a struct size. Give the reload path a way to carry the build flags and
|
agree on a struct size. Give the reload path a way to carry the build flags and
|
||||||
@ -1321,7 +1332,8 @@ until something touches it. The registry is advisory: a key the class never
|
|||||||
declared is dropped by the next migration, which is data loss with no enforcement
|
declared is dropped by the next migration, which is data loss with no enforcement
|
||||||
behind it.
|
behind it.
|
||||||
|
|
||||||
** TODO A class registry keeps one slot list per class, not one per layout version
|
** WAIT A class registry keeps one slot list per class, not one per layout version
|
||||||
|
Decided 2026-09-25: waits for a case name-matching migration to the current list gets wrong.
|
||||||
"A redefined class's old instances stay resolvable" needs every version's metadata
|
"A redefined class's old instances stay resolvable" needs every version's metadata
|
||||||
retained for as long as any instance holds it, the way nothing is ever
|
retained for as long as any instance holds it, the way nothing is ever
|
||||||
=dlclose=d. What exists is one current slot list and one generation per class, and
|
=dlclose=d. What exists is one current slot list and one generation per class, and
|
||||||
@ -1399,7 +1411,8 @@ through a pointer into it is answerable; memcheck is told the same fact, so the
|
|||||||
same read is reported. The two stay two claims — different tools reaching
|
same read is reported. The two stay two claims — different tools reaching
|
||||||
different people.
|
different people.
|
||||||
|
|
||||||
** TODO The leak question across the corpus
|
** NEXT The leak question across the corpus
|
||||||
|
Decided 2026-09-25: one pass over the whole corpus with LeakSanitizer and memcheck's leak check on. Memory an allocator holds by design is set aside; memory nothing owns is a leak and is fixed. The sweeps' default stays leak-checking off.
|
||||||
Both sweeps run with leak checking off, because allocate-once-never-free is this
|
Both sweeps run with leak checking off, because allocate-once-never-free is this
|
||||||
runtime's design and a leak check produces a suppression list. A green sweep
|
runtime's design and a leak check produces a suppression list. A green sweep
|
||||||
therefore says nothing about who frees the newly allocating =(bytes s)=. Worth
|
therefore says nothing about who frees the newly allocating =(bytes s)=. Worth
|
||||||
@ -1499,7 +1512,8 @@ A finished program parks instead of dying, and a daemon op wakes it and re-enter
|
|||||||
Globals are not reset between runs — the process never died. Rules out a fresh
|
Globals are not reset between runs — the process never died. Rules out a fresh
|
||||||
process per run.
|
process per run.
|
||||||
|
|
||||||
** TODO Re-run does not work under --two-process
|
** NEXT Re-run does not work under --two-process
|
||||||
|
Decided 2026-09-25: re-run under =--two-process= starts a fresh child, installed redefinitions included, and says that globals start over because the process is new.
|
||||||
A finished child process is genuinely gone, so there is nothing to wake. Re-run is
|
A finished child process is genuinely gone, so there is nothing to wake. Re-run is
|
||||||
merged-build only, and since the default backend runs merged it is no longer the
|
merged-build only, and since the default backend runs merged it is no longer the
|
||||||
blocked case.
|
blocked case.
|
||||||
@ -1539,7 +1553,8 @@ was delivered" is a generation number rather than a name, so evaluating from
|
|||||||
inside a break into a thunk that stops on the same condition class is settled by
|
inside a break into a thunk that stops on the same condition class is settled by
|
||||||
comparing two integers.
|
comparing two integers.
|
||||||
|
|
||||||
** TODO Whose break it is, which no counter answers
|
** NEXT Whose break it is, which no counter answers
|
||||||
|
Decided 2026-09-25: fix it. A stop records whether the thread that stopped was running the evaluation's thunk or the program's own code, so the sentence is decided by the frame and not by the generation counter.
|
||||||
A game loop that signals during the build or the wait bumps the generation exactly
|
A game loop that signals during the build or the wait bumps the generation exactly
|
||||||
as a thunk would. The machine-readable fields stay right; what is wrong is the
|
as a thunk would. The machine-readable fields stay right; what is wrong is the
|
||||||
sentence. The per-frame program-or-eval label is computed by the daemon from
|
sentence. The per-frame program-or-eval label is computed by the daemon from
|
||||||
@ -1592,7 +1607,8 @@ that polls or waits. Full invisibility — a dev build linking the agent whether
|
|||||||
not the source says so — needs the package force-linked and is a decision about
|
not the source says so — needs the package force-linked and is a decision about
|
||||||
what =--dev= means.
|
what =--dev= means.
|
||||||
|
|
||||||
** TODO FLAN_AGENT_SOCKET in a shell's environment steals the socket
|
** NEXT FLAN_AGENT_SOCKET in a shell's environment steals the socket
|
||||||
|
Decided 2026-09-25: narrow the gate. The daemon also exports its own pid, and the constructor binds the socket only when that pid is the program's parent (or the program itself, in a merged build).
|
||||||
Binding unlinks the path first, and before the constructor that unlink was reached
|
Binding unlinks the path first, and before the constructor that unlink was reached
|
||||||
only by an explicit call. A sentence about the shape of the gate rather than an
|
only by an explicit call. A sentence about the shape of the gate rather than an
|
||||||
observed problem: only the daemon sets the variable and it never runs release
|
observed problem: only the daemon sets the variable and it never runs release
|
||||||
@ -1656,7 +1672,8 @@ on the frame, the restart listing carrying the signature, and the daemon compili
|
|||||||
each argument against the declared type and writing the values into the frame's
|
each argument against the declared type and writing the values into the frame's
|
||||||
buffer before aiming the channel.
|
buffer before aiming the channel.
|
||||||
|
|
||||||
** TODO The type identity of a local is not qualified
|
** NEXT The type identity of a local is not qualified
|
||||||
|
Decided 2026-09-25: a local's type prints package-qualified in the break buffer and the inspector, as a field's and a condition's already do.
|
||||||
Settled for conditions and for structs, because =Load= qualifies every declaration
|
Settled for conditions and for structs, because =Load= qualifies every declaration
|
||||||
at import. Still open for locals, where the debug information gives a bare name and
|
at import. Still open for locals, where the debug information gives a bare name and
|
||||||
nothing qualifies it.
|
nothing qualifies it.
|
||||||
@ -1748,7 +1765,8 @@ nothing orders the two. The read raised on a closed socket and the test binary
|
|||||||
exited 1 with no failure line, which is the worst shape a failure can have when
|
exited 1 with no failure line, which is the worst shape a failure can have when
|
||||||
a lane is judged on the exit status.
|
a lane is judged on the exit status.
|
||||||
|
|
||||||
** TODO A program driven by a real flan dev daemon under a sanitizer
|
** NEXT A program driven by a real flan dev daemon under a sanitizer
|
||||||
|
Decided 2026-09-25: =flan dev --sanitize= builds the host under ASan/UBSan on the LLVM backend (refused by name with =--x86=), and the @sanitize alias gains a case driving a real session through reloads and a break.
|
||||||
The daemon builds its host through its own path and the CLI has no way to pass a
|
The daemon builds its host through its own path and the CLI has no way to pass a
|
||||||
sanitizer flag to it. Named as the check worth adding next; a day rather than an
|
sanitizer flag to it. Named as the check worth adding next; a day rather than an
|
||||||
hour. The x86 backend is not a gap here — that pair is refused by name, because
|
hour. The x86 backend is not a gap here — that pair is refused by name, because
|
||||||
@ -1835,10 +1853,15 @@ pass. Deriving from clojure-mode at runtime stays rejected: it would add an
|
|||||||
external dependency to a mode that ships in this repository and needs nothing
|
external dependency to a mode that ships in this repository and needs nothing
|
||||||
beyond stock Emacs, and that community is mid-transition to a tree-sitter mode.
|
beyond stock Emacs, and that community is mid-transition to a tree-sitter mode.
|
||||||
|
|
||||||
** TODO 373 lines in 20 files still reindent differently
|
** DONE Every tracked .flan file reindents to itself
|
||||||
Concentrated in four files, untouched by the Emacs pass and untouched before it.
|
CLOSED: [2026-09-25]
|
||||||
The indenter and the hand-formatting there disagree about shapes nothing has
|
clojure-mode decided each shape. The indenter was wrong on one: a =with-= head,
|
||||||
looked at.
|
and a qualified =def…= or =with-= head, now indents as a body, and a qualified
|
||||||
|
name finds its unqualified part's spec. The rest was hand formatting and was
|
||||||
|
reindented: a =cond= or =match= result on its own line sits under its test, and
|
||||||
|
an ordinary call's later arguments align under its first. A lone =;= comment
|
||||||
|
line goes to =comment-column= in every Lisp mode, so continuation comments are
|
||||||
|
written as =;;= lines above the code instead.
|
||||||
|
|
||||||
** DONE C-c C-i inspects the expression at point
|
** DONE C-c C-i inspects the expression at point
|
||||||
No prompt, because the expression is already written in the buffer. =C-u= opens
|
No prompt, because the expression is already written in the buffer. =C-u= opens
|
||||||
@ -1962,6 +1985,12 @@ motion states in that mode; every other key, including what =special-mode-map=
|
|||||||
binds, stays Evil's. The other special-mode buffers (inspect, watch, doc, disassembly,
|
binds, stays Evil's. The other special-mode buffers (inspect, watch, doc, disassembly,
|
||||||
diagnostics, lower) have the same exposure and are not changed.
|
diagnostics, lower) have the same exposure and are not changed.
|
||||||
|
|
||||||
|
** NEXT C-x C-e in a package's file resolves names in that package
|
||||||
|
Decided 2026-09-25: CIDER's rule — an evaluation sent from a file resolves names
|
||||||
|
as code written in that file would, so =(integrate 1.0)= in =physics/step.flan=
|
||||||
|
reaches the package's own functions, =defn-= included. Today it answers
|
||||||
|
"unknown function", resolving as the program's main file.
|
||||||
|
|
||||||
** NEXT Evil takes the keys in the other Flan buffers
|
** NEXT Evil takes the keys in the other Flan buffers
|
||||||
The inspect, watch, doc, disassembly, diagnostics and lower buffers get the break
|
The inspect, watch, doc, disassembly, diagnostics and lower buffers get the break
|
||||||
buffer's treatment: the keys each binds itself go to Evil's normal and motion
|
buffer's treatment: the keys each binds itself go to Evil's normal and motion
|
||||||
@ -2047,7 +2076,8 @@ host built with no user program, and a load-file op — the reload machinery
|
|||||||
already builds a file as a module. Open: what =flan-rerun= does with no =main=,
|
already builds a file as a module. Open: what =flan-rerun= does with no =main=,
|
||||||
and a half-loaded file. =C-c C-k= is taken by the inspector.
|
and a half-loaded file. =C-c C-k= is taken by the inspector.
|
||||||
|
|
||||||
** TODO The daemon buffer is navigable but not coloured
|
** NEXT The daemon buffer is navigable but not coloured
|
||||||
|
Decided 2026-09-25: errors, warnings and notes take compilation-mode's faces, and the program's own output takes a face of its own so it reads apart from the compiler's.
|
||||||
=*flan*= is all plain text. =compilation-minor-mode= is on (=emacs/flan.el:822=)
|
=*flan*= is all plain text. =compilation-minor-mode= is on (=emacs/flan.el:822=)
|
||||||
so =next-error= works, but a minor mode installs no font-lock. Open: whether the
|
so =next-error= works, but a minor mode installs no font-lock. Open: whether the
|
||||||
program's output should look different from the compiler's.
|
program's output should look different from the compiler's.
|
||||||
@ -2212,7 +2242,8 @@ The five that run — =macros=, =macro-params=, =macro-unless=, =pkg-macro=,
|
|||||||
out with the other negative cases. The list stays explicit rather than a glob.
|
out with the other negative cases. The list stays explicit rather than a glob.
|
||||||
Not yet run under the sweep: that waits for the batched =@sanitize=.
|
Not yet run under the sweep: that waits for the batched =@sanitize=.
|
||||||
|
|
||||||
** TODO The mutation pass has not been re-run
|
** NEXT The mutation pass has not been re-run
|
||||||
|
Decided 2026-09-25: re-run it once after the second batch of 2026-09-25 merges, with the heavy sweeps, after asking the author.
|
||||||
Sixty mutations, nineteen of which left the whole suite green; all nineteen are
|
Sixty mutations, nineteen of which left the whole suite green; all nineteen are
|
||||||
closed, each re-planted and watched fail against the new test. What is open is
|
closed, each re-planted and watched fail against the new test. What is open is
|
||||||
that the pass has not been run again, so nineteen is the old number.
|
that the pass has not been run again, so nineteen is the old number.
|
||||||
@ -2232,10 +2263,10 @@ it kept; without =keep= that file is gone and the second half is =None=. The
|
|||||||
two-process daemon moves the host's IR from the path it is given and no longer
|
two-process daemon moves the host's IR from the path it is given and no longer
|
||||||
recomputes =Build.workdir=.
|
recomputes =Build.workdir=.
|
||||||
|
|
||||||
** TODO The 2MB OFL font is not vendored
|
** CANCELLED The 2MB OFL font is not vendored
|
||||||
One example wants a font that is OFL and redistributable; it says on screen when
|
CLOSED: [2026-09-25]
|
||||||
it is missing and runs either way. A call about the repository, not about the
|
Two megabytes of history for one example that already says on screen when the
|
||||||
port.
|
font is missing and runs without it.
|
||||||
|
|
||||||
** DONE old-ocaml/ and the built executables are untracked on purpose
|
** DONE old-ocaml/ and the built executables are untracked on purpose
|
||||||
The executables are what a build drops beside their sources. =old-ocaml/= is the
|
The executables are what a build drops beside their sources. =old-ocaml/= is the
|
||||||
|
|||||||
@ -581,12 +581,27 @@ For `syntax-propertize-function'."
|
|||||||
("declare" . 1)
|
("declare" . 1)
|
||||||
("declare-c" . 1))
|
("declare-c" . 1))
|
||||||
"How each form indents, by name.
|
"How each form indents, by name.
|
||||||
Anything not named here that begins with `def' is treated as `:defn' by
|
A qualified name falls back to the entry for its unqualified part. Anything
|
||||||
`flan-indent-function'; anything else indents as a function call.")
|
still unnamed that begins with `def' (but not `default') or `with-' indents as
|
||||||
|
`:defn' in `flan-indent-function'; anything else indents as a function call.")
|
||||||
|
|
||||||
(defun flan--indent-spec (name)
|
(defun flan--indent-spec (name)
|
||||||
"The indent spec for the form called NAME, or nil."
|
"The indent spec for the form called NAME, or nil.
|
||||||
(and name (cdr (assoc name flan-indent-specs))))
|
A qualified name falls back to its unqualified part, so `rl/when' would find
|
||||||
|
the entry for `when' — `clojure--get-indent-method' does the same."
|
||||||
|
(and name
|
||||||
|
(cdr (or (assoc name flan-indent-specs)
|
||||||
|
(and (string-match "/\\([^/]+\\)\\'" name)
|
||||||
|
(assoc (match-string 1 name) flan-indent-specs))))))
|
||||||
|
|
||||||
|
(defun flan--definer-p (name)
|
||||||
|
"Non-nil if NAME indents as a definition or a `with-' form.
|
||||||
|
Either may be qualified: `rl/with-drawing' is a `with-' form. This is
|
||||||
|
`clojure-indent-function''s fallback for a head with no spec, regexp and all;
|
||||||
|
`default…' is excluded there because it is not a definer, and here too."
|
||||||
|
(and name
|
||||||
|
(string-match "\\`\\(?:\\S +/\\)?\\(def[a-z]*\\|with-\\)" name)
|
||||||
|
(not (string-match-p "\\`default" (match-string 1 name)))))
|
||||||
|
|
||||||
(defconst flan--labelled-forms '("dotimes" "while" "until")
|
(defconst flan--labelled-forms '("dotimes" "while" "until")
|
||||||
"Loops that may carry a label, which `break' and `continue' name.
|
"Loops that may carry a label, which `break' and `continue' name.
|
||||||
@ -755,8 +770,9 @@ decision to `calculate-lisp-indent'."
|
|||||||
;; No spec. Anything else spelled `def…' is a definition and indents
|
;; No spec. Anything else spelled `def…' is a definition and indents
|
||||||
;; like one, which covers `defstruct', `defdata', `defunion',
|
;; like one, which covers `defstruct', `defdata', `defunion',
|
||||||
;; `defenum', `defonce', `defconst' and `defalias' without naming
|
;; `defenum', `defonce', `defconst' and `defalias' without naming
|
||||||
;; them.
|
;; them. A `with-' form is a body too — `rl/with-drawing',
|
||||||
((and name (string-match-p "\\`def" name))
|
;; `rl/with-mode-2d camera' — whatever it takes before the body.
|
||||||
|
((flan--definer-p name)
|
||||||
(+ lisp-body-indent head-column))
|
(+ lisp-body-indent head-column))
|
||||||
;; A clause: `(name [params] body…)'. `handler-bind', `handler-case'
|
;; A clause: `(name [params] body…)'. `handler-bind', `handler-case'
|
||||||
;; and `restart-case' all write their clauses this way, and the head is
|
;; and `restart-case' all write their clauses this way, and the head is
|
||||||
|
|||||||
@ -326,6 +326,54 @@
|
|||||||
y 2.0]
|
y 2.0]
|
||||||
(print y))")
|
(print y))")
|
||||||
|
|
||||||
|
;; A `with-' form is a body, qualified or not, whatever it takes on the head's
|
||||||
|
;; line — `clojure-indent-function''s fallback. From `examples/core-2d-camera.flan'.
|
||||||
|
(test-flan-mode--check
|
||||||
|
"a qualified with- form indents its body by two past an argument"
|
||||||
|
"(rl/with-mode-2d camera
|
||||||
|
(rl/draw-rectangle-rec player rl/red)
|
||||||
|
(rl/draw-grid 10 1.0))")
|
||||||
|
|
||||||
|
(test-flan-mode--check
|
||||||
|
"a qualified def form indents its body by two"
|
||||||
|
"(m/defthing name
|
||||||
|
(body))")
|
||||||
|
|
||||||
|
;; A qualified name finds the spec of its unqualified part.
|
||||||
|
(test-flan-mode--check
|
||||||
|
"a qualified when keeps when's spec"
|
||||||
|
"(rl/when (ready?)
|
||||||
|
(go))")
|
||||||
|
|
||||||
|
;; `default…' begins with `def' and is not a definer.
|
||||||
|
(test-flan-mode--check
|
||||||
|
"a default-prefixed call aligns its arguments"
|
||||||
|
"(default-color a
|
||||||
|
b)")
|
||||||
|
|
||||||
|
;; A cond result on its own line sits under its test, not deeper.
|
||||||
|
(test-flan-mode--check
|
||||||
|
"a cond result on its own line aligns with its test"
|
||||||
|
"(cond
|
||||||
|
(= k 1)
|
||||||
|
(one)
|
||||||
|
:else
|
||||||
|
(other))")
|
||||||
|
|
||||||
|
(test-flan-mode--check
|
||||||
|
"a match result on its own line aligns with its pattern"
|
||||||
|
"(match v
|
||||||
|
(Int n) n
|
||||||
|
(List items)
|
||||||
|
(length items))")
|
||||||
|
|
||||||
|
;; A trailing argument of an ordinary call aligns under the first argument,
|
||||||
|
;; even when it is long; only a `with-', `def' or specced head gives a body.
|
||||||
|
(test-flan-mode--check
|
||||||
|
"an ordinary call's arguments align under the first one"
|
||||||
|
"(push missing
|
||||||
|
`(when (ok) (go)))")
|
||||||
|
|
||||||
|
|
||||||
;;; Font lock
|
;;; Font lock
|
||||||
|
|
||||||
|
|||||||
@ -13,8 +13,8 @@
|
|||||||
;; Normative references: spec-memory.md (ownership, containers, places,
|
;; Normative references: spec-memory.md (ownership, containers, places,
|
||||||
;; generics, function values) and spec-conditions.md (restart semantics).
|
;; generics, function values) and spec-conditions.md (restart semantics).
|
||||||
|
|
||||||
(import rl "vendor:raylib") ; directory = package, declaration optional;
|
;; Imports are always qualified: rl/foo.
|
||||||
; imports are always qualified rl/foo
|
(import rl "vendor:raylib") ; directory = package, declaration optional
|
||||||
|
|
||||||
;; ── Type notation ─────────────────────────────────────────────────────
|
;; ── Type notation ─────────────────────────────────────────────────────
|
||||||
;; [4 f32] fixed array — a value, copies on assignment
|
;; [4 f32] fixed array — a value, copies on assignment
|
||||||
|
|||||||
@ -213,8 +213,9 @@
|
|||||||
|
|
||||||
;; ── The refusals, each asserted on its own reason ─────────────────
|
;; ── The refusals, each asserted on its own reason ─────────────────
|
||||||
(refusal "\"a\\nb\"") ; an escape inside a string
|
(refusal "\"a\\nb\"") ; an escape inside a string
|
||||||
(refusal "\"a\\\"b\"") ; an escaped quote — the case where a wrong
|
;; An escaped quote is the case where a wrong version returns `a\` and
|
||||||
; version returns `a\` and leaves `b"` behind
|
;; leaves `b"` behind.
|
||||||
|
(refusal "\"a\\\"b\"") ; an escaped quote
|
||||||
(refusal "\"unterminated") ; not a refusal, but the other string failure
|
(refusal "\"unterminated") ; not a refusal, but the other string failure
|
||||||
;; A set is read now, so what is left to refuse about one is its balance. A
|
;; A set is read now, so what is left to refuse about one is its balance. A
|
||||||
;; `#{` that pushed nothing would answer "no error" for both of these.
|
;; `#{` that pushed nothing would answer "no error" for both of these.
|
||||||
@ -228,8 +229,8 @@
|
|||||||
(refusal "\\a") ; a character literal
|
(refusal "\\a") ; a character literal
|
||||||
(refusal "12x") ; starts like a number, is not one
|
(refusal "12x") ; starts like a number, is not one
|
||||||
(refusal "[1 :]") ; a colon with no name
|
(refusal "[1 :]") ; a colon with no name
|
||||||
(refusal "@") ; not the start of any value — and the case a
|
;; `@` is the case a scan-to-delimiter reads as a one-byte symbol.
|
||||||
; scan-to-delimiter reads as a one-byte symbol
|
(refusal "@") ; not the start of any value
|
||||||
(refusal "`x") ; a Clojure reader macro, not EDN
|
(refusal "`x") ; a Clojure reader macro, not EDN
|
||||||
(refusal "[1 2}") ; the wrong closer
|
(refusal "[1 2}") ; the wrong closer
|
||||||
(refusal "]") ; a closer with nothing open
|
(refusal "]") ; a closer with nothing open
|
||||||
|
|||||||
@ -102,9 +102,9 @@
|
|||||||
(show-dec (slice lone-cont 0 1)) ; a continuation byte leading
|
(show-dec (slice lone-cont 0 1)) ; a continuation byte leading
|
||||||
(show-dec (slice overlong2 0 2)) ; overlong "/"
|
(show-dec (slice overlong2 0 2)) ; overlong "/"
|
||||||
(show-dec (slice overlong3 0 3)) ; overlong "/" again, three bytes
|
(show-dec (slice overlong3 0 3)) ; overlong "/" again, three bytes
|
||||||
(show-dec (slice overlong4 0 4)) ; and four. Added after a mutation run:
|
;; Added after a mutation run: relaxing 0xf0's floor to 0x80 left the
|
||||||
; relaxing 0xf0's floor to 0x80 left the
|
;; whole suite green without this line.
|
||||||
; whole suite green without this line.
|
(show-dec (slice overlong4 0 4)) ; and four
|
||||||
(show-dec (slice surrogate 0 3)) ; U+D800
|
(show-dec (slice surrogate 0 3)) ; U+D800
|
||||||
(show-dec (slice above-max 0 4)) ; U+110000
|
(show-dec (slice above-max 0 4)) ; U+110000
|
||||||
(show-dec (slice lead-f5 0 4)) ; 0xf5 leads nothing
|
(show-dec (slice lead-f5 0 4)) ; 0xf5 leads nothing
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user