Decimal is what the value is and stays first; hex and binary go beside it. It
is the wrong base for about half the numbers anyone opens this buffer for — a
colour is 0x303030FF, a gesture is an OR of flags, a mask is read a bit at a
time — and reading those out of a decimal is arithmetic done by hand.
In two places: under the header of a value opened on its own, and on each
numeric row of a field list. The second is the one that matters, because a leaf
cannot be stepped into, so the field list is the only place most numbers are
ever seen.
Nothing is asked of the program. It is arithmetic on text the renderer already
wrote, so it works on a stopped program and costs no round trip. Binary is
grouped in nibbles because a mask is read in nibbles. A negative is shown as
the 64-bit two's complement it is in memory and says the width out loud: the
rendered value carries none, and every Flan integer comes back through i64.
A float is left alone rather than answered wrongly — its bits are an IEEE
layout, reinterpreting them is a different question, and the rendered text does
not carry the width to answer it.
The pointer half of this is not done and the refusal now says why. Render.render
writes the bare word <ptr> for every pointer on purpose: it is the same renderer
print uses, an address is not stable across runs, and test_acceptance pins the
current text for that reason. Showing one is a decision about the language's
printer, not about this buffer.
The reported bug — the second and later bindings of a let one column too far —
was never one missing rule. `flan-indent-function' checked the head of the
enclosing form, and inside a binding vector the enclosing open is `[' and the
symbol after it is the first binding's name, so it fell through to Emacs's
`lisp-indent-function', which treats the vector as a call and aligns under the
first argument instead of the first binding.
Emacs Lisp is the wrong reference. It has no vectors-as-bindings, no maps and
no bracket variety, so every rule Flan needs has to be added by hand and the
binding vector is simply the first one hit. The indenter is rewritten from
clojure-mode's source instead: `clojure-mode' is neither an ancestor nor a
dependency — flan-mode still needs nothing beyond stock Emacs — it is the file
whose rules were read and written out again.
A bracket aligns under its first element, and that one rule fixes the binding
vector, `defn' parameter lists, `restart-case' and `handler-bind' clause
parameters and both spellings of a struct literal at once. `{:x 1}' and
`{.x 1}' indent identically because nothing here looks at the key, which is
what the colon-to-dot lane needs of it.
Where Flan diverges it is handled on purpose. `defn' is `:defn' rather than a
count because the return type between the parameters and the body is optional.
A clause — `(Name [params] body)' — is recognised by its shape, since its head
is a condition class or a restart name and can never be in a table; clojure-mode
reaches the same clauses by backtracking out to the enclosing form, which buys
generality this language has no other use for. Special arguments indent by one
body rather than Clojure's two, and a call whose head is alone on its line
indents its arguments by a body rather than aligning them under the head,
because that is how the whole corpus is written.
Checked by reindenting every .flan file in the tree: the only lines that move
are sand.flan's reported bug, raylib.flan's hand-wrapped parameter vectors —
which is the fix — and lone-`;' comment continuations, which stock
`lisp-indent-line' has always moved.
test-flan-mode.el is loaded from test-flan-cider.el rather than given a stanza
of its own, because emacs/*.el is already a dependency of that test.
Two failures in test-flan-cider.el that predate this: fixture frames lacked
`:fetched', so folding one open went looking for a daemon, and `layout' was
identified by being the last request when `flan-cnr-show' now makes three.
C-c C-b asks layout with the condition's own name and draws the fields under
it. The values stay refused, by name, because the shape of a condition is a
fact about the build and its contents are a fact about the stopped frame — and
only one of those is knowable today. A layout the daemon refuses is nil rather
than an error: the buffer already draws a section saying why one is empty, and
failing the whole command would take away the restarts over an annotation.
Saying "the fields are not available" was under-claiming. A condition is a
struct, and Tast.structs holds every struct's field names and types in the
daemon, which owns the build — no running program is involved in answering
what a Missing is made of. Only the values need the pointer the break loop
was handed, and break_loop currently discards that pointer, so they are two
different gaps with two different fixes on two different sides of the socket.
The buffer now draws a field named and typed with its value refused, which is
what tells you whether the field you were about to blame is even a field of
this condition.
Navigation backwards was not the same list walked the other way. Forward
wrapped and backward stopped, and from the middle of a line the two disagreed
about where a field begins — a field line carries the property on all of it,
so a property-change walk from mid-line finds the end of the field you are
already in. Both now go through one list of field starts. The mid-line case
went red on the first try and the expectation was the thing that was wrong:
landing on the current field's start is what CIDER does and is the less
surprising of the two.
C-c C-b is a completing-read over restart names, which is the whole UI for
the one moment the dev loop exists to make survivable. It shows the names and
nothing else, and it will let you pick one that cannot be taken.
That last part is a bug, not a gap. §4 says restart lookup takes the first
frame offering a name, and flan_find_restart does exactly that; so a second
frame offering "retry" is real, is on the list, and is unreachable — picking
it sends the string "retry" and the inner frame runs, silently. SBCL has
shown this since forever by numbering the restarts and omitting the bracket
on a name already used. Taken as is, and the shadowed row now refuses by name
and says what would fix it: an index verb, which does not exist.
SBCL also decides the order. invoke-debugger prints the condition, then
show-restarts, and stops; the backtrace is a command you type. The restarts
are the decision and the stack is the explanation for it, and a debugger that
opens with forty frames has buried one under the other.
What CIDER's stacktrace buffer gives is the behaviour — frames that fold in
place, everything on the keyboard. Not its cause chain: a JVM exception wraps
another one and a Flan condition wraps nothing.
The fields, the stack and the locals are drawn as sections that say why they
are empty and what each would take. A section left out cannot be told from
one that happened to have nothing in it, and only one of those is a fact
about the program.
C-x C-e renders once and stops at depth 4 and span 8. A field past either
comes back as "..." and nothing recovers it from the echo area. Re-rooting
the walk at that field renders it from depth 0, so the bound moves with you
— that, and not tidiness, is why an inspector is worth having beside the
expression evaluator.
CIDER keeps its inspector stack on the server because a JVM value can be
retained. Nothing here can: a Flan value has no header and the thunk that
rendered it is dlclosed the moment it returns. So the stack is a stack of
expressions on this side, and going into a field means sending a different
one — (.pos b) where the last one was b. It costs a re-evaluation per step,
which buys a view that is never stale and is why refresh is a key someone
presses rather than a timer.
Driven from fixtures, which is also the only way the cases a live program
will not hold still for get tested at all.