From 135a780f3d2f08348b7711f6141b52a669c437c4 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 12 Sep 2026 16:53:43 +0700 Subject: [PATCH] A watch window, an address-rooted inspector, and structural typing that is just layout --- NEXT.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/NEXT.md b/NEXT.md index eef6d32..f072553 100644 --- a/NEXT.md +++ b/NEXT.md @@ -653,6 +653,54 @@ exists for the expander, which cannot work that way. `exported` and the refusal machinery already exist and take a second rule in one line, but there is no way for a package to *mark* a name private, and adding one means a parser change. +## Decided in discussion — three more, all approved and none started + +**A watch window, ported from the author's Clojure one.** `~/Development/siam-farmer/watch.el` is the working +original; read it first. Its design, and the parts to keep: + +- **The program defines what is shown.** Emacs polls one function — `(watch/render)` — and paints the string it + returns. There is no watch-expression machinery, no per-variable registration, no UI for building a query. The user + writes a function in the game. +- **Async, not synchronous.** A sync request on a 0.2s timer blocks Emacs's UI thread every tick. The original says so + in a comment, having evidently learned it. +- **`replace-buffer-contents`, not erase-and-insert.** It diffs, so point and scroll survive every tick; erasing yanks + the cursor to the top five times a second. +- Nothing is appended — the buffer is always the current snapshot. + +**The one thing that does not port, and it decides the design.** In Clojure an eval is cheap. Here `eval-expr` +*compiles a module and `dlopen`s it* — tens of milliseconds and a new `.so` each time, in a directory nothing sweeps. +Polling at 5Hz would produce hundreds of shared objects a minute. So **the watch thunk must be compiled once and then +called repeatedly**, which makes this a daemon feature rather than something the Emacs side can do alone: a `watch` +op that compiles on first use and a cheap re-invoke per tick. + +The author also raised **ghost text** as an alternative or addition to a dedicated buffer — values shown inline at the +code they belong to. Not designed; the buffer is the port, ghost text is a further question. + +**The inspector gets a second way to start: an address and a type.** Closes the hole in `DISCUSS.md` item 1, where `i` +on a local in any frame but the innermost evaluates a name wherever the evaluator stands rather than in that frame, and +may silently inspect something else. + +The inspector navigates by rewriting *expressions* — `(.pos b)` where the last was `b` — and `l` pops back. That is +why the obvious fix, rooting it at the slot's address, was rejected: an address is not an expression, so the first +`RET` has nothing to build from. **The shadow stack changed this.** The daemon now has a frame's address and every +slot's type, so the second rooting mode is cheap: start from an address plus a type, and stepping into a field is +address-plus-offset with the field's type. `Render.render` already does exactly that arithmetic for locals, and +navigation keeps working, which was the objection. + +**Structural typing requires identical layout — same fields, same types, same order.** Settled by the author, and it +makes the feature simple rather than hard: structural compatibility becomes "the same memory", which costs nothing at +run time and needs no copy, no reordering and no adaptor. The motivating case is `{.x 1.0 .y 1.0}` and that order is +natural anyway. + +**Flexible field order waits for classes, deliberately.** A class has an implementation-defined representation, so the +compiler owns the layout and field order stops being observable — any order can match. That is the right place to pay +for flexibility, because a class already carries identity and metadata, and a `Vector2` should pay for neither. See the +`defclass` entry: `Handle` is the gate. + +Note what this settles from the earlier discussion: writability was the question that decided layout, and requiring +identical layout answers it — fields are writable on the ordinary terms, by value a copy and through a `(Ptr T)` the +original, with no special case. + ## Blocked and unfinished Everything below was found, decided or half-built and then stopped. Each says what blocks it. Nothing here is a