From 5557594f3194416e94b2754d71098febe45bcbf7 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Fri, 25 Sep 2026 06:45:26 +0700 Subject: [PATCH] What the dogfooding session turned up --- TODO.org | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/TODO.org b/TODO.org index 08b63fcb..fcf9257b 100644 --- a/TODO.org +++ b/TODO.org @@ -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 :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