flan/test/programs/dev-locals.flan
Joseph Ferano 1cbe8ed386 The break loop keeps its condition, and the daemon renders it
The break loop used to discard the pointer it was handed, so the buffer
could name a BoundsError's fields and never show 648. Now the snapshot
stashes it, flan_agent_condition hands it back on the stopped thread,
and a daemon-built thunk — locals pointed at the condition — renders
each field. Delivered at-stop, so a resume-and-restop cannot get the
old type read over the new pointer.

The trap sites publish their loc around the hook call, the snapshot
copies it, and break answers :site with the line's text as :source —
the frame lines say where each call was; this is the only record of
the indexing itself.

Compiler temps are hidden from the locals listing rather than refused
as s4; a shadowing rebind strips its ~N except where the outer binding
is on the same list, where both keep their raw spelling.
2026-09-20 22:39:08 +07:00

44 lines
1.8 KiB
Plaintext

;;;; A program that stops with something worth looking at in the frame.
;;;;
;;;; dev-break.flan proves an editor can find out *that* a program stopped and
;;;; choose a restart; this one is about what the frame holds while it is
;;;; stopped. One local of each shape the structural printer has an arm for —
;;;; a parameter, a string, a struct, a fixed array, a bool — plus one that is
;;;; bound only *after* the error, which is the case that must come back
;;;; refused rather than rendered: its slot is storage nothing has written yet.
(import agent "vendor:agent")
(defstruct Point [x f32 y f32])
(defstruct Boom [why i32])
(defn look [n i64 label string] i64
(let [p (Point {.x 1.5 .y 2.5})
xs [10 20 30]
flag (> n 0)]
;; A loop, for its hidden bound: [dotimes] allocates a slot nobody named,
;; and the listing must *hide* it rather than refuse it by an invented
;; name — [s6] is not a variable anyone can find in this file.
(dotimes [hop 0] (print ""))
;; And a shadowing rebind. The checker suffixes the repeat as [label~2]
;; so the debug info never claims one binding is the other; the listing
;; keeps both raw spellings, because two rows both called [label] with
;; nothing to tell them apart would be worse.
(let [label "inner"]
(restart-case
(do (error (Boom {.why 7}))
;; Never reached before the break, so [after] is a slot with nothing
;; in it: the frame records a null for it and this is what "not bound
;; yet" has to mean.
(let [after (i64 99)] after))
(carry-on [] 5)))))
(defvar ticks i64)
(defn main [] i32
(agent/start "/tmp/flan-dev-locals-fallback.sock")
(print (look 3 "hello")) (println "")
(dotimes [i 4000]
(agent/wait 5)
(set ticks (+ ticks 1)))
0)