Three escape tables became two, and the docs catch up

SPIKE-DUPLICITY.md 9 and SPIKE-DYNAMIC.md both still counted three and quoted
a flan_dyn.c comment that has since been rewritten. The typed side two are one
flan_escape_char with a framing each; the dyn copy stays, with the ownership
defence the section already accepted. NEXT.md named flan_break_resume, which
does not exist -- flan_restart_frame and flan_restart_take are the pair a break
loop resumes through.
This commit is contained in:
Joseph Ferano 2026-09-20 13:00:55 +07:00
parent 6461322aa9
commit 2d29528cad
3 changed files with 30 additions and 20 deletions

View File

@ -2395,8 +2395,11 @@ plan.org's single line on it (831) names a `for` the language does not have and
What is missing is the half only an editor can do — the leverage SBCL lacks. `eval` already compiles and runs an
expression inside the live program and the daemon already holds the struct layouts, so "ask the human, type-check
the answer, hand it over" is a short hop, and it is the one path the runtime today *refuses*: a restart with
parameters taken from the break loop traps, because `flan_break_resume` and `flan_restart_take` aim the channel at
a frame and have nothing to fill its buffer with. What it needs, end to end:
parameters taken from the break loop traps, because the pair a break loop resumes through — `flan_restart_frame`,
which hands back the frame the human picked, and `flan_restart_take`, which aims the transfer channel at it —
reaches the frame and has nothing to fill its buffer with. (`flan_break_resume` is what this paragraph named
before; there is no such function, and `flan_break_hook` is the unrelated thing that gets the break loop *into*
the agent in the first place.) What it needs, end to end:
- the frame already carries the arity and the signature as a string — `flan_restart_arity` and `flan_restart_sig`
beside `flan_restart_name`, the same walk, so `restarts` can say what each one takes;
- `:restarts` on the wire carries the signature per entry, so the minibuffer can show `use-value (i32)` rather

View File

@ -425,23 +425,28 @@ and reload semantics are working." Under this reading the gating item changed. C
## 9. Print, hash, sort — one family already duplicated, two not yet
**Printing is duplicated three ways on one side, and it is not the dyn/typed split.**
`runtime/flan_dyn.c:308-313` says so itself:
**Printing was duplicated three ways on one side, and it is not the dyn/typed split. Two of the three are
now one.** This section found three escape tables; the runtime dedupe lane took the two that were on the
same side and made them one function with two framings, which is what this section asked for. What is left
is the split it argued was correct.
> A text inside a structure, quoted and escaped. The same table as `flan_rt.c`'s `flan_escape_bytes` and
> `flan_dev.c`'s `flan_dev_emit_str`...
The table lives once, as `flan_escape_char` (`runtime/flan_rt.c:388`), and both typed-side printers call
it: `flan_escape_bytes` (`:422`), which builds a capped slice for `println`, and `put_str`
(`runtime/flan_dev.c:302`), which streams into the inspector's fixed buffer so the REPL can parse the
printed form back. The framing is each caller's and is the part that is genuinely theirs; the switch is
shared, because two printers disagreeing about what a string looks like is two wire formats.
Three escape tables: `runtime/flan_rt.c:383` (`flan_escape_bytes`, with `:366-372` noting it is already "the
same escape table as `flan_dev_emit_str`"), `runtime/flan_dev.c:237` and again at `:551`, and
`runtime/flan_dyn.c:314` (`emit_escaped`). `SPIKE-DYNAMIC.md:329-332` calls its own "the third copy" and
leaves the instruction "If that table changes, change all three."
The dyn copy stays, as `emit_escaped` (`runtime/flan_dyn.c:472`), and says why at `:465-470`:
Two of those three are on the same side. `flan_rt.c`'s is `println`'s; `flan_dev.c`'s is the inspector's, so
the REPL can parse the printed form back. That pair predates dyn and is same-side duplication that dyn has
now made three-way. The *dyn* copy is the one with a defence, and `SPIKE-DYNAMIC.md:334-338` gives it: a
typed `Vec` prints as `<vec>` because the typed printer will not walk storage it does not own, while a dyn
vec's storage belongs to the collector and the printer is inside the runtime that owns it. That is correct
duplicity. Three tables is not.
> This copy is deliberate, and the argument for it is docs/SPIKE-DUPLICITY.md §9's: the dyn printer lives
> inside the runtime that owns the storage it walks, which is why it prints a dyn vec structurally where
> the typed printer answers `<vec>`. The whole printer is this side's; the table it shares with the other
> side is the part that must not drift. If that table changes, change this one.
That is the defence `SPIKE-DYNAMIC.md` gives and this section accepted: a typed `Vec` prints as `<vec>`
because the typed printer will not walk storage it does not own, while a dyn vec's storage belongs to the
collector and the printer is inside the runtime that owns it. One copy per *side*, which is the doctrine.
Two tables is the answer; three was not.
**Hashing is typed-only.** `flan_hash_fn`, `flan_hash_flat`, `flan_hash_str`, `flan_hash_combine`
(`runtime/flan_rt.c:1807, 1911, 1939, 1954`), reached through `Types.keyable` and the checker's `key_pair`

View File

@ -326,10 +326,12 @@ running program**, not read off `lib/render.ml` — that file is the REPL's insp
| nil | — | `nil` |
The leading space before every element is what `lib/render.ml`'s slice loop emits and what a Flan program
prints today; a tidier answer would diverge from an acceptance test. The escape table is the third copy
of the one in `flan_rt.c`'s `flan_escape_bytes` and `flan_dev.c`'s `flan_dev_emit_str` — those two are
already kept identical because the REPL parses the printed form back, and this one joins them. If that
table changes, change all three.
prints today; a tidier answer would diverge from an acceptance test. The escape table is the dyn side's
own copy of the typed side's. The typed side used to have two and now has one — `flan_escape_char`
(`runtime/flan_rt.c:388`), which `flan_escape_bytes` calls for `println` and `flan_dev.c`'s `put_str`
calls for the inspector, each framing the result its own way — so there are two tables in the tree, not
three, and the split that is left is the dyn/typed one argued just below. If that table changes, change
this one too.
**A typed `Vec` prints as `<vec>` and a dyn vec does not.** That looks like a mismatch and is not. The
typed printer's refusal is about walking storage it does not own — `render.ml` says so, and directs you to