diff --git a/DISCUSS.md b/DISCUSS.md new file mode 100644 index 0000000..760abb9 --- /dev/null +++ b/DISCUSS.md @@ -0,0 +1,134 @@ +# Let's discuss + +Open questions, raised and deliberately not answered yet. Nothing here is a decision or a task. Each entry is the +question as asked, plus what is already known in this repo that bears on it — so the investigation starts from what +exists rather than from scratch. + +Settled decisions live in `NEXT.md`. Reasons for what already exists live in `BUILT.md`. + +--- + +## 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. Worth considering: only globals the stopped function +mentions, which the compiler knows; or a filter; or a pinned watch list. + +## 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? + +Raised as a question about intent, and it deserves one, because the current answer may be wrong rather than merely +undocumented. + +`i` on a local passes that local's **name** to the inspector, which evaluates it as an expression inside the running +program. That is correct for the innermost frame, where the name is in scope. **It is not obviously correct for any +other frame** — evaluating `y` while looking at frame 3 evaluates `y` wherever the evaluator stands, not in frame 3. +It may resolve to a global, to a different binding, or fail. + +The locals *listing* does not have this problem: it renders from the frame's own slot addresses. So the display is +frame-accurate and the inspector may not be. Options: root the inspector at the slot's address the way the listing is +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 + +The compiler should interleave the originating Flan expression into both the emitted LLVM IR and the disassembly +listing. + +What already exists: `emit.ml` writes `.ll` as text, so a comment costs nothing and cannot break anything. Every typed IR +node carries a `Loc.t`. DWARF is emitted under `--debug`, with a line table naming the `.flan` file, and the disassembly +listing already rebases addresses and annotates cells, calls and branch targets. So both halves have the information +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` + +There is no `def`. There is `defvar` (mutable global) and `defconst` (compile-time constant, folded). + +The proposal: a `def` that is mutable, where `defvar` only rewrites the value if the variable is *new* — Common Lisp's +actual `defvar` semantics, where re-evaluating a `defvar` deliberately does not clobber a value you have been building up +at runtime. That distinction matters much more here than in most languages, because `C-c C-k` on a whole buffer +re-evaluates every top-level form against a *running* program, and today that resets state you may have spent a session +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 + +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 +*checking*, not the data. + +The questions this opens: whether a function can take "any struct with an `x` and a `y`"; whether anonymous structs get a +spelling; how this interacts with the FFI, where the shim generates a C typedef per named struct; and how it interacts +with the planned managed classes, which are explicitly the *opposite* direction — identity and metadata rather than plain +layout. + +## 7. 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 +in browser devtools, or something else. + +The hard parts are the ones the wasm target got for free from clang: the memory model (Flan is pointers and explicit +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 + +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 +declaration matches the real signature, and that is written down as trusted rather than guaranteed. + +The proposal is to read the header, prefix a namespace, and get `rl/InitWindow` for free — plus possibly automatic +kebab-casing to `rl/init-window`. + +This is a large change in kind, not just in size: it means a C parser or a libclang dependency in the build, and it +trades an explicit, checkable list for an implicit surface. Worth weighing against what `declare-c` already buys, which +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 + +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 +than the document assumes. + +What it is meant to add: identity, runtime shape metadata, an implementation-defined representation, generic-function +dispatch, and live schema change with an explicit migration at a frame boundary — which is the answer to the one thing +redefinition still cannot do, changing a struct's layout while instances exist. + +Three findings from an earlier review, recorded in `NEXT.md` and not yet in `plan.org`: + +- **A generic function is a cell.** Adding a method from a later module is the same problem indirection cells already + solve, so the expensive half is built and tested. +- **The pool is not one storage option among three.** `migrate-instances` has to enumerate live instances, which a pool + behind a generational `Handle` gives by construction and the other two options do not. +- **Every layout version must stay resolvable** for as long as any instance holds it — the same rule as "nothing is ever + `dlclose`d". + +Also open and related: whether a *condition* can be a class, which decides whether handler matching has one path or two. +`NEXT.md` records the argument; decision 4 there took the cheaper parent-link route for now and explicitly left real +inheritance possible later. diff --git a/NEXT.md b/NEXT.md index f5eb2fe..f2b5687 100644 --- a/NEXT.md +++ b/NEXT.md @@ -4,6 +4,10 @@ **Branch `dev-loop`, 199 commits, working tree clean, `dune test` green.** +**[`DISCUSS.md`](DISCUSS.md) is what has been *asked* and not answered** — open questions with the repo context that +bears on each, so an investigation starts from what exists. Nothing in it is a decision or a task; when one becomes +either, it moves here. + **`NEXT.md` is what is left. [`BUILT.md`](BUILT.md) is why the existing parts are the shape they are** — the reload primitive, cells, the agent, the session, the daemon, the Emacs client, conditions, the FFI shim, the layout, and the order it all got built in. This file was half build log until it was split; do not let it become one again. When a