Every decision taken in the TODO review is recorded, and NEXT is a keyword the file declares
This commit is contained in:
parent
795ba945d9
commit
92d63e3b36
130
TODO.org
130
TODO.org
@ -1,5 +1,5 @@
|
||||
#+TITLE: Flan
|
||||
#+TODO: TODO WAIT | DONE CANCELLED
|
||||
#+TODO: TODO NEXT WAIT | DONE CANCELLED
|
||||
#+ARCHIVE: ::* Archive
|
||||
|
||||
Every decision, open question and known gap, one =**= heading each under the
|
||||
@ -137,7 +137,8 @@ big-endian bytes, which is how the hex literal reads. Two and not one with a
|
||||
wider operand because the intrinsic takes a single repeated byte: the byte fill
|
||||
is one instruction and the four-byte pattern is a loop on both backends.
|
||||
|
||||
** TODO There is no literal for an infinity or a NaN
|
||||
** NEXT There is no literal for an infinity or a NaN
|
||||
Decided 2026-09-25: four constants the compiler supplies, =f64-inf=, =f64-nan=, =f32-inf=, =f32-nan=, beside =f64-max= and the rest. Negative infinity is =(- f64-inf)=. Rules out Clojure's =##Inf= reader literal.
|
||||
=lib/reader.ml= has no literal for either, and =float_repr= prints =inf= and =nan=
|
||||
as words the reader will not read back. =(/ 1.0 0.0)= is the only route to an
|
||||
infinity, and the constant folder is integers only, so it cannot be a =defconst=.
|
||||
@ -328,6 +329,13 @@ a =defer= in one always registers. A loop body and a branch are still refused by
|
||||
name: =defer= is a compile-time construct with the cleanup copied into every exit
|
||||
path, so "maybe registered" is not expressible.
|
||||
|
||||
** TODO A return runs its defers before it computes its value
|
||||
=(return v)= is lowered as =Do (defers @ [Return v])= (=lib/check.ml= near 3474),
|
||||
so a defer that changes what =v= reads changes the answer, and =(return x)= and
|
||||
falling off the end with =x= disagree. All backends agree with each other. The
|
||||
value is computed first and the defers run after, the order Odin, Go and Zig
|
||||
use.
|
||||
|
||||
** DONE edn reads into a struct and answers a dynamic value
|
||||
CLOSED: [2026-09-17]
|
||||
Two projects, not one, and the dynamic half goes through an allocator rather than
|
||||
@ -362,17 +370,17 @@ expressible and a build-time refusal would be unusable. =barf= on web signals
|
||||
no-op — which is how a save file disappears with nothing said — and the
|
||||
build-time refusal.
|
||||
|
||||
** TODO Conditions get a parent link, not class inheritance
|
||||
** NEXT Conditions get a parent link, not class inheritance
|
||||
Decided 2026-09-25: build it, with a root =Error= every built-in error descends from, so one handler catches any error. A catch-all handler gets the condition's name and the runtime's sentence, not its fields. =(pause)= and warnings are not under =Error=.
|
||||
A condition type may name a parent where it is declared, and handler matching
|
||||
walks that static chain. It buys the hierarchy conditions most lack — a catch-all
|
||||
"any file error" handler — at compile-time cost only. Rules out the class answer:
|
||||
a class condition allocates at the signal site, inverts the lifetime rule, and
|
||||
lets a layout change under a standing handler frame. Not built.
|
||||
|
||||
** TODO Can a condition be a class?
|
||||
Unanswered, and the shape that probably wins is both — a struct condition stays
|
||||
what it is, a class condition allocates and matches by walking its class chain.
|
||||
Three costs, one serious: signalling would allocate on the failure path.
|
||||
** CANCELLED Can a condition be a class?
|
||||
CLOSED: [2026-09-25]
|
||||
A class condition allocates on the failure path. The parent link above gives the hierarchy without it.
|
||||
|
||||
** DONE handler-case
|
||||
CLOSED: [2026-09-19]
|
||||
@ -437,13 +445,15 @@ it. The alternative weighed — a per-binding declaration naming which argument
|
||||
carries the count — cannot reach a count that is a sibling field. No marker on the
|
||||
name; =ptr= is the marker, it owns nothing, and =free= refuses it.
|
||||
|
||||
** TODO A string cannot be returned from C
|
||||
** NEXT A string cannot be returned from C
|
||||
Decided 2026-09-25: copy the returned text into the context allocator at the boundary. =TextFormat= stays unbound, being variadic.
|
||||
A string crosses as a parameter only — a C function that returns one returns
|
||||
something Flan has no owner for. It is what makes =GetGamepadName= unbindable, and
|
||||
the same rule refuses =TextFormat=, which is variadic and so has no honest
|
||||
signature either.
|
||||
|
||||
** TODO Model, Mesh and FilePathList want a defstruct, and a callback wants the other direction
|
||||
** NEXT Model, Mesh and FilePathList want a defstruct, and a callback wants the other direction
|
||||
Decided 2026-09-25: =FilePathList= crosses by the same copy-at-the-boundary rule, in the lane with =Model= and =Mesh=. Callbacks wait until a program needs one.
|
||||
=Ray= and =BoundingBox= have their defstructs now. =FilePathList= is a =char**=
|
||||
and blocks the drop-files example; =Model= and =Mesh= are ordinary widening.
|
||||
Function-pointer parameters — =SetTraceLogCallback=, the audio stream processors —
|
||||
@ -479,7 +489,8 @@ an ordinary =defn= declares no environment and is byte-for-byte what it was. The
|
||||
static side does not pay for the dynamic side. Rejected names: Closure, Proc, Fun,
|
||||
Func, Fnptr.
|
||||
|
||||
** TODO Escaping closures, allocated on the GC side
|
||||
** NEXT Escaping closures, allocated on the GC side
|
||||
Decided 2026-09-25: start it. It must work on wasm32.
|
||||
The second half of "do both". What changes is where the environment points — a
|
||||
frame slot today, a collector allocation then — and the escape check goes away
|
||||
with it, along with the refusals on returning, storing, pointing at and pushing a
|
||||
@ -584,7 +595,8 @@ back to the call site. Costs about 2µs a call. Rules out putting a =loc= field
|
||||
on the wire, and rules out structural matching of the expansion against the
|
||||
arguments, which can pick the wrong one of two equal subtrees.
|
||||
|
||||
** TODO A declared name may carry the $ sigil
|
||||
** NEXT A declared name may carry the $ sigil
|
||||
Decided 2026-09-25: refuse =$= at the start of any declared name; the refusal says =$= marks a type variable.
|
||||
=(defn $foo [x i32] i32 ...)= is accepted and =($foo 3)= calls it; so is
|
||||
=(defstruct $S [a i32])=, whose type can then be written nowhere. The character is
|
||||
reserved in every type position and in no name. Refusing it in a declared name
|
||||
@ -629,7 +641,8 @@ A machine-type target needs =numeric?=; an enum target needs =integer?=;
|
||||
by what it claims, not by the set it happens to denote this week — which is why
|
||||
=ordered?= is refused even though every type it admits today converts.
|
||||
|
||||
** TODO There is now no generic enum to integer conversion
|
||||
** NEXT There is now no generic enum to integer conversion
|
||||
Decided 2026-09-25: build =enum?= as described.
|
||||
Recorded as a loss. The one spelling that worked did so by not asking about the
|
||||
operand at all, so removing it was still right. =enum?= is the eventual answer —
|
||||
it would entail =ordered?= and =equal?= and not =numeric?=, so the cast rule
|
||||
@ -695,7 +708,8 @@ depth it gave up at. The bare depth number is a backstop that also prints the
|
||||
chain. Before any of it, the compiler hung rather than failed, which wedges =C-c
|
||||
C-c= with nothing to show.
|
||||
|
||||
** TODO Generic types
|
||||
** NEXT Generic types
|
||||
Decided 2026-09-25: the freeze is lifted for this; build both type and length parameters.
|
||||
=(defstruct Pair [a $t b $t])= cannot be spelled, and neither can a length
|
||||
parameter. =Types.Named= is a bare string with no room for parameters; giving it
|
||||
some changes the type, the layout calculator, both backends, the renderer and the
|
||||
@ -704,7 +718,8 @@ not started — it is a language feature under a freeze, and it was stopped once
|
||||
already for that reason. The motivating case is Odin's =Small_Array=: a
|
||||
fixed-capacity array with a count and no allocation.
|
||||
|
||||
** TODO A value predicate over a length parameter
|
||||
** WAIT A value predicate over a length parameter
|
||||
Decided 2026-09-25: waits until a program wants one.
|
||||
Odin's =where N >= 0= is a predicate over a value, not a type, and a =where=
|
||||
clause here admits nothing but type predicates. Whether it should take value
|
||||
predicates over a length parameter deserves answering deliberately rather than
|
||||
@ -797,7 +812,8 @@ CLOSED: [2026-09-20]
|
||||
typed conditions stay strict =bool=. =and= and =or= hand back the operand that
|
||||
decided them, Clojure's rule, through a desugaring that evaluates each test once.
|
||||
|
||||
** TODO A bool arm and a dyn arm joining as dyn
|
||||
** NEXT A bool arm and a dyn arm joining as dyn
|
||||
Decided 2026-09-25: they join as =dyn=, the =bool= boxed — Clojure's rule, so =(or false (box "s"))= answers ="s"=.
|
||||
With both arms of a desugared =and=/=or= holding real values, a non-bool =dyn= on
|
||||
the losing side meets the strict =bool= boundary and traps —
|
||||
=(or false (box "s"))= is the case. Whether a =bool= arm and a =dyn= arm should
|
||||
@ -867,7 +883,8 @@ ordinary expressions and the builtin reads the type back out of one
|
||||
type an expression cannot hold, such as =(Fn [i32] ())=, is parsed as
|
||||
=Ast.TypeArg=. Rules out a type expression anywhere else in expression position.
|
||||
|
||||
** TODO An array literal cannot say it is [f32]
|
||||
** NEXT An array literal cannot say it is [f32]
|
||||
Decided 2026-09-25: the first element's type carries to the rest, so =[(f32 1.0) 2.5]= is an =[f32]=; that is refused today and is a bug. No =1.0f= suffix for now.
|
||||
A float literal defaults to =f64=, an array literal has no context, and a =let=
|
||||
has no annotation. Same shape as =(vec-new [u8])= and probably the same fix.
|
||||
Not the same fix: a bracket literal has no argument to put a type in. Decision:
|
||||
@ -882,13 +899,15 @@ blocker it was, since =(array 4 T)= answers the case that raised it. plan.org's
|
||||
rule is "annotate function signatures, infer locals", so a general annotation is a
|
||||
deliberate absence.
|
||||
|
||||
** TODO A read-only slice type
|
||||
** NEXT A read-only slice type
|
||||
Decided 2026-09-25: =[const u8]=, Zig's spelling in Flan's brackets. =bytes-view= answers one and a =set= through it is a compile error; a =[T]= converts to =[const T]= and not back, and the prelude's read-only functions take it. =const= is reserved as a name, since =[n T]= accepts a constant's name for =n=.
|
||||
=bytes-view= is read-only by convention only — the type system cannot say a =[u8]=
|
||||
may not be stored through, so a trap on read-only memory is the enforcement. A
|
||||
read-only slice type, or provenance, is what would move that refusal to compile
|
||||
time.
|
||||
|
||||
** TODO Writing through a string literal
|
||||
** NEXT Writing through a string literal
|
||||
Decided 2026-09-25: closed by the read-only slice type above.
|
||||
=(let [s (bytes-view "Hi")] (set (at s 0) \h))= stores into read-only memory at
|
||||
=-O0= and is deleted as undefined at =-O2= — same source, and which way it fails
|
||||
depends on a flag. Narrowed when =(bytes s)= started copying, so the common
|
||||
@ -966,7 +985,8 @@ at all, which is what the diagnosis predicted. A =map= that *changes* the elemen
|
||||
type is the one shape that did not come with them: one copy per ordered pair of
|
||||
types rather than per type.
|
||||
|
||||
** TODO CFn in a struct or a fixed array
|
||||
** NEXT CFn in a struct or a fixed array
|
||||
Decided 2026-09-25: allowed. A call through a null =CFn= is a named runtime condition on both backends, and parks in a dev build.
|
||||
A zeroed function value is a null pointer, so a function value is refused in any
|
||||
position zero-initialisation would conjure one — =CFn= included. An =(Option
|
||||
(CFn ...))= field is already legal. A table of function pointers is exactly what
|
||||
@ -1030,11 +1050,9 @@ compared as a constant rather than as a lowering. That is how the float =%= gap
|
||||
survived. Two things would close it: an =-O0= pass of the sweep, and something
|
||||
that walks the two backends' primitive match arms mechanically. Neither is queued.
|
||||
|
||||
** TODO (uninit) and unreachable differ between the backends, and the language has not said what they mean
|
||||
=(uninit)= is stable garbage — whatever the stack slot held — rather than
|
||||
=poison=, and an exhausted match is =ud2=, a defined SIGILL, rather than undefined
|
||||
behaviour. Both are deliberate and both are now written down, but the *language*
|
||||
still has not defined what reading an uninitialised value means, which is the item.
|
||||
** DONE Reading (uninit) before writing it is undefined behaviour
|
||||
CLOSED: [2026-09-25]
|
||||
Reading an =(uninit)= value before writing it is undefined behaviour, and the backends may differ on it. An exhausted match stays =ud2= on x86.
|
||||
|
||||
** DONE f64 to i64 out of range, and INT64_MIN / -1
|
||||
CLOSED: [2026-09-14]
|
||||
@ -1067,6 +1085,10 @@ build included, reaches =emit_globals_init= as =Emit.startup_plan='s
|
||||
straight into their symbol are =Tast.const_init= ones, which neither transfer
|
||||
nor read anything. Nothing to change.
|
||||
|
||||
** TODO A u64 converted to f64 is signed on x86
|
||||
=(f64 (u64 18446744073709551615))= prints =1.84467e+19= under LLVM and =-1= under
|
||||
=--x86=, with a literal or a run-time =u64= alike.
|
||||
|
||||
** DONE An aggregate built in place never reads its own destination
|
||||
CLOSED: [2026-09-25]
|
||||
=assign= builds in place only when the right-hand side settles *and* reads no
|
||||
@ -1093,7 +1115,8 @@ so a =declare-c= wrapper leaning on the courtesy is already backend-dependent as
|
||||
well as slice-dependent. The contract is pointer and length, and nothing promised
|
||||
otherwise.
|
||||
|
||||
** TODO Frame descriptions are gated on --debug
|
||||
** NEXT Frame descriptions are gated on --debug
|
||||
Decided 2026-09-25: emit the x86 backend's =.cfi= directives in every build. No runtime cost; the lane measures the =.eh_frame= size it adds.
|
||||
They are correct in every build and free at runtime, and a release build is where
|
||||
a crash would most want them. One =if= in three places.
|
||||
|
||||
@ -1103,11 +1126,9 @@ outer one. The disambiguating suffix makes both visible, which is not the same a
|
||||
making the answer right. It needs block structure the typed IR does not carry, and
|
||||
the variable declarations moved out of the entry block.
|
||||
|
||||
** TODO UBSan sees no Flan code, and no flag changes that
|
||||
UBSan's checks are branches clang's C frontend emits inline, not a pass, so shift
|
||||
undefined behaviour, alignment and a NaN float-to-int cast are unreached. Either
|
||||
the emitter grows those checks behind the flag — a compiler feature of the same
|
||||
shape as the bounds checks — or they belong to the checker. Not decided.
|
||||
** CANCELLED UBSan sees no Flan code, and no flag changes that
|
||||
CLOSED: [2026-09-25]
|
||||
The language defines the cases UBSan would catch itself: a computed shift count is masked, and a float-to-int cast out of range or of a NaN signals =ArithError=. Flan code produces no misaligned access.
|
||||
|
||||
** DONE A JS backend is a dialect, not a second machine
|
||||
CLOSED: [2026-09-17]
|
||||
@ -1374,7 +1395,8 @@ Hiding it means a new field in the frame layout written out in both backends and
|
||||
the runtime. Choosing it is refused loudly rather than answered wrongly, so this is
|
||||
cosmetic.
|
||||
|
||||
** TODO A formatted number does not outlive its frame
|
||||
** NEXT A formatted number does not outlive its frame
|
||||
Decided 2026-09-25: the conversion's bytes are always copied into the context allocator, so the string outlives the frame. Rules out refusing the escape, which needs flow tracking.
|
||||
The conversion buffer is one frame slot per call site, so returning a string built
|
||||
from it returns a view of storage the return has just released, and pushing one
|
||||
pushes an element aliasing that slot. Neither shape is refused. Copy the bytes for
|
||||
@ -1392,7 +1414,8 @@ The opposite of what the escaping-alloca argument predicts, and the measurement
|
||||
that first said otherwise was comparing a 40-frame binary with a 600-frame one.
|
||||
That is why every number in =docs/BUILT.md= is a minimum of nine runs.
|
||||
|
||||
** TODO runtime/flan_dyn_stub.c is dead
|
||||
** NEXT runtime/flan_dyn_stub.c is dead
|
||||
Decided 2026-09-25: delete it, as part of a sweep for dead code across the repository, each removal checked unused first.
|
||||
No dune rule mentions it, no module refers to it, no test links it, and it does
|
||||
not compile — two conflicting-type errors against its own header. It is maintained
|
||||
by accident: one lane added a function to it, which is duplicity on the same side
|
||||
@ -1536,7 +1559,8 @@ only by an explicit call. A sentence about the shape of the gate rather than an
|
||||
observed problem: only the daemon sets the variable and it never runs release
|
||||
builds. The fix, if it is ever felt, is a narrower gate.
|
||||
|
||||
** TODO The daemon's "has not called (agent/start ...)" note is unreachable
|
||||
** NEXT The daemon's "has not called (agent/start ...)" note is unreachable
|
||||
Decided 2026-09-25: retire the note in the same dead-code sweep.
|
||||
Unreachable, not merely unexercised: the one state it was true of is closed by the
|
||||
constructor. Retiring it is the author's call over a lane that merged days ago, so
|
||||
it is left in place saying a true thing about a state nothing can be in.
|
||||
@ -1598,14 +1622,14 @@ Settled for conditions and for structs, because =Load= qualifies every declarati
|
||||
at import. Still open for locals, where the debug information gives a bare name and
|
||||
nothing qualifies it.
|
||||
|
||||
** TODO The render-thunk-per-inspection design
|
||||
** NEXT The render-thunk-per-inspection design
|
||||
Decided 2026-09-25: the inspector reads a value through the type layouts the compiler records, with no compile per inspection, which lets it hold a value.
|
||||
An inspection still compiles a thunk per request. A redesign rather than a
|
||||
deletion, and its own lane: it is what unblocks the inspector retaining a value.
|
||||
|
||||
** TODO The watch design is reopened
|
||||
Push was chosen partly because polling costs a compile, and that premise weakened
|
||||
when the processes merged. The table-and-read design stands; whether it should stay
|
||||
pushed is open.
|
||||
** DONE The watch table stays pushed
|
||||
CLOSED: [2026-09-25]
|
||||
The watch table stays pushed, and shares the push channel program output moves to.
|
||||
|
||||
** DONE A watch over a struct or a slice
|
||||
CLOSED: [2026-09-25]
|
||||
@ -1630,7 +1654,8 @@ Only instantiations reach the function list, so a generic name used to report th
|
||||
it had installed nothing at all. The session expands to instantiations before
|
||||
reporting.
|
||||
|
||||
** TODO Signature generations and stale-caller warnings
|
||||
** NEXT Signature generations and stale-caller warnings
|
||||
Decided 2026-09-25: SBCL's behaviour: the new definition always installs, the stale callers are listed by file and line, and a stale call stops in the break buffer naming its site. The check is a signature word the cell carries, compared at each dev-build call site; a release build has neither. Per-block control of runtime checks (Zig's =@setRuntimeSafety=, Odin's =#no_bounds_check=) is a separate question, not built.
|
||||
The biggest hole in "you never restart the program". A changed signature is
|
||||
refused outright today and that is a placeholder, not the design. It needs function
|
||||
versions, a trampoline per version, and caller tracking good enough to name the
|
||||
@ -1814,7 +1839,8 @@ specification's own branch — the flag is the command and the printed shape is
|
||||
error pattern, so anyone who wants one has the four lines, and the manual carries
|
||||
them.
|
||||
|
||||
** TODO defclass slots take types, checked on write
|
||||
** NEXT defclass slots take types, checked on write
|
||||
Decided 2026-09-25: slots are name/type pairs checked on write; an untyped slot stays legal and holds any =dyn=. A migration keeps a stored value that no longer fits the new type, warns once, and the next write is checked. One lane with the =set= entry below.
|
||||
=(defclass State [pause bool step bool])= reads as four untyped slots and
|
||||
reports a duplicate =bool=. Wanted: the slot list is name/type pairs, as CLOS
|
||||
does it. The type is a declaration about the values and not a layout — an
|
||||
@ -1826,14 +1852,16 @@ Open: what migration does with a stored value that no longer fits a changed
|
||||
slot type, and whether an untyped slot stays legal (it should — =dyn= is a type
|
||||
and writing nothing should mean it).
|
||||
|
||||
** TODO println takes up to a second to appear
|
||||
** NEXT println takes up to a second to appear
|
||||
Decided 2026-09-25: the daemon pushes program output on the editor's connection as it is written. Rules out a faster poll.
|
||||
Output is drained by =flan--poll= at =flan-poll-interval=, 1.0s
|
||||
(=emacs/flan.el:541=). A reply carries whatever was buffered when it was
|
||||
composed, so anything the program prints after that waits for the next tick.
|
||||
Polling faster costs a request a second for nothing most of the time; the
|
||||
daemon pushing on its own connection is the other shape. Decide which.
|
||||
|
||||
** TODO set writes a class slot; put is for maps
|
||||
** NEXT set writes a class slot; put is for maps
|
||||
Decided 2026-09-25: as written; one lane with typed slots.
|
||||
=put= exists because an absent map key has no location to store into, which is
|
||||
why =(get m k)= is refused as a place (=lib/parse.ml:1159=). A class instance is
|
||||
not in that situation: its slots are fixed by the =defclass=, so a declared slot
|
||||
@ -1842,7 +1870,8 @@ always exists and =(set (get state :pause) true)= is a field store like
|
||||
insertion is real. Writing an undeclared slot through =set= is then a refusal
|
||||
naming the class.
|
||||
|
||||
** TODO update: change a place by applying a function to it
|
||||
** NEXT update: change a place by applying a function to it
|
||||
Decided 2026-09-25: every place evaluates each of its subexpressions once, C's compound-assignment rule, which also fixes =++= and =--=; =update= is built on that. Rules out refusing side effects in a place.
|
||||
=(set (.velocity g) (inc (.velocity g)))= names the place twice. Clojure's
|
||||
=update= would be a macro over the same two steps, for a struct field and a
|
||||
class slot alike.
|
||||
@ -1880,7 +1909,14 @@ motion states in that mode; every other key, including what =special-mode-map=
|
||||
binds, stays Evil's. The other special-mode buffers (inspect, watch, doc, disassembly,
|
||||
diagnostics, lower) have the same exposure and are not changed.
|
||||
|
||||
** TODO Eval in the frame, from the break loop
|
||||
** NEXT Evil takes the keys in the other Flan buffers
|
||||
The inspect, watch, doc, disassembly, diagnostics and lower buffers get the break
|
||||
buffer's treatment: the keys each binds itself go to Evil's normal and motion
|
||||
states, and every other key stays Evil's. Bindings stay as close as possible
|
||||
between Evil and Emacs states.
|
||||
|
||||
** NEXT Eval in the frame, from the break loop
|
||||
Decided 2026-09-25: SLIME's eval-in-frame, as described.
|
||||
An expression is evaluated at a frame boundary, so it sees globals and not the
|
||||
stopped frame's locals — which are the values anyone stopped there wants. Wants
|
||||
SLIME's eval-in-frame: pick a frame, and the expression is checked and run with
|
||||
@ -1896,7 +1932,8 @@ because =locals= and the inspector are asked by it. The innermost frame is
|
||||
shown even when it is the prelude's, unless the stop is =(pause)=, because it
|
||||
is where the program stopped. Rules out renumbering the visible frames.
|
||||
|
||||
** TODO There is no stepper
|
||||
** NEXT There is no stepper
|
||||
Decided 2026-09-25: stepping happens inside a stopped frame, so the game loop and its clock are frozen, as under =(pause)=.
|
||||
=(pause)= stops and offers restarts, frames, locals and the inspector, but
|
||||
nothing advances a form at a time. CIDER instruments a form and steps the
|
||||
instrumented copy; the equivalent here is a dev-build-only instrumented
|
||||
@ -1948,7 +1985,8 @@ daemon asks for the sink off (=lib/loc.ml:185=) and gets one exception, so a
|
||||
function with three bad expressions takes three round trips. The sink is
|
||||
per-phase; making it per-form would need a resync point inside a body.
|
||||
|
||||
** TODO A session should start before a program compiles
|
||||
** NEXT A session should start before a program compiles
|
||||
Decided 2026-09-25: SBCL/nREPL order: an empty host session, then a load-file op. Re-running with no =main= says there is none; a half-loaded file keeps what compiled and lists the errors. =C-c C-k= is load-file and the inspector moves to =C-c M-i=, CIDER's key.
|
||||
=flan dev= builds the program first, so a =main= that does not compile gives no
|
||||
session. Want SBCL/nREPL order: empty image, then load a file into it. Needs a
|
||||
host built with no user program, and a load-file op — the reload machinery
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user