What the dogfooding session turned up

This commit is contained in:
Joseph Ferano 2026-09-25 06:45:26 +07:00
parent 9d7da2ba7a
commit 5557594f31

View File

@ -1731,13 +1731,99 @@ specification's own branch — the flag is the command and the printed shape is
error pattern, so anyone who wants one has the four lines, and the manual carries
them.
** TODO The break buffer prints a condition's fields, not its message
** TODO defclass slots take types, checked on write
=(defclass State [pause bool step bool])= reads as four untyped slots and
reports a duplicate =bool=. Wanted: the slot list is name/type pairs, as CLOS
does it. The type is a declaration about the values and not a layout — an
instance stays a map, so redefinition and lazy migration are unchanged. SBCL
checks it on write (=src/pcl/slots.lisp:160=, the typecheck before the store),
which is where the bad value is, so =put= is the site here.
Open: what migration does with a stored value that no longer fits a changed
slot type, and whether an untyped slot stays legal (it should — =dyn= is a type
and writing nothing should mean it).
** TODO println takes up to a second to appear
Output is drained by =flan--poll= at =flan-poll-interval=, 1.0s
(=emacs/flan.el:541=). A reply carries whatever was buffered when it was
composed, so anything the program prints after that waits for the next tick.
Polling faster costs a request a second for nothing most of the time; the
daemon pushing on its own connection is the other shape. Decide which.
** TODO set writes a class slot; put is for maps
=put= exists because an absent map key has no location to store into, which is
why =(get m k)= is refused as a place (=lib/parse.ml:1159=). A class instance is
not in that situation: its slots are fixed by the =defclass=, so a declared slot
always exists and =(set (get state :pause) true)= is a field store like
=(set (.velocity g) 0.0)=. Make =set= take it, and leave =put= to maps, where
insertion is real. Writing an undeclared slot through =set= is then a refusal
naming the class.
** TODO update: change a place by applying a function to it
=(set (.velocity g) (inc (.velocity g)))= names the place twice. Clojure's
=update= would be a macro over the same two steps, for a struct field and a
class slot alike.
Blocked on the double-evaluation question, which =++=, =--= and any
compound assignment share: =(update (at grid (next-index) c) inc)= evaluates
=(next-index)= twice, and a place with a side effect is then wrong rather than
slow. Either places get a general single-evaluation rule — bind every
subexpression of a place to a temp once, which is what C's compound assignment
does — or the language says a place must be side-effect free and refuses
otherwise. The first is the real fix and it is a change to how every place
lowers, not to one macro.
** TODO A session eval reported (CFn [] ()) does not cross into dyn yet
At =sand.flan:46:20=, the =:pause= in =(when (get state :pause) (return))=,
where =state= is a =defclass= instance with a =pause= slot. =(CFn [] ())= is
the prelude's =pause='s own type, so a keyword looks to have resolved to the
function of that name. Not reproduced: the file type checks, =flan reload= of
the same form builds, and a minimal =defclass= + =get= + =return= program
compiles. So it is the session path against an installed program, and what is
missing is what that daemon had installed at the time.
** TODO A digit does not take the restart RET takes
Pressing =0= left the program stopped; RET on the same line resumed it. Both
end in =flan-cnr-take=, but the digit path (=flan-cnr-take-number=,
=flan-cnr.el:559=) scans from =point-min= for the line whose =flan-cnr-index=
matches and calls =take= inside a =save-excursion=. Not reproduced yet — needs
a non-raylib program stopped under a test daemon.
** TODO Eval in the frame, from the break loop
An expression is evaluated at a frame boundary, so it sees globals and not the
stopped frame's locals — which are the values anyone stopped there wants. Wants
SLIME's eval-in-frame: pick a frame, and the expression is checked and run with
its slots in scope. The slots are already on the frame and already readable
(=flan_dev_frame_slot=); what is missing is checking an expression against that
frame's names and types.
** TODO The stack lists prelude frames
=0: pause <prelude>:151:7= is the breakpoint the author wrote, not a step in
their program. Prelude frames want hiding by default, with a key to show them.
** TODO There is no stepper
=(pause)= stops and offers restarts, frames, locals and the inspector, but
nothing advances a form at a time. CIDER instruments a form and steps the
instrumented copy; the equivalent here is a dev-build-only instrumented
redefinition, which the cell indirection already makes deliverable. Open:
whether stepping suspends the frame loop, and what it does to a game's clock.
** TODO A NaN cast says "does not fit", which reads as too big
=runtime/flan_rt.c:1008= covers every out-of-range float with one sentence, so
=(i32 nan)= reports the =i32= bounds as if the value had overshot them. NaN and
the infinities convert to no integer at all and want saying so by name. Found
by filling a struct holding an =f32= with =(filled 0xFF)=, where every bit set
is NaN.
** TODO The break buffer prints fields, not the sentence the runtime wrote
=ArithError — op 4, lhs -2147483648, rhs 2147483647= where the runtime's own
sentence for that op is "this value does not fit the integer type it is cast
to" (=runtime/flan_rt.c:986=). The good sentences exist and go to stderr on an
unhandled stop; the buffer renders the struct generically instead. Either put
the rendered sentence on the wire, or let a condition type carry a printer.
=op= being a bare number is the same gap — it is an enum spelled as =i32=.
sentence is "this value does not fit the integer type it is cast to"
(=runtime/flan_rt.c:986=). Worse for a dyn trap: =DynType= has no struct at all,
so the buffer says "no struct is named DynType" while =flan_dyn.c:799= has
written the operation, both tags and both values to stderr. The sentences exist
and go to the daemon buffer; the break buffer wants them on the wire.
=ArithError='s =op= being a bare number is the same gap — it is an enum spelled
as =i32=.
** TODO A backtrace frame names the function, not the call
=fninfo= (=lib/emit.ml:185=) holds one static =loc=, the =defn='s own, and