From f4f61ae8c9b2b272dc527ba13e085f7a581e0dff Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 12 Sep 2026 13:30:15 +0700 Subject: [PATCH] Two questions became decisions, so they move to what is left --- DISCUSS.md | 60 ++++++++---------------------------------------------- NEXT.md | 27 ++++++++++++++++++++++++ sand.flan | 3 ++- 3 files changed, 37 insertions(+), 53 deletions(-) diff --git a/DISCUSS.md b/DISCUSS.md index 3983399..77e958e 100644 --- a/DISCUSS.md +++ b/DISCUSS.md @@ -8,51 +8,7 @@ Settled decisions live in `NEXT.md`. Reasons for what already exists live in `BU --- -## 1. Inspecting globals, separately, in the break buffer - -Locals are readable now; globals are not shown anywhere. They are arguably the more useful half in this language today, -because a Flan game keeps most of its state in top-level `defvar`s — `sand.flan` holds its entire grid that way. - -What already exists: the daemon's `describe` op returns the program's globals by name, and `Session.render` walks a -concrete type to a printed value. The `eval-expr` path already renders a global by name inside the running program. So -the machinery is there and the question is mostly one of presentation — its own section in the break buffer, folded like -the stack, or a separate buffer. - -The real question is **which** globals. A program has hundreds and a listing of all of them is the backtrace problem -again: the thing you want is buried by the thing you don't. - -**Direction: one Globals section, scoped to the stack.** Per-frame was considered and dropped. The reason is that a -global is not part of a frame — it is program state the frame happened to touch — so nesting it under one implies an -ownership that is not there, and the same name then appears once per frame that reads it. - -So: a section of its own, whose contents are the **union of the globals every frame on the current stack references**. -That keeps the compiler doing the choosing (the reference set per function is already known) without repeating anything, -and without falling back to listing all of a program's globals, which is the backtrace problem again. - -Two refinements that came out of the same conversation: - -- **Annotate each entry with which frames touch it.** This recovers what per-frame would have told you — "the whole - chain is reading this" reads differently from "only the innermost one is" — at no cost in duplication. -- **Order by the innermost frame that touches it.** A deep stack makes the union large again, and proximity to the - error is the ordering that puts the likely culprit on top. - -What this deliberately does not solve: a global that nothing on the stack names but that you still want visible — the -sand grid while stopped in a helper that never mentions it. That is watching, and it is item 10 rather than part of -this. - -## 2. The break buffer should appear by itself - -Today a condition stops the program and the buffer only opens when `C-c C-b` is typed. It should `pop-to-buffer` the -moment the program stops. - -What already exists: `flan-dev--absorb` inspects every reply for `:stopped`, and there is a poll for the case where no -reply is pending — the client already *knows* the moment it stops, and already updates the mode line from it. So this is -a hook at a point that exists, not new plumbing. - -To decide: whether it steals focus or only displays; whether it should also fire for a `(pause)`, which is a deliberate -stop and arguably always wants the window; and what happens when it stops while point is mid-edit in another buffer. - -## 3. What is `i` supposed to inspect in the stack section? +## 1. What is `i` supposed to inspect in the stack section? Raised as a question about intent, and it deserves one, because the current answer may be wrong rather than merely undocumented. @@ -67,7 +23,7 @@ frame-accurate and the inspector may not be. Options: root the inspector at the rooted; refuse `i` outside the innermost frame; or make the inspector frame-aware. Worth settling before anyone relies on it. -## 4. Annotating the IR and the disassembly with the source +## 2. Annotating the IR and the disassembly with the source The compiler should interleave the originating Flan expression into both the emitted LLVM IR and the disassembly listing. @@ -80,7 +36,7 @@ already; what is missing is the interleaving. The IR half is nearly free. The disassembly half is more interesting and more valuable — it is what would let you see what one line of Flan actually costs, including the indirection cell a dev build puts on every cross-function call. -## 5. `def`, `defvar`, `defconst` +## 3. `def`, `defvar`, `defconst` There is no `def`. There is `defvar` (mutable global) and `defconst` (compile-time constant, folded). @@ -93,7 +49,7 @@ accumulating. `defconst` under redefinition is the unclear one, as noted — it is folded into its use sites, so changing one is closer to a recompile than to an assignment. -## 6. Structural typing, row polymorphism, anonymous structs +## 4. Structural typing, row polymorphism, anonymous structs Worth noting before this is scoped: `types.ml` already does **structural equality** on resolved types, and a Flan struct is exactly its C layout with no header or tag word. So the representation is already structural; what is nominal is the @@ -104,7 +60,7 @@ spelling; how this interacts with the FFI, where the shim generates a C typedef with the planned managed classes, which are explicitly the *opposite* direction — identity and metadata rather than plain layout. -## 7. A performant JS transpiler +## 5. A performant JS transpiler Asked as a feasibility question. Bear in mind the web target already exists and ships real machine code via wasm, so this is not the only route to a browser and the case for it needs stating: smaller artifacts, no wasm toolchain, debuggability @@ -114,7 +70,7 @@ The hard parts are the ones the wasm target got for free from clang: the memory layout; JS is not), the FFI, and the fact that the whole raylib layer is C. A JS backend that cannot run raylib is a different product from the one that can. -## 8. C interop as seamless as Zig's +## 6. C interop as seamless as Zig's Today: `declare-c` names one C function per line, and the compiler generates the wrapper, the typedefs and the flattened declaration. 175 of them for raylib. **No header is ever read, deliberately** — which means nothing can check that a @@ -128,7 +84,7 @@ trades an explicit, checkable list for an implicit surface. Worth weighing again is that a binding is one line and the wrapper is generated. The auto-kebab-case question is separable and much smaller — and note the FFI currently keeps C's own spelling on purpose, so the mapping would need to be reversible. -## 9. Where `defclass` stands +## 7. Where `defclass` stands Specified in `plan.org` and **deliberately not started** — its own last line says nothing happens until ordinary `struct`, `Handle` and reload semantics work. Those have largely landed since that was written, so the gate may be closer @@ -151,7 +107,7 @@ Also open and related: whether a *condition* can be a class, which decides wheth `NEXT.md` records the argument; decision 4 there took the cheaper parent-link route for now and explicitly left real inheritance possible later. -## 10. Watching variables +## 8. Watching variables Raised while designing item 1, and deliberately separated from it. diff --git a/NEXT.md b/NEXT.md index f2b5687..5dff89c 100644 --- a/NEXT.md +++ b/NEXT.md @@ -318,6 +318,33 @@ algorithmic cleverness. **jank is not the model** — it is Clojure, so its maps sharing, which plan.org rules out by name because shared structure destroys the clear ownership that is the whole reason there is no collector. +## Decided in discussion, queued + +**Globals in the break buffer — one section, scoped to the stack.** Locals are readable; globals are not shown anywhere, +and in this language they are arguably the more useful half: a game keeps most of its state in top-level `defvar`s and +`sand.flan` holds its entire grid that way. + +Not per frame. A global is not part of a frame — it is program state the frame happened to touch — so nesting it under +one implies an ownership that is not there and repeats the name once per frame that reads it. Instead: its own section, +whose contents are the **union of the globals every frame on the current stack references**, which keeps the compiler +doing the choosing (the per-function reference set is already known) without listing all of a program's globals. + +Two refinements decided with it: **annotate each entry with which frames touch it**, which recovers what per-frame would +have told you at no cost in duplication; and **order by the innermost frame that touches it**, since a deep stack makes +the union large and proximity to the error is the ordering that puts the likely culprit on top. + +The daemon already has the pieces — `describe` returns the globals, `Session.render` walks a concrete type to a printed +value, and the `layout` op established that a qualified name is an identity the daemon can resolve. + +**The break buffer opens by itself when the program stops.** Today a condition stops the program and the buffer appears +only when `C-c C-b` is typed. `flan-dev--absorb` already inspects every reply for `:stopped` and a poll covers the case +where no reply is pending, so the client already knows the moment it happens and already moves the mode line from it — +this is a hook at a point that exists, not new plumbing. + +Three things to settle while building it: whether it takes focus or only displays; whether `(pause)` should always take +the window, being a deliberate stop rather than a failure; and what it does when the program stops while point is +mid-edit in another buffer. + ## Blocked and unfinished Everything below was found, decided or half-built and then stopped. Each says what blocks it. Nothing here is a diff --git a/sand.flan b/sand.flan index 12165a3..bd3fbce 100644 --- a/sand.flan +++ b/sand.flan @@ -101,6 +101,7 @@ ;; over a mutable scan position, which is what a while loop is. (defn settle [row i32 col i32] (let [vel (+ gravity (at velocity row col)) + some-point (rl/Vector2 {:x 15.0 :y 12}) y (min (- rows 1) (+ row (i32 vel)))] (while (> y row) (when (empty-at? y col) @@ -108,8 +109,8 @@ (return)) (let [left? (and (> col 0) (empty-at? y (- col 1))) right? (and (< col (- cols 1)) (empty-at? y (+ col 1)))] - (pause) (when (or left? right?) + (pause) (let [side (cond (not left?) 1 (not right?) -1