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 error pattern, so anyone who wants one has the four lines, and the manual carries
them. 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 =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 sentence is "this value does not fit the integer type it is cast to"
to" (=runtime/flan_rt.c:986=). The good sentences exist and go to stderr on an (=runtime/flan_rt.c:986=). Worse for a dyn trap: =DynType= has no struct at all,
unhandled stop; the buffer renders the struct generically instead. Either put so the buffer says "no struct is named DynType" while =flan_dyn.c:799= has
the rendered sentence on the wire, or let a condition type carry a printer. written the operation, both tags and both values to stderr. The sentences exist
=op= being a bare number is the same gap — it is an enum spelled as =i32=. 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 ** TODO A backtrace frame names the function, not the call
=fninfo= (=lib/emit.ml:185=) holds one static =loc=, the =defn='s own, and =fninfo= (=lib/emit.ml:185=) holds one static =loc=, the =defn='s own, and