OCaml side is done and builds. Emacs side is mid-edit and INCOMPLETE — see
the handoff below. `dune build --root . @check` is green. `dune test --root .`
was NOT run. The .el files were not byte-compiled and flan-inspect.el will not
work as it stands: the state layer still speaks the old single-expression
shape while the helpers above it have been rewritten for roots and paths.
WHAT WORKS (daemon, lib/, in the parent commit and unchanged here)
- `(:op "inspect" :frame N :slot I :path (...))` renders one value rooted at a
stopped frame's slot address. `Session.render_slot` is `render_locals` with a
path applied to the root before the walk and one line out instead of one per
slot; no second walk was written and no backend change was needed.
- A path step is a string for a struct field, an integer for an array or slice
element, and the symbol `some` for an option's payload. A union case field is
spelled `Union.case.field`, because the payload's offset depends on the case
and only the renderer knows which case the value is in.
- Every step that does not fit the type in hand is refused by name with the
reason: a field the type does not have, an index past a fixed array's end,
`some` on something that is not an option, a union field without its case.
- The frame's identity IS checked, and not by a second copy: `Dev.stopped_frame`
is one function now and `locals` and `inspect` both go through it — alive,
stopped, frame exists, the frame is the program's and not a thunk's, the body
is one this session holds, the slot count matches, and `Emit.slot_fingerprint`
matches. `inspect` additionally refuses an unbound slot, for the listing's
reason: a null address would fault on the stopped game thread.
- The slot travels by INDEX, not by name. Two slots can share a name
(`fresh_slot` only allocates) and a refused slot is not in the listing, so
neither the name nor the position identifies one. `locals` now puts the slot
index as a fourth element on each `:locals` entry.
- `Dev.run_render_thunk` is one function; `locals`, `globals` and `inspect`
share the build/deliver/wait/read tail.
- `layout`'s "union values are milestone 6" is corrected.
WHAT IS HALF-BUILT, AND EXACTLY WHERE IT STOPS
`emacs/flan-inspect.el`. Done: the header comment explaining the two roots;
`flan-inspect-step-expr` taking a 3-element `:field` step; `flan-inspect-wire-step`;
`flan-inspect--root-label`; `flan-inspect-refusal` taking an optional ROOT and
allowing an option's payload under a `:slot` root.
NOT done, and this is the whole of what is left:
1. `flan-inspect--expr` / `flan-inspect--stack` still hold a bare expression.
They must become `flan-inspect--root` (`(:expr EXPR)` or
`(:slot FRAME SLOT NAME)`) plus `flan-inspect--path`, with stack entries of
`(ROOT PATH . POINT)`.
2. `flan-inspect--value` must branch on the root: `eval-expr` with
`(flan-inspect--root-label root path)` for `:expr`; for `:slot`, send
`(:op "inspect" :frame F :slot S :path P)` with P built by
`flan-inspect-wire-step` over the path, and take `:value` from the reply.
3. `flan-inspect--show`, `-into`, `-pop`, `-refresh` rewired to (ROOT PATH).
`-into` must build a `:some` step when the node's kind is `option`, and put
the parent node's `:type` as the third element of a `:field` step.
4. New entry point `flan-inspect-slot (frame slot name)`, kept separate from
`flan-inspect (expr)` — `emacs/flan-mode.el` autoloads and binds the latter
and that file is out of this lane.
5. `emacs/flan-cnr.el`: the `flan-cnr-inspect` text property must carry
`(:slot FRAME SLOT NAME)` on a local line — the slot index is `(nth 3 l)`
now — and `(:expr NAME)` on a global line, with `flan-cnr-inspect`
dispatching to the right entry point.
6. `emacs/test-flan-cider.el`: the fixture at "`i' on a local inspects it by
name" asserts the old behaviour and must be rewritten; the locals fixtures
need a fourth element.
7. `test/test_dev.ml`: no coverage of the new op yet. The discriminating test
to write first is a stack whose OUTER frame has a local whose name is also a
global with a different value, asserting `inspect` answers the frame's value.
A new `test/programs/dev-inspect.flan` is picked up by the existing glob.
8. `BUILT.md`, `emacs/MANUAL.md`, and striking the item from `NEXT.md`'s
"Decided in discussion" and `DISCUSS.md` item 1 — none done.
THE THREE ANSWERS THE TASK ASKED FOR
- Navigation in the new mode: the daemon supports it fully — RET extends the
path, `l` shortens it, and both are a fresh request, so the view is never
stale. The Emacs half of that is item 3 above and is not wired.
- `l` does not cross between the modes, and that is structural rather than a
rule: a stack entry carries its own root, RET only ever extends the path
under the root it already has, and every new root starts with an empty
stack. A mixed stack cannot be constructed, so the question does not arise —
and it stays answered if a third rooting mode is added.
- What each mode cannot do that the other can. The expression root works on a
RUNNING program and roots at anything you can write, a call included; it
cannot name a frame, so it is the bug. The slot root names one frame and one
slot and is exact; it reaches an option's payload and a union case's fields,
which have offsets but no accessor in the surface language; it needs a
stopped program, it is refused when the frame's body was redefined since it
was entered, and it cannot root at an expression at all.