526 lines
30 KiB
Org Mode
526 lines
30 KiB
Org Mode
* Additional things
|
|
|
|
** flan dev blocks 4-5s before first eval when the program doesn't call agent/start
|
|
"ready on sock" prints in [merged_setup] before [accept_loop] ever runs.
|
|
[merged_serve] (lib/dev.ml:4417) waits up to 10s (await ~ms:10000, line 4427)
|
|
for agent.sock to exist before starting accept_loop. The listen socket is
|
|
already up, so Emacs connects fine, but nothing answers until that wait
|
|
finishes/times out. So a program with no (agent/start ...) looks "ready" but
|
|
actually stalls the first request for up to 10s.
|
|
|
|
Maybe accept_loop should start immediately and the agent-socket wait should
|
|
happen concurrently / only gate agent-specific requests?
|
|
|
|
Update: happening a lot with sand.flan, not just once. sand.flan calls
|
|
agent/start (sand.flan:125) but only after rl/init-window (line 122)
|
|
returns, so if window setup is slow the 10s wait in merged_serve lapses
|
|
before agent/start ever runs — and since accept_loop is gated behind that
|
|
wait, this isn't just a noisy log line, it's a real Emacs stall every time.
|
|
Reinforces: fix should be starting accept_loop unconditionally rather than
|
|
tuning the timeout.
|
|
|
|
Update: likely explains C-M-i (completion-at-point) and eldoc both breaking
|
|
at once. Both read flan--defs (emacs/flan.el:1511), refreshed only on
|
|
connect and after an accepted eval, always through `ignore-errors`
|
|
(flan.el:592, 1743, 1841, 2333). If a refresh lands during the accept_loop
|
|
stall it just silently fails, leaving flan--defs empty/stale with nothing
|
|
retrying it — killing both features with no visible error. Same underlying
|
|
fix (start accept_loop unconditionally) should resolve this too.
|
|
|
|
Update: also probably explains "redefined game-draw via C-c C-c but raylib
|
|
never updates" — deliver (lib/dev.ml:159, called at line 764) pushes the new
|
|
module over the same agent connection. A stale/half-set-up connection from
|
|
the accept_loop race could report "ok" on install without the game thread's
|
|
agent/poll ever actually picking it up at the next frame boundary. Quick
|
|
workaround: M-x flan-restart-program for a fresh connection, then redefine
|
|
again. Real fix is still the accept_loop one.
|
|
|
|
** add // for forced truncating division?
|
|
Lexically free (comments are `;`, not `/`). But nothing is actually missing:
|
|
`/` on two ints already truncates toward zero (sdiv/udiv, lib/emit.ml:2529),
|
|
and casting a float division truncates toward zero too ((i32 (/ 7.0 2.0)) ->
|
|
fptosi -> 3, lib/emit.ml:2909). So `//` as sugar for that is trivial.
|
|
|
|
Catch: Python's `//` is not toward-zero truncation, it's *floor* division —
|
|
rounds toward -infinity. (-7 // 2) is -4 in Python, but sdiv/fptosi give -3.
|
|
So "Python-like //" and "forced truncation" are two different asks.
|
|
|
|
Resolved: Joe wants the toward-zero-truncation one, for when a float
|
|
division needs an int result. (int (/ a b)) already does this — no new
|
|
operator needed. Not adding //.
|
|
|
|
** "generic code over type variable X" is a bad error for a plain typo
|
|
Hit with `int` before defining the defalias for it. resolve_name
|
|
(lib/check.ml:776-778) treats any unrecognized lowercase name as a type
|
|
variable by default; near_miss (lib/check.ml:670-698, single-edit-distance
|
|
only) is what rescues real typos into "unknown type X — did you mean Y?"
|
|
(line 770-772). `int` -> `i32` is 2 substitutions, outside that net, so it
|
|
falls into "generic code over the type variable int is not implemented yet —
|
|
milestone 5" instead of a plain "unknown type int".
|
|
|
|
Common near-misses (int, float, double, str, ...) all get this confusing
|
|
message rather than "unknown type". Maybe near_miss should also check a
|
|
short hardcoded list of common non-flan spellings (int/float/double/bool/str
|
|
etc. from other languages) regardless of edit distance.
|
|
|
|
** eval result ghost text position/minibuffer, both deliberate
|
|
Both things I want changed are documented decisions, not bugs:
|
|
- Position is "end of the form sent" (flan.el:1822-1825), not end of line —
|
|
deliberately different from flan-watch's end-of-line ghost text so the two
|
|
don't get confused (flan.el:1381-1389).
|
|
- Overlay and minibuffer echo are mutually exclusive on purpose
|
|
(flan-inline-result docstring, flan.el:91-101): "saying the same number
|
|
twice is how a reader learns to stop reading both."
|
|
|
|
I want it at end of line + always also in the minibuffer. Revisit —
|
|
would mean changing flan--report's `at` computation and the shown/echo
|
|
either-or in flan.el:1847-1848.
|
|
|
|
** "unknown name u8" from (defconst grid [rows [cols u8]]) — not a real i8/u8 bug
|
|
i8/u8 etc. all exist fine (Types.ikind_of_name, lib/types.ml:72). Real issue:
|
|
defconst's 2-arg form (defconst name value) has no type slot — the second
|
|
form is always parsed as a VALUE via expr, never as a type (lib/parse.ml:
|
|
1341-1345). So [rows [cols u8]] became an array *literal*, and `u8` inside
|
|
it read as a bare variable reference, which is genuinely unbound as a value
|
|
-> "unknown name u8".
|
|
|
|
defconst has no "type only, no value" form; that's defvar's job. Fix: use
|
|
defvar instead — (defvar grid [rows [cols u8]]) is defvar's 2-arg
|
|
(name Type) form (lib/parse.ml:1333-1335), zero-initialized, and parses the
|
|
bracket correctly as a type via texpr. Fits anyway since grid is mutable.
|
|
|
|
** (when test) with no body should work like (when test (do))
|
|
lib/parse.ml:305-309: `when` is a hardcoded special form (not a macro yet —
|
|
"until macros land at milestone 5") and requires `body <> []`, else fails
|
|
"when is (when test body ...)". No real reason for the restriction — empty
|
|
body could just become Ast.Do [], same as (do) already means. One-line fix
|
|
(drop the `body <> []` guard) whenever this file gets touched.
|
|
|
|
** bare struct literal {.field v} can't infer its type from context (e.g. defn return type)
|
|
`(defn get-mouse-cell [] Cell ... {.row r .col c})` fails to parse — "a bare
|
|
map is not an expression; write (Type {.field v})". Root cause: this refusal
|
|
is in `expr` at PARSE time (lib/parse.ml:274-276), before type-checking ever
|
|
runs, so it can't see that the enclosing defn's return type is Cell. check.ml
|
|
does support expected-type-driven checking elsewhere (`check ~want:ty`), but
|
|
a bare struct literal never reaches it — parse rejects it first.
|
|
|
|
To make this work, the refusal would need to move from parse into check, so
|
|
it can consult a `want` type (return type, let annotation, etc.) and only
|
|
fail when there truly isn't one available. Real feature, not a small change.
|
|
|
|
** struct destructuring shorthand: {.row .col} binding same-named locals
|
|
Only two struct-pattern forms exist: {:keys [x y]} (lib/parse.ml:729-753) and
|
|
{name .field} pairs (line 759-762) — both spell the field name twice ({:keys
|
|
[x y]}) or need name+dot per field. :keys makes sense for dyn maps (keys
|
|
aren't statically known field names), but a struct's fields are typed and
|
|
known, so a bare {.row .col} that just binds `row`/`col` locals directly
|
|
would read better and is the common case.
|
|
|
|
Would be a new match arm in `dmap` (lib/parse.ml:724-778) for a lone .field
|
|
symbol with no name before it. No obvious grammar collision — nothing
|
|
currently matches a bare .field symbol on its own. Not implemented.
|
|
|
|
** C-c C-i should auto-inspect at point, C-u C-c C-i for the minibuffer
|
|
Feature request. Today flan-inspect (emacs/flan-inspect.el:681-696) always
|
|
prompts via read-string, pre-filled with the sexp before point — no
|
|
current-prefix-arg handling anywhere in the file, and emacs/MANUAL.md:369
|
|
documents no C-u variant either. Not sure if I'm misremembering discussing
|
|
this for flan-inspect specifically or some other command — check that too
|
|
if it comes up again. Wanted: plain C-c C-i inspects the expression at point
|
|
with no prompt; C-u C-c C-i opens the minibuffer to type a different one.
|
|
|
|
** inc/dec (pure) and ++/-- (mutating) — doable as user macros, no compiler change
|
|
No inc/dec/1+/++/-- exist anywhere (prelude or special forms) — (+ x 1) is
|
|
the only spelling today.
|
|
|
|
Unlike most items above, this needs no compiler change: defmacro already
|
|
exists at user level (vendor/raylib's with-drawing etc.), and `place` (what
|
|
`set` parses — lib/parse.ml:961-983: vars, .field, at, deref) is already
|
|
general enough. Can write today:
|
|
|
|
(defmacro inc [args] `(+ ~(at args 0) 1))
|
|
(defmacro dec [args] `(- ~(at args 0) 1))
|
|
(defmacro ++ [args] `(set ~(at args 0) (+ ~(at args 0) 1)))
|
|
(defmacro -- [args] `(set ~(at args 0) (- ~(at args 0) 1)))
|
|
|
|
inc/dec are generic for free since +/- already are. ++/-- reuse the place
|
|
twice (read then write) — fine for a plain var, double-evaluates a place
|
|
like (at arr (compute-index)) if the index has side effects. Same
|
|
non-hygienic-macro tradeoff with-drawing/with-mode-2d already accept.
|
|
Maybe worth putting these in the prelude itself rather than per-project.
|
|
|
|
** (Cell 1 2) as a positional struct constructor
|
|
Designated initializers (C/Odin-style) already exist: {.field v ...} + ZII
|
|
zero-fill for omitted fields (zii_fill, lib/check.ml:3814), any order, {}
|
|
fully zeroed. Not missing — just spelled with braces/dots.
|
|
|
|
Positional (Cell 1 2) genuinely doesn't exist, and can't be added as a
|
|
parser special case the way {.field ...} was: struct-literal recognition
|
|
happens at PARSE time purely by syntax shape (Sym name applied to a
|
|
{.field...}-shaped map, lib/parse.ml:593-600) — the parser has no symbol
|
|
table, so (Cell 1 2) is syntactically identical to any function call
|
|
(parse.ml:602-603 Ast.Call). Would need check.ml to notice an Ast.Call's
|
|
head resolves to a struct type rather than a function, and reinterpret the
|
|
positional args in field-declaration order there instead. Same parse/check
|
|
boundary issue as the bare-struct-literal-return-type item above — recurring
|
|
pattern worth keeping in mind for future struct/type ergonomics asks.
|
|
|
|
** a DEADBEEF-style sentinel-fill builtin, instead of zero
|
|
Idea: a debug memset that writes a real byte pattern instead of the zero
|
|
llvm.memset already uses (lib/emit.ml:934), for catching reads of
|
|
uninitialized memory. Distinct from Tast.Uninit, which compiles to LLVM
|
|
`poison` (lib/emit.ml:1613) — a semantic "don't care" marker, not an actual
|
|
inspectable byte pattern.
|
|
|
|
Technical snag: llvm.memset only takes a single repeated i8, so a real 4-byte
|
|
0xDEADBEEF pattern needs a fill loop, not memset — more expensive. This is
|
|
why MSVC/glibc-style debug allocators use a single repeated byte instead
|
|
(0xCC/0xCD/0xDD/0xFD). Need to decide: cheap single-byte sentinel, or a real
|
|
4-byte pattern via a loop. Name candidates:
|
|
- single-byte (memset-cheap): 0xCC (MSVC uninit stack), 0xCD (MSVC "Clean"
|
|
unwritten heap), 0xDD (MSVC "Dead" freed heap), 0xFD (MSVC fence/guard)
|
|
- 4-byte (loop, reads great in a hex dump): 0xDEADBEEF, 0xBAADF00D (Windows
|
|
LocalAlloc uninit), 0xFEEEFEEE (Windows HeapFree'd), 0xDEADC0DE, 0xC0FFEE,
|
|
0x8BADF00D (Apple watchdog-timeout crash code)
|
|
|
|
Scope: should take a struct or an array (by place/pointer) and fill it with
|
|
the sentinel — general over both, not just raw byte buffers. Ties into the
|
|
memcpy discussion (2026-09-20): a struct/array is a first-class aggregate
|
|
value in flan today (copies happen via load/store, no memcpy builtin
|
|
exists), so this would be the first builtin that reaches into one of those
|
|
and overwrites it as raw bytes rather than treating it as a typed value —
|
|
same category of operation llvm.memset already does for zero, just exposed
|
|
as a real user-facing builtin instead of only an internal codegen detail.
|
|
|
|
Not designed or implemented, just an idea.
|
|
|
|
Built, 2026-09-20, as two builtins rather than one — the author's answer to
|
|
the cheap-byte-or-real-pattern question was "why not both? we need some sort
|
|
of memset -1 right? and dead-beef can loop, that's fine". They are
|
|
(filled BYTE) and (dead-beef), spelled the way [zeroed] is: the value of
|
|
whatever type is expected of them, so (set grid (filled 0xFF)) is how a place
|
|
is filled and there is no place-taking form to learn beside [set].
|
|
|
|
(dead-beef) writes the default DEADBEEF and (dead-beef 0xBAADF00D) writes the
|
|
pattern given, under one byte-order rule: a pattern's ascending bytes are its
|
|
big-endian bytes, which is how the hex literal reads left to right. So every
|
|
candidate listed above is spellable without the compiler naming any of them,
|
|
and the bare form is checked into the spelled-out default rather than being a
|
|
case a backend knows about. The pattern may be computed, not only written —
|
|
same rule as the byte arm, and both backends byte-reverse at run time when it
|
|
is.
|
|
|
|
The snag above is why there are two and not one with a wider operand. The
|
|
byte fill is one llvm.memset / one rep stosb; the 4-byte pattern is a loop on
|
|
both sides, a counted dword loop in emit.ml and rep stosd in x86.ml, because
|
|
the intrinsic really does only take a repeated i8.
|
|
|
|
What may be filled is numbers, and structs and fixed arrays built out of
|
|
numbers — nothing that carries a tag, a length, an owning pointer or a
|
|
collector descriptor. That boundary, and why each refusal is the runtime's
|
|
rather than a matter of taste, is written up in FIX.org, "The two byte
|
|
fills".
|
|
|
|
** struct field type change: got a plain type error, not the documented refusal
|
|
Hit "expected i32, found u8" after changing Cell's row/col from u8 to i32.
|
|
Turned out to be a leftover (u8 (/ my cell-size)) cast at the construction
|
|
site that I hadn't updated to i32 yet — an ordinary, correct type error, not
|
|
a hot-reload problem. Already fixed in the file.
|
|
|
|
Worth remembering: a struct field type change is one of exactly three
|
|
redefinitions the session actually refuses outright rather than silently
|
|
mis-loading (docs/BUILT.md:1213-1219: "a struct's fields | the values the
|
|
process is holding have the old layout"). That refusal has its own distinct
|
|
message — if I hit it for real it won't look like an ordinary type mismatch.
|
|
|
|
** compiler messages need a persistent, copyable buffer — not just *Messages*
|
|
No dedicated log/output buffer for compiler errors exists anywhere in
|
|
emacs/flan.el. Today it's only: a transient error overlay
|
|
(flan--error-overlays, cleared on the next command) and `message` to
|
|
*Messages* (append-only, awkward to copy out of, no structure). Want both
|
|
the ghost text AND a persistent buffer that accumulates these, so a message
|
|
can actually be copy-pasted (e.g. to hand to Claude) without hunting through
|
|
*Messages*. Not designed or implemented.
|
|
|
|
** defmacro should support real parameter lists, not just one [Form] arg
|
|
Every defmacro today takes exactly one parameter, the whole call's arg list
|
|
as [Form] (lib/parse.ml comment: (defmacro m [args] body) is (defn m [args
|
|
[Form]] Form body)). Porting a Clojure macro like
|
|
(defmacro do-grid [[r rows c cols] & body] ...) means manually picking apart
|
|
`args` by hand every time — (at args 0), match on Form.Vec to unwrap a
|
|
binding vector, (form-rest args 1) for the body, no arity checking, no
|
|
destructuring in the signature itself. with-drawing/with-mode-2d
|
|
(vendor/raylib/modes.flan) already do this by hand and it works, but it's
|
|
real boilerplate for something Clojure gets for free from defmacro's own
|
|
parameter list. Real design gap, not a small fix — would mean parsing macro
|
|
params with the same destructuring patterns [let] already has (dvec/dmap,
|
|
lib/parse.ml:698-799) plus variadic &body support, applied at the macro
|
|
call site before expansion rather than left to the macro body.
|
|
|
|
** profiling: use Tracy, don't build our own
|
|
No profiling infra or prior discussion exists in FIX.org/plan.org/docs.
|
|
Recommendation: bind Tracy (TracyC.h, a pure C API) via declare-c — same
|
|
mechanism raylib already goes through. Building a bespoke profiler means
|
|
redoing a capture protocol, a viewer, flame graphs and a timeline, all of
|
|
which Tracy already does well, for a domain (real-time/game loops) it was
|
|
built for.
|
|
|
|
Two integration points already fit flan's own design:
|
|
- Tracy's frame mark maps directly onto flan's existing frame-boundary
|
|
notion — where agent/poll is called each loop iteration.
|
|
- flan already has precedent for dev-build-only instrumentation wired in as
|
|
an LLVM constructor (the allocation registry, lib/emit.ml ~4165). Automatic
|
|
per-function Tracy zones in dev builds, using the same dev/cell
|
|
indirection every call already goes through, could follow that pattern
|
|
instead of requiring hand-instrumentation everywhere.
|
|
|
|
Alternatives considered:
|
|
- Optick — similar to Tracy but less active, historically Windows-first. No
|
|
real advantage over Tracy.
|
|
- Superluminal — great UI, but commercial and Windows-only. Ruled out (Linux).
|
|
- perf/Hotspot — free, zero-instrumentation sampling, but not frame-aware,
|
|
not live-viewable, Linux-only. Fine as a second opinion, not primary.
|
|
- Chrome Trace Event Format / Perfetto (or a tiny header-only exporter for
|
|
it) — much smaller lift than Tracy: push timestamped begin/end onto a
|
|
buffer, dump JSON, view in Perfetto/chrome://tracing after the fact. No
|
|
live view, no socket server — but it's the one option with an actual story
|
|
for wasm32 (plan.org's committed third target: write trace to a buffer,
|
|
pull out via JS). Tracy's capture side assumes a real OS socket the viewer
|
|
connects to, which doesn't really work in a wasm sandbox.
|
|
|
|
Conclusion: Tracy for native dev-loop profiling now (best tool, trivial
|
|
declare-c binding, fits the frame-boundary idea already in the design). If
|
|
wasm32 profiling becomes a real need later, a small Chrome-Trace-Format
|
|
exporter is the justified "build our own" — not a Tracy replacement, a
|
|
different target Tracy doesn't reach. Not designed or implemented.
|
|
|
|
** no type-limit constants: u8-max, i32-max, f32-infinity, etc. — nothing exists
|
|
Confirmed missing entirely. No MAX/MIN constants in the prelude for any
|
|
sized int type; min/max are just binary comparison builtins, not type-bound
|
|
limits. No C limits.h/float.h macro import either (cimport only pulls in
|
|
declared functions/structs/typedefs, not #define constants) — no path to
|
|
INT_MAX/FLT_MAX that way.
|
|
|
|
Worse for infinity/NaN: there's no literal syntax at all. float_repr
|
|
(lib/form.ml:87-104) prints inf/nan as words for a runtime value that
|
|
happens to be one, but the reader doesn't parse those words back ("nan.0 is
|
|
no improvement on a literal no reader accepts either way"). Only way to get
|
|
+inf today is at runtime: (/ 1.0 0.0) (float division by zero is IEEE-754
|
|
defined, not a trap the way integer division by zero is) — no way to just
|
|
write it down. Real gap, not implemented.
|
|
|
|
** investigate SBCL's redefinition model — warn + keep old value until callers update
|
|
Right now a global/struct type change is a hard refusal (lib/session.ml:
|
|
304-311, docs/BUILT.md:1213-1219) — "restart to change it." I remembered a
|
|
design where the OLD value/layout keeps being used until the calling code
|
|
is recompiled against the new one, since code using the old shape probably
|
|
won't even compile anymore anyway, so nothing unsafe reads mismatched
|
|
memory. Found it — it's real, but only planned for a narrower case:
|
|
|
|
plan.org's Hot Reload section (~685-696): a signature-changing function
|
|
redefinition creates a new internal function version + trampoline. Newly
|
|
compiled callers use the new one; existing callers and stored function
|
|
values keep the old version safely. The session warns at every tracked old
|
|
caller site; recompiling one either updates it or gives a normal type error.
|
|
|
|
plan.org's dev/release table (~671-682) also lists "Structs: version word"
|
|
for dev builds — implying a similar versioned-layout plan existed for
|
|
structs too. But docs/BUILT.md:1223-1230 says none of this is built for
|
|
functions either ("no function versions, no trampolines... the refusal
|
|
stays, because the alternative to refusing is not the new design, it is a
|
|
silent argument mismatch. It is a stopgap"). So today BOTH functions and
|
|
globals/structs just hard-refuse; the versioned/warn-and-keep-old-value
|
|
design exists on paper for functions but nowhere for structs/globals, and
|
|
isn't implemented for either.
|
|
|
|
Action: look at what SBCL actually does on struct/type redefinition (it
|
|
warns rather than refusing, and instances of the old layout get an
|
|
"obsolete instance"-style condition on next access rather than corrupting
|
|
memory) as a model for what flan's struct/global version-word plan could
|
|
be, versus the current hard-refuse stopgap.
|
|
|
|
** need a value-producing array constructor, usable as a defvar initializer
|
|
Wanted to fill `grid` with 255 as part of its defvar declaration, not as a
|
|
separate mutation step after. Doesn't work today: dotimes returns Unit, not
|
|
an array value, so it can only mutate an already-existing place — it can't
|
|
be the initializer expression itself, which has to produce the whole typed
|
|
value in one go. Had to declare grid zeroed then mutate it in main instead.
|
|
|
|
(array 4 rl/Vector2) (Ast.ArrayOf, "the one position with no type slot",
|
|
docs/BUILT.md ~3475) already exists but only zero-fills — no way to give it
|
|
a fill value or a generator. Want something like (array-fill n v) or a
|
|
repeat/generate form that IS an expression (produces the array value
|
|
directly, works nested for 2D), so it composes as a defvar initializer the
|
|
same way a bracket literal does. Not designed or implemented.
|
|
|
|
** revamp the flan buffers: *flan-dev* has no compilation-mode / jump-to-error
|
|
Related to the earlier "compiler messages need a persistent buffer" item but
|
|
distinct — that one was about eval-time messages; this is *flan-dev*, the
|
|
daemon's own startup/build output (emacs/flan.el:729-765,
|
|
flan--start-daemon), which is where a `main` that fails to compile shows its
|
|
errors. It's a plain buffer — get-buffer-create with no major mode, raw
|
|
process output appended via make-process. No compilation-mode, no
|
|
compilation-shell-minor-mode, so no next-error / M-g M-n / jump-to-source.
|
|
|
|
The fix is probably small: flan's own diagnostics already print in the
|
|
ordinary path:line:col: message shape (every error pasted in this session
|
|
has been that format) — the same shape Emacs' built-in
|
|
compilation-error-regexp-alist already parses (it's the GCC/Clang shape
|
|
compile-mode was built around). So this likely isn't a custom-regex job,
|
|
just turning on compilation-minor-mode in *flan-dev* (or a dedicated
|
|
flan-daemon-mode derived from compilation-mode). Umbrella task: revisit all
|
|
the flan-* buffers (*flan-dev*, *flan-output*, error overlays, the eval
|
|
result overlay/minibuffer item) together rather than patching each one
|
|
separately. Not implemented.
|
|
|
|
** implicit numeric conversions with a warning flag, instead of hard errors
|
|
Not liking the strictness; want implicit typing with an opt-in warning flag
|
|
for implicit conversions instead of a hard type error.
|
|
|
|
Worth being clear-eyed first: "no implicit widening/narrowing anywhere" is
|
|
not an oversight, it's a core invariant repeated all over the codebase —
|
|
lib/check.ml:1774 ("this language has no implicit narrowing anywhere"),
|
|
:4736, :5640, :6575-6579 ("no implicit widening, so one side has to decide
|
|
it"), docs/BUILT.md:809. It's the Odin/Rust-style bet against C's decades of
|
|
silent-precision-loss bugs, and it's load-bearing in how binary-op checking
|
|
picks which side "decides" the type. A warn-instead-of-refuse mode isn't a
|
|
flag on top of that — it's a second type-checking mode that would need
|
|
threading through every one of those sites, not a small change.
|
|
|
|
That said, real middle-ground precedent exists: C/C++'s -Wconversion is
|
|
exactly "implicit, but warn" bolted on after the fact. Something similar
|
|
here would mean: allow widening/narrowing between numeric types silently at
|
|
the type-check level, but have the checker also emit a separate warning
|
|
list (surfaced same as any other diagnostic) for every site where a
|
|
conversion happened that wasn't an explicit cast. Not designed, and a real
|
|
philosophy question for the language, not just an implementation task.
|
|
|
|
** pos?/neg?/zero? don't exist, and need to be generic over numeric types
|
|
Nothing named pos?/neg?/zero?/sign anywhere in the prelude. Trivial to write
|
|
per-type ((defn pos? [x i64] bool (> x 0))), but that's the problem: we want
|
|
these (and inc/dec from earlier) to work across every numeric type without
|
|
writing one copy per type.
|
|
|
|
This is blocked on real generics, which don't exist yet — "generic code over
|
|
the type variable %s is not implemented yet — milestone 5" (lib/check.ml:
|
|
776-778, hit earlier when `int` fell through to the type-variable path).
|
|
min/max already do this at the builtin level (special-cased in the checker,
|
|
not written as generic Flan functions), which is the current workaround for
|
|
"needs to work over any numeric type" — but that doesn't scale to arbitrary
|
|
user-defined functions like pos?/neg?/inc/dec. Two related asks logged
|
|
together: (1) add pos?/neg?/zero?, (2) make milestone-5 generics real so
|
|
functions like these don't need special-casing into the compiler to be
|
|
generic.
|
|
|
|
** println output goes to *flan-output*, not inline in the repl
|
|
Deliberate per emacs/flan-repl.el:40-44: "a value and the program's output
|
|
are different things and arrive by different routes... showing them in one
|
|
place would be convenient and wrong." Same split in flan-mode's C-x C-e.
|
|
|
|
Not sure I like it — printed output feels disconnected from the eval that
|
|
produced it when it's in a separate buffer/window. Revisit.
|
|
|
|
** main cannot actually be redefined
|
|
Confirmed. emit_main (lib/emit.ml:3858-3861) calls the flan-level `main` via
|
|
`fname "main"` — a direct symbol call. Every other call site goes through
|
|
`body_of` (lib/emit.ml:1980-1994) instead, which loads from the dev-build
|
|
indirection cell so a C-c C-c redefinition takes effect. emit_main skips
|
|
that, so flan_program_main (what M-x flan-rerun re-enters) always runs the
|
|
body main had at the initial build, no matter what's redefined into main's
|
|
cell afterward. Not documented as a known limitation anywhere. Should
|
|
probably route through body_of too, or say plainly that main is special.
|
|
|
|
** the "queued; the program is parked..." note is annoying
|
|
lib/dev.ml:778-790. Fires on every C-c C-c redefine while parked — and a
|
|
finished program is always parked, so re-evaling main after each run always
|
|
shows this long note. Message is correct, just too verbose/frequent for the
|
|
common case. Maybe shorten, or only show once per park / make it toggleable?
|
|
|
|
** (rl/with-drawing ()) fails with a confusing error
|
|
`()` is never a valid expression (lib/parse.ml:278). with-drawing splices its
|
|
arg verbatim (vendor/raylib/modes.flan:79-84), so `(rl/with-drawing ())`
|
|
expands to `(do (begin-drawing) () (end-drawing))` and the bare `()` blows up
|
|
deep in the expansion. Workaround: use `(rl/with-drawing (do))` — `(do)`
|
|
with zero forms is a valid no-op (used elsewhere, e.g.
|
|
examples/core-input-gamepad.flan:268).
|
|
|
|
The macro does have an empty-body guard ((< (len args) 1) ->
|
|
with-drawing-takes-a-body) but it only catches zero arguments
|
|
((rl/with-drawing)), not one argument that is itself (). Guard should
|
|
probably also treat a single bare-() arg as "no body".
|
|
|
|
`()` is reserved as the type-position spelling of Unit (Types.to_string
|
|
Unit = "()", lib/types.ml:125) and has no value-position meaning today — the
|
|
unit value only comes from `(do)`, a form with no body, etc. Position
|
|
already disambiguates Vec/Map literals from their type spellings (parse.ml
|
|
comments near line 266-273); worth checking whether () could get the same
|
|
treatment — type in type position, unit value in expression position —
|
|
instead of being refused outright everywhere.
|
|
|
|
** error inside a macro-expanded body points at the macro call site, not the real line
|
|
Hit with do-grid: "unknown function neg?" reported at the do-grid call line,
|
|
not the actual (unless (neg? cell-val) ...) line inside the spliced ~@body.
|
|
|
|
Already known/tracked, not a fresh gap: the Form wire format a macro
|
|
receives and returns (lib/expand.ml:96-101) is Form.value, not Form.t — it
|
|
structurally has no loc field, "a macro cannot invent a source location and
|
|
the image has no room for one." So EVERY node a macro returns, including
|
|
~@body forms spliced through completely unchanged (my own original source,
|
|
not compiler-generated), gets the call site's location stamped on it
|
|
(lib/prelude.ml:1863-1868 says the same thing). The comment names the real
|
|
fix directly: preserving locations through a macro needs "the
|
|
structured-error rewrite," which doesn't exist yet — today's stamping is
|
|
explicitly the stopgap "that can be had now without" it.
|
|
|
|
So: not asking for something new, just noting I hit the documented
|
|
limitation and it's genuinely annoying for macros like do-grid that splice
|
|
a large body through — every error in the body mislocates to the macro call.
|
|
|
|
** built-in comment
|
|
No (comment ...) exists. Trivial as a one-line user macro today —
|
|
(defmacro comment [args] `(do)) — and it already has the useful property:
|
|
macro args are raw unparsed Form, never checked as expressions, so whatever
|
|
is inside never needs to type-check. Want it built in (prelude or special
|
|
form) rather than something every project defines for itself.
|
|
|
|
** #_ (discard) isn't syntax-highlighted, though it works correctly
|
|
Compiler side is fine and deliberate — lib/reader.ml:16-24, 202-262 reads
|
|
and throws away the next form, Clojure-style, including #_#_ counting-free
|
|
chaining. Purely an editor gap: flan-font-lock-keywords
|
|
(emacs/flan-mode.el:126-146) has no rule for #_ at all, and
|
|
flan-mode-syntax-table (line 180+, derived from lisp-mode-syntax-table) has
|
|
no notion of it either since Common Lisp doesn't have this construct. So a
|
|
discarded form renders as plain text, not grayed out/comment-styled.
|
|
|
|
real clojure-mode.el's approach is precedent: a syntax-propertize-function
|
|
that marks a discarded form's characters with the comment syntax class, so
|
|
font-lock renders it as a comment for free. Not implemented here.
|
|
|
|
** syntax highlighting: defmacro/when/etc missing, and macros aren't dynamic
|
|
Two separate gaps.
|
|
|
|
Easy: flan--definers (emacs/flan-mode.el:114-118) is missing `defmacro`
|
|
entirely, even though it introduces a top-level name like defn does.
|
|
flan--special (120-124) is missing when/cond/and/or/break/continue/recur/
|
|
handler-bind/handler-case/restart-case/invoke-restart — all real lib/parse.ml
|
|
special forms, just never added to the static lists. Plain oversights,
|
|
trivial to fix.
|
|
|
|
Harder: highlighting a user-defined macro like do-grid can't be a static
|
|
list — and the daemon doesn't even have the info today. A macro compiles
|
|
down to an ordinary Tast.fn (defmacro m [args] body is exactly
|
|
defn m [args [Form]] Form body) — macro-ness is erased by check time.
|
|
Confirmed: defs (lib/dev.ml:1080-1133) reports every function with
|
|
kind:"fn", no "macro" kind exists anywhere, because Tast.fn has no field
|
|
recording it came from a defmacro. So even flan--defs (the live cache
|
|
already driving eldoc/completion) can't tell do-grid is a macro right now.
|
|
|
|
Would need: (1) plumb a macro flag through Check/Tast so defs can report a
|
|
real "macro" kind, (2) have flan-mode treat flan--defs as a dynamic
|
|
font-lock source — filter macro-kind names, font-lock-add-keywords +
|
|
font-lock-flush/fontify-buffer whenever the cache refreshes (same points
|
|
flan-refresh-defs already runs at: connect, after an accepted eval). CIDER
|
|
does exactly this for clojure-mode off a live nREPL connection — real
|
|
working precedent. Not designed or implemented here.
|
|
|