Two questions became decisions, so they move to what is left
This commit is contained in:
parent
244f83fe53
commit
f4f61ae8c9
60
DISCUSS.md
60
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.
|
||||
|
||||
|
||||
27
NEXT.md
27
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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user