An inspector and a conditions buffer, and a restart nobody could take
Two CIDER-shaped buffers: C-c C-i navigates a value, C-c C-b shows the condition, the restarts and the stack. C-c C-M-b keeps the old one-key prompt. The inspector needs no protocol change at all. eval-expr already answers a rendered string, and Session.render writes exactly seven shapes, so that string is a grammar. Navigation is a stack of *expressions* rather than of handles: going into :pos sends (.pos b), into element 2 sends (at (.tags b) 2) - both ordinary Flan a person could type. CIDER keeps its stack on the server because a JVM value can be retained; nothing here can, since a Flan value has no header and the render thunk is dlclosed the moment it returns. The view is therefore never stale, where CIDER's shows the object as it was when you pushed. What it buys over C-x C-e is the depth-4 span-8 bound: a field past it comes back as ... and nothing recovers it from the echo area, and re-rooting renders it from depth 0. SBCL decided the order - condition, restarts, then stack - because invoke-debugger prints the condition and show-restarts and then stops; the backtrace is a command you type. The restarts are the decision and the stack is the explanation for it. And SBCL found a live bug. show-restarts omits the bracket on a name already used further in, which is not decoration: §4 takes the first frame offering a name, so a second frame offering retry is real, is on the list, and cannot be chosen. The old prompt showed retry twice and sent the string either way, and the inner frame took it silently. restarts.flan's own nested function has been that counterexample since the transfer landed. The buffer draws the shadowed row unbracketed and refuses RET on it by name, with a test asserting nothing was sent - which stops the lie without restoring the choice. Taking a restart by index is the fix and is recorded as such. Sections that cannot be filled are drawn saying why rather than omitted: a missing section cannot be told from an empty one, and only one of those is a fact about the program.
This commit is contained in:
parent
13e8002d39
commit
6ed413a232
27
NEXT.md
27
NEXT.md
@ -477,6 +477,17 @@ vague intention — if it is listed, someone has already established it is real.
|
||||
|
||||
### Bugs found and not yet fixed
|
||||
|
||||
- **A shadowed restart is offered and cannot be taken.** §4 says lookup takes the *first* frame offering a name, and
|
||||
`flan_find_restart` does exactly that — so when two frames offer `retry`, the second one is real, is on the list the
|
||||
break loop prints, and is unreachable. The old `C-c C-b` prompt showed `retry` twice and sent the string either way;
|
||||
the inner frame took it, silently. `test/programs/restarts.flan`'s own `nested` function is the counterexample, and
|
||||
it has been there since the transfer landed. The C&R buffer now draws the shadowed row unbracketed and refuses
|
||||
`RET` on it by name, which stops the lie but does not restore the choice. The fix is to take a restart **by index**
|
||||
rather than by name — SBCL identifies them positionally for precisely this reason:
|
||||
- `(:op "restart-at" :index N)` on the daemon, and a matching agent verb. `flan_restart_name(i, &len)` already
|
||||
walks the stack by index, so resuming by index is that loop plus `flan_break_resume`'s tail. C only, no compiler
|
||||
work.
|
||||
|
||||
- **A restart chosen at a break inside a thunk is accepted, announced, and silently not taken.** Demonstrated.
|
||||
`flan_reload_call` allocates its own `xfer` and discards it on return, so a transfer aimed at a frame below the
|
||||
`flan_agent_poll` C frame unwinds only as far as the thunk. Three statements that the program will resume, none
|
||||
@ -509,6 +520,22 @@ Sixty mutations, nineteen left the whole suite green. The severe cluster is clos
|
||||
- And a warning: a reader mutation makes the suite **hang** rather than fail. A green run is not the only outcome to
|
||||
plan for in CI.
|
||||
|
||||
### Asked for by the editor lanes
|
||||
|
||||
- **`(:op "layout" :type T)` → the struct's fields and their types.** `Tast.structs` is held by the daemon at all
|
||||
times because it owns the build, and **no running program is involved** — this is the cheapest real win on the
|
||||
list, and the C&R buffer already draws its result.
|
||||
- **`(:op "condition")` → the stopped program's condition, rendered.** Two steps: `break_loop` currently does
|
||||
`(void)condition;` and *discards the pointer*, so stash it beside `condition_name`; then the daemon builds a render
|
||||
thunk aimed at that address, which is `Session.render` rooted at a `Ptr` instead of an expression.
|
||||
- **One thing to get right for both:** the type must be an identity the daemon can resolve to a `Tast` type, not a
|
||||
bare class name. The hook is handed a string, and two packages both declaring `Missing` leave the daemon unable to
|
||||
pick a layout. A qualified name or a type id. The same wrinkle bites locals later, because DWARF also gives a name.
|
||||
- **`(:op "backtrace")` is blocked** on frame metadata — unlocked by the DWARF work, then a new agent verb. Locals are
|
||||
blocked twice: DWARF for the frame layout, *and* the pointer-rooted render thunk. Restart source locations and
|
||||
arity are blocked too — `flan_restart` carries `prev`, `name_id`, `name` and `namelen`, so both need a new field in
|
||||
the frame, which means the compiler emitting it.
|
||||
|
||||
### One line away
|
||||
|
||||
- **`match` over enums.** Fully desugarable, wanted, and blocked only by `Ast.pattern` needing a keyword case, which
|
||||
|
||||
@ -24,6 +24,11 @@
|
||||
(autoload 'flan-show-output "flan-dev" nil t)
|
||||
(autoload 'flan-repl "flan-repl" nil t)
|
||||
(autoload 'flan-break "flan-dev" nil t)
|
||||
;; The two CIDER-shaped buffers. They reach the daemon through an indirection
|
||||
;; of their own so that fixtures can drive them, so autoloading is all the
|
||||
;; wiring they need.
|
||||
(autoload 'flan-inspect "flan-inspect" nil t)
|
||||
(autoload 'flan-cnr-show "flan-cnr" nil t)
|
||||
(autoload 'flan-doc "flan-dev" nil t)
|
||||
(autoload 'flan-dev "flan-dev" nil t)
|
||||
(autoload 'flan-dev-quit "flan-dev" nil t)
|
||||
@ -121,7 +126,14 @@ line is off screen."
|
||||
(define-key map (kbd "C-c C-d") #'flan-describe)
|
||||
(define-key map (kbd "C-c C-o") #'flan-show-output)
|
||||
(define-key map (kbd "C-c C-r") #'flan-repl)
|
||||
(define-key map (kbd "C-c C-b") #'flan-break)
|
||||
;; C-c C-b opens the buffer rather than the minibuffer prompt: it shows the
|
||||
;; same restarts plus the condition and the stack, and — the reason it
|
||||
;; replaces rather than joins — it is the one that refuses a *shadowed*
|
||||
;; restart instead of silently invoking a different frame's. flan-break is
|
||||
;; still there under C-c C-M-b for the one-key path.
|
||||
(define-key map (kbd "C-c C-b") #'flan-cnr-show)
|
||||
(define-key map (kbd "C-c C-M-b") #'flan-break)
|
||||
(define-key map (kbd "C-c C-i") #'flan-inspect)
|
||||
;; Help on the name at point. C-c C-d is taken by `flan-describe', which
|
||||
;; is about the session rather than about a name, and renaming a key that
|
||||
;; is already documented costs more than it is worth. Not C-c C-h either:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user