# Flan in Emacs A manual for the editor side. It assumes you know Emacs and nothing about how Flan's dev loop is built — if you want that, `docs/BUILT.md` has it. The short version: you start a program, you keep it running, and you change it while it runs. Everything below is a variation on that. --- ## Setting up Put `emacs/` on your load path and require the mode. Nothing else is needed — `flan-mode` pulls in the rest as you use it. ```elisp (add-to-list 'load-path "~/Development/flan/emacs") (require 'flan-mode) ``` `.flan` files open in `flan-mode` after that. The client, the REPL, the inspector and the conditions buffer all load on first use, so requiring the mode does not drag them in. One optional extra: `flan-dape.el` gives you lldb through [dape](https://github.com/svaante/dape). It is separate on purpose — `flan-mode` works without dape installed, and `C-c C-g` only exists once you load it. ```elisp (require 'flan-dape) ; only if you have dape ``` Or install it as a package, which gets you autoloads and byte-compiled files without a `load-path` line. `package-install-file` names the package after the directory, so the directory has to be called `flan-mode` — symlink it once and point Emacs at the link: ```sh ln -s ~/Development/flan/emacs ~/.emacs.d/flan-mode ``` ``` M-x package-install-file RET ~/.emacs.d/flan-mode/ RET ``` Every file carries `Version:` and `Package-Requires: ((emacs "29.1"))`, which is what package.el reads. dape is deliberately not in that list: `flan-dape.el` reaches it through `declare-function`, so everything else installs and works without it. You also need the `flan` binary on your `PATH`. If it is somewhere else, set `flan-command`. Two settings worth knowing about before you need them. `flan-start-timeout` (60s) bounds the wait for a daemon to come up, and `flan-reply-timeout` (30s) bounds one request once it has. The second is the one a long first compile can exhaust: the message says so and names `*flan*`, where the daemon's own build log is, so you can see whether it is still working before you raise it. --- ## Starting a program Two ways in, and they are different. **`M-x flan`** starts the program for you. It runs `flan dev` on a file, waits for it to come up, and connects. This is the normal way. From a buffer already visiting a `.flan` file it starts *that* file and asks nothing — the buffer has answered the question. From anywhere else it reads the file from the minibuffer, and `C-u M-x flan` reads it even from a `.flan` buffer, which is how you start some other program without leaving the one you are looking at. **`C-c C-z`** (`flan-connect`) attaches to a program that is *already* running — one you started in a terminal, say. It looks for a `.flan-dev.sock` file in the current directory and upward, so from anywhere in the project it finds the one program you have running. Either way, when you are connected the modeline says so and Emacs tells you what it found: ``` flan dev: connected to ~/Development/flan/.flan-dev.sock (47 functions, 2 globals) ``` **`C-c C-q`** disconnects without stopping the program. **`M-x flan-quit`** stops the program too — but only one this Emacs started. A daemon you launched in a terminal is not Emacs' to kill, and it will say so rather than do something surprising. ### A file with no `main` A file of functions with no `main` — a scratch file, a library being written — starts a session too. Nothing runs, and the session is there to take definitions and expressions: `C-x C-e` on `(fib 10)` answers, and `C-c C-k` loads more into it. `M-x flan-rerun` says there is no `main` until one has been loaded. A form in such a file that does not compile is left out of the start and printed in `*flan*`, and the session starts with the rest. The program does not have to import the agent. `flan dev` links it into every program it builds, and a release build is unaffected. ### Which backend the session uses, and what it costs `flan dev` compiles the session with the hand-written x86-64 backend. That is the default, and it is the reason `C-c C-c` is fast: about 28ms at the socket against about 63ms through LLVM, because `as` does in 8ms what `llc` does in 45ms. On a project you are iterating in, that is the difference you feel. It is not the whole compiler. Two things a session built by it cannot do: - **No `C-u C-c C-a`.** That view shows the LLVM IR a body was built from, and there is none; the listing this backend produced is assembly. Plain `C-c C-a` disassembles the object and works exactly as before. - **Some programs it refuses by name.** It covers a subset of the IR and says so rather than guessing — a `signal` or a `restart-case` inside a global's initialiser, a `declare` that returns a struct by value, a value crossing the C boundary in a shape it has no classifier for. A refusal names the form *and* names `--llvm`, in both places you can meet one: the daemon's own build log when it starts, and the error overlay when `C-c C-c` hits one mid-session. It is never a bare "unsupported". **`--debug` is not on this list**, because it picks LLVM for you. `flan dev --debug` — the breakpoint and `flan-dape` path — is an LLVM session, since a redefinition module compiled by the x86 backend carries no line table and a breakpoint set in one would stop firing at the first `C-c C-c`. Writing `--x86 --debug` together is still refused, and now says which one to drop. ### Asking for LLVM Set `flan-daemon-args`. It is a list of strings, spliced into the daemon's command line after the file, and it is how anything that is not the file or the socket reaches `flan dev` from Emacs: ```elisp (setq flan-daemon-args '("--llvm")) ; the IR view, every form (setq flan-daemon-args '("--debug")) ; breakpoints, through flan-dape ``` The first line of `*flan*` is the command that actually ran, so you can always check what a session was started with. There is no prefix argument for this and that is deliberate: `C-u M-x flan` already means "ask me which file", and a second level that meant "ask me which flags" would be two unrelated questions on one key. A setting is also the honest shape of the thing — which backend your sessions use is a property of the project you are working on, not of the keystroke that started this one. **If you were relying on the old default:** every `flan dev` before this one was an LLVM session, so a workflow built around `C-u C-c C-a` will find it refused now. One line in your init puts it back. --- ## The loop This is the part the whole project exists for. ### `C-c C-c` — change one function Put point anywhere in a top-level form and press it. The form is recompiled and installed into the running program, which does not stop, restart, or lose anything. The next time that function is called, the new one runs. It works on the *buffer text*, not the saved file, so you do not have to save first. If the form point is in is not a declaration — a bare `(+ 1 1)` at column 1, say — it is evaluated as an expression instead, and its value comes back the way `C-x C-e`'s does. `C-M-x` is the same command on the binding SLIME and CIDER use, and does the same thing: whatever is under it, the obvious thing. Point *inside* a `defn` body still means the `defn` — the form chosen is the one point is in, and only then is it asked whether it is a definition. ### `C-x C-e` — evaluate an expression The expression before point is compiled, run **inside the running program**, and its value shown as `=> 2` at the end of the line the form is on. Not a copy of the program, not a simulation — the actual process, with its actual state. So in a game you can type `(length enemies)` and get the real number. **Where it appears.** At the end of the *line*, which is where the watch buffer's ghost text goes too — the two are the same thing seen twice and they are drawn the same way. It used to sit at the end of the form instead, so that the two features could be told apart by position; what that cost is a value wedged into the middle of a line whenever the form was not the last thing on it, reflowing everything after it while you read. **And in the echo area, always.** The overlay goes away on your next keystroke, like every other bit of feedback about a command that just ran — so the same value is written to the echo area as well, where it is still there afterwards. The echo used to be suppressed whenever the overlay drew, on the argument that the same number twice teaches a reader to skip both; between saying it twice and losing it before it was read, saying it twice is the smaller cost. The two settings are still different questions, and neither now suppresses the other. `flan-inline-result` decides the overlay: set it to `nil` and the echo area is the only place a value appears, which is also what happens when there is nowhere in a buffer to draw one, as at the REPL. `flan-echo-result` decides the echo area, and it also covers the sentence an *install* reports — which names landed and what it cost — since an install has no value to draw. The form before point can also be a declaration — a `defonce` typed at the top of a file — in which case `C-x C-e` installs it rather than refusing it, and says which names changed instead of printing a value. **Names mean what they mean in the file.** An expression sent from a package's file resolves as code written in that file would: `(integrate 1.0)` in `physics/step.flan` reaches `physics/integrate`, a `defn-` included. The REPL is not a file, so there the program's own names apply. ### `C-u C-c C-c` — stop there The same key with a prefix argument **marks a form as a breakpoint**. `C-u C-c C-c` marks the innermost form point is inside — with the cursor in `(+ ticks 1)` the program stops at that `(+ ...)` — and `C-u C-u C-c C-c` marks the top-level form itself, which means stopping on entry to it. The buffer is not edited. The position goes to the daemon beside the code, and the `(pause)` call is put into the tree after parsing, so every location in the file stays exactly where it was and error markers, `M-.` and the debugger keep pointing at the right place. The marked form is underlined while the mark is on it. When the program reaches it, it stops in the ordinary break loop: `C-c C-b` shows the stack, and `continue` resumes at the call. Nothing special-cases this — `pause` is a prelude function that signals a `Pause` condition, so a breakpoint is just a condition nobody handled. **The mark sticks** until you evaluate that form plainly. Mark it, run the game, hit it as many times as you like; an ordinary `C-c C-c` over the same form (or `C-c C-k` over the buffer) takes it off. **Stepping.** `C-c C-s` installs the `defn` at point so that a call stops before each form of its body. Each stop is a break like `(pause)`, and the source of the form about to run is shown beside it. `s` goes to the next form, `c` runs the rest of the call, and the next call steps again. `C-c C-c` over the same form installs it plain. `C-u C-x C-e` does the same for the expression before point: it stops *at* the expression instead of printing its value. That one does not stick, because there is no definition for it to stick to. `C-u C-c C-c` on a top-level form that is an expression rather than a declaration means exactly this — there is no inside for a position to point at, so `C-u` and `C-u C-u` say the one thing there is to say, and it does not stick either. ### `C-c C-m` — what a macro call expands to Put point on a macro call and press it. `*flan-macroexpansion*` opens with the code that call turns into, as Flan source, indented and coloured like any other. **One step by default, `C-u C-c C-m` for all the way.** The two genuinely differ: a macro may produce a call to another macro, so `(mac/quad 3)` is `(mac/twice (mac/twice 3))` after one step and `(+ (+ 3 3) (+ 3 3))` at the end. One step is the default because it is the one that can say *which* macro produced what — the full expansion is tagged with the name you wrote and nothing else. Inside the buffer, `m` expands the form at point one more step **in place**, `a` takes it to the end, `g` asks again — useful after you have re-evaluated the `defmacro` — and `q` closes it. This matters most for a macro you cannot read beside the call: a package's macros arrive qualified, so `rl/with-drawing` is defined in another directory and there is nothing next to the call site to look at. It expands against the macros **this session holds** — the prelude's, the ones its imports brought in, and every `defmacro` you have evaluated since it started — not against a fresh read of the file. So it agrees with what an evaluation does, and it sees the macro you have typed rather than the one you last saved. Two things the buffer says out loud. Nothing in it has a location of its own: every node carries the *call site's* file, line and column with the macro's name stamped on it, which is how an error inside expanded code points at the call you wrote. And it is not the text of any file, so `C-c C-c`, `C-c C-k` and `C-x C-e` in that buffer refuse rather than installing code nobody wrote. A macro that never settles is refused at a bound and named, with the refusal drawn on the call — it does not hang. ### `C-c C-k` — the whole buffer The whole buffer is loaded into the running program, as `C-c C-k` loads a file in SLIME and CIDER. It is sent as **one** module rather than as a form at a time. That matters: a `defonce` and the function that uses it have to arrive together, or the function refers to storage that does not exist yet. A form that does not compile is left out, and the rest are installed. A name that was already in the program keeps its earlier definition, and the forms that use it are compiled against that one; a form that uses a name defined nowhere else is left out too. Each form left out is marked in the buffer and listed in `*flan-diagnostics*`, and the echo area counts them. When nothing compiles, nothing is installed and the first error is reported as `C-c C-c` reports one. Use this when you have changed several things at once, when you have added a new global, or to load a file into a session started on another. ### When it lands Changes install at a frame boundary — the program finishes what it is doing and picks up the new code at a clean point. You do not have to think about this except to know that a change is not necessarily live the same *millisecond* you press the key. --- ## The REPL **`C-c C-r`** opens `*flan-repl*`. It is a comint buffer; every line goes through the same machinery `C-x C-e` uses, so anything you can evaluate there you can evaluate here. One thing to know: it is **program-scoped**, not buffer-scoped. Names are the running program's names. In sand you write `sim/settle`, not `settle`, because that is what the program calls it. **Output lands here too.** Whatever the program printed while evaluating your form rides along on the reply and is inserted above the prompt — output first, then the value, the way a terminal REPL reads. When a compile fails the prompt gets one line, `1 error — see *flan-diagnostics*`, and the message itself is in that buffer, which pops up. What the program prints on its own, between evaluations, is sent by the daemon as it is printed and appears at once. The daemon's buffer `*flan*` mirrors all program output, so a println is never lost when no prompt is open. **Two clears.** `C-c C-o` removes what the last send produced — output, value or error line — and leaves the transcript. `C-c M-o` erases the transcript whole and leaves a fresh prompt. Both work from a `.flan` buffer as well as from the prompt, and neither touches the input history. **History.** `` and `` walk it while you are on the line you are typing, filtered by whatever you have typed so far — `(sim` then `` reaches only the forms that start that way. Anywhere above the prompt the same keys move by line, so the transcript is still something you can scroll back through. `M-p`, `M-n` and `M-r` are comint's own and unchanged. It survives quitting Emacs. The file is `flan-repl-history` under `user-emacs-directory`, so `no-littering` and a moved state directory both take it with them; set `flan-repl-history-file` to nil to keep history for the session only, or `flan-repl-history-size` to keep more or less than 500 entries. A form typed over several lines is saved whole and comes back whole, and RET on one recalled sends all of it. --- ## When the program stops If the program hits an error nobody handled, it does not die. It stops, on the frame where the error happened, and waits. The modeline says `stopped`. Everything else in Emacs behaves normally — a stopped program looks like a running one from anywhere else. ### `C-c C-b` — the conditions buffer This is where you decide what to do. It shows three things, in this order: 1. **the condition** — what went wrong 2. **the restarts** — your choices 3. **the stack** — the explanation That order is deliberate. The decision in front of you is which restart to take; the stack is why. A debugger that opens with forty frames has buried the decision under the explanation. Keys in that buffer: | Key | Does | |---|---| | `RET` | take the restart at point; on a frame or on the `at` line, visit the source | | `0`–`9` | take that restart by number | | `TAB` / `n` | next restart | | `S-TAB` / `p` | previous | | `f` | fold a stack frame open or closed | | `v` | visit the source of the frame at point | | `P` | show or hide the prelude's frames | | `i` | inspect the local or global at point | | `e` | evaluate an expression in the frame at point; it sees that frame's locals | | `s` | at a step, go to the next form | | `c` | take `continue`: at a step, run the rest of the call | | `a` | abort | | `g` | read the program again | | `q` | close the buffer | **Why restarts are numbered.** A restart is taken by *position*, not by name. Two frames can offer a restart with the same name — `retry` is common — and a name resolves to the innermost one. So an outer `retry` is real, is on the list, and cannot be reached by name. The numbers are how you reach it. Restarts that genuinely cannot be taken are shown and refused with a reason rather than silently omitted. **When the break came from a `C-x C-e`.** If the expression you evaluated is what stopped, the list carries one more restart than the program established: `abandon-evaluation`. Taking it drops the expression and returns the program to where it was called from, still running. Point starts on it, because after an evaluation goes wrong that is usually the answer — and because the alternative used to be `abort`, which ends the session over a mistyped index. It abandons, it does not undo. The expression ran until it stopped, and every global it set and every byte it allocated on the way is still set and still allocated. The program's own restarts are still on the list below it, marked and refused: a transfer to a frame below the evaluation has nowhere to land. They become takeable only when the program itself was already stopped — abandon the evaluation and the program's own break comes back with them on offer. If the program was running, abandon and call the code again. **The stack.** Frames are numbered innermost first. A frame of a prelude function — `pause` is one, so every breakpoint has one — is hidden, and a line in its place says how many were hidden; `P` shows them. A hidden frame keeps its number, so the numbers either side of it have a gap. The innermost frame is where the program stopped, so it is shown even when it belongs to the prelude, unless the stop is a `(pause)`. A frame's location is where its function is written, and the `at` line under the condition is the expression that stopped. `RET` on either opens that file at that line. `next-error` (`M-g M-n`) walks the same list from any buffer while the program is stopped: the stop first, then each frame outward, skipping frames that have no file. **`C-c C-M-b`** is the same choice as a quick one-key prompt, when you already know which restart you want and do not need the buffer. **The condition's fields are named and typed, and have no values.** Under the condition you get the struct it is — `:path str`, `:tried i32` — because the daemon compiled the program and knows what that type looks like without asking the program anything. What is beside each field is a note saying the value is not available, not a blank: a value lives in the stopped frame, and nothing yet hands the break loop's condition pointer back. Knowing the shape is still worth having — it tells you whether the field you were about to blame is a field of this condition at all. If that section says it could not resolve the name, read it: a package qualifies what it declares, so two packages' `Missing` are `a/Missing` and `b/Missing`. The daemon refuses a bare name and says what it could have meant rather than picking one. After you choose, the program carries on from the restart. It never unwound, so everything it had is still there. --- ## Looking at values **`C-c C-i`** inspects a value: the expression point is on, with no prompt. You get its fields, one per line. It takes the innermost thing point is on rather than only the one behind point, so the middle of a name works as well as the end of one — point anywhere in `enemies` takes `enemies`, and on the open paren of `(length enemies)` takes the whole call. **`C-u C-c C-i`** opens the minibuffer instead, pre-filled with whatever was at point — for inspecting something that is not written in the buffer, or is written somewhere else. That is also what you get when there is nothing at point to take. | Key | Does | |---|---| | `RET` | go into the field at point | | `l` | back out one level | | `g` | read it again | | `TAB` / `n` | next field | | `S-TAB` / `p` | previous field | | `e` | set the field at point | | `C-c C-e` | open the value for editing | | `C-c C-c` | commit what you changed in it | | `C-c C-k` | abandon the edit | | `q` | close | Two things worth knowing, because they are unlike other inspectors. **The view is never stale.** Every step reads the program as it is *now*. Most inspectors show you the object as it was when you opened it. **The root runs again on every step.** Going into a field sends a new request, not a lookup in something remembered. Appending a field name to an expression is harmless, but the expression itself need not be: if you inspect `(spawn-enemy)`, you spawn one per keystroke. That is why there is no auto-refresh and why `g` is a key you press rather than a timer. ### Three ways to root a walk There are three, they are not equally capable, and the top line of the buffer says which one you are on. **An expression** — `C-c C-i`, and `i` on a global line in the break buffer. It works on a **running** program and starts from anything you can write, including a call. It cannot name a frame: an expression is evaluated where the evaluator stands, so a local's name reaches that local only when its frame is the innermost one. And it cannot reach an option's payload, because nothing in the language names it. **A frame and a slot** — `i` on a local line in the break buffer. It is exact to one frame and one slot, so the value you get is the one the listing above it drew. It reaches an **option's payload** and a **union case's fields**, which have offsets but no accessor form to write. In exchange it needs a **stopped** program, and it is refused — by name, with the reason — once the program resumes or if the frame's body was redefined since the frame was entered. It cannot start from an expression at all. Because it reads a place in the stopped frame, the buffer also shows where that place is stored, as `at 0x…` under the value. **An address** — `M-x flan-inspect-address`. A number, `#x7f…` or decimal, of the kind a debugger, a valgrind report or a C shim's `printf` hands you. There is no frame in it and no expression: what it shows is the `(Ptr T)` at that address, followed if the storage is still live and an epitaph naming what died there if it is not. **The type is optional, and leaving it out is the point.** A dev build records the type at every allocation — the allocator's caller knew it, and a Flan value carries no header, so that is the only moment anything could — and this command is the one place that recorded name is read back and turned into a type again. Naming a type overrides it, for reading half a struct or an element the table recorded under a container's name; the reply still carries what the allocator wrote down, so you are never shown one type while the program believes another. It needs a **stopped** program, for a reason of its own: whether an address is still live is exactly what a running program is changing. And it takes **no path** — `RET` does not go into it — because the whole answer is the pointer arm's branch, and stepping in would step from the pointee, which is the deref the registry has only just been asked to bless. An address the registry has never seen is refused by name rather than rendered. That is a stack local, a global, or a pointer from C; the first two are answered by name in the stack and globals sections already. None of the three subsumes the others, which is why all three are here. The one you get is chosen for you by the line you press `i` on, or by starting an address root by hand. **`l` never crosses between them**, and that is structural rather than a rule someone has to remember. Every entry on the buffer's stack carries its own root; `RET` only ever lengthens the path under the root already in hand; and starting a new root starts an empty stack. So a stack with both kinds in it cannot be built, and `l` has nothing to cross into. ### Changing one — `e`, and `C-c C-e` The inspector writes as well as reads, and the loop it is for is the one the whole dev story is about: "that field is wrong" and "is it *this* value that fixes it" are two keystrokes apart, where editing the source and reloading answers a different question — it answers what the **next** run does. **`e`** sets the field or element at point. You are prompted with what is there now, and what you type is a Flan **expression**, evaluated in the program and checked against the type of the place it is going into. `(+ 1 2)`, a string literal, a whole struct literal all work; one that does not fit comes back with the checker's own sentence — *expected f32, found str* — and nothing is stored. A literal arrives at the place's width, so `3` into an `f32` field is three point oh and not a refusal you would have had to write `3.0` to avoid. Point on no field line at all sets the whole value, which is how a data type's case is changed. **`C-c C-e`** turns the buffer into the value. What you get is the Flan literal the program wrote — the value's own spelling, with no second notation to learn — and you edit it as text. **`C-c C-c`** commits; **`C-c C-k`** throws it away. A commit is a **diff**, not a blast. The buffer is read back as a value, compared leaf by leaf with what was drawn, and one write is sent per leaf that changed — so editing one field of a struct does not rewrite the others with what happened to be on your screen. Every write in a commit goes in **one** module: either all of them happened at this stop, or none did. What it refuses, and each by name with the reason: - **A shape that changed.** Adding or removing an element, adding or renaming a field, putting a number where a struct was, changing which case a data type holds. Those are changes to the container or to the tag, not stores into storage. `e` on the value sets it whole, which is the operation that *is* available. - **A value the renderer did not write.** ``, ``, and the `...` the walk writes where it stopped at its depth or span bound. A buffer with one of those in it is a buffer missing part of the value, and a commit read off it could not tell a field that was never written from one you deleted — so the value refuses to be opened for editing at all. - **A pointer.** An address typed into a prompt is one the editor made up. Nothing blessed it and the program would dereference it at a moment nobody chose; the read half does not follow pointers either. - **An option's payload on its own.** The tag is what says whether there is one. Set the option. ### What makes writing safe Writing is refused outright on the expression and address roots. Only the **frame and slot** root can be written to, and the reasons are what the whole feature rests on. The program must be **stopped**, because a poke into storage the program is mutating is corruption with a plausible shape. But "stopped" is not enough on its own: a write is built by a compiler, and a third of a second of `llc` is long enough for a game to resume, run a frame, and stop again. The same slot index of a different stack is not the place you were looking at. So a write names the **stop** it was addressed to, not the state. Every inspection carries the number of the stop it was read at; a write hands that number back; and it is checked twice — here, before anything is built, so a stale buffer refuses in a tenth of the time and names the buffer rather than the module, and again inside the program, on the game thread, at the moment the module is claimed. A program that went round its loop between the drawing and the commit refuses with *look again and re-do the edit*, which is the whole of the fix. The read-only view stays the default. `e` and `C-c C-e` are there when the root can carry them, and the key legend at the foot of the buffer lists them only then — a legend offering a key that would refuse is advertising a refusal. A **global** is not writable from here yet: the break buffer roots globals at an expression, and an expression is evaluated wherever the evaluator stands and whenever it next reaches a frame boundary, which for a write means possibly into a running program. `C-x C-e` on `(set the-global …)` does that write knowingly; the inspector will not do it behind a key that looks the same as the safe one. ### Where the memory went — `M-x flan-allocations` and `M-x flan-leaks` The same registry, read as a table rather than at one address. **`M-x flan-allocations`** is every block it recorded, live and dead both, grouped by the type the allocator's caller named and ordered biggest first by bytes. The dead are in it on purpose: in a long-running program they are the bulk of it, and they are what says where the allocation *went* rather than only where it stayed. **`M-x flan-leaks`** is the same walk with the dead left out — what the program is still holding. **"Still holding" means at the moment you ask, and there is no exit report to wait for.** A program killed by a signal, which is how a program under this editor usually ends, runs no exit handler at all, so nothing written inside it could report anything. Asking is the answer, and you can ask at any time including just before you quit. A program that returns from `main` on its own can print the same breakdown to stderr by being run with `FLAN_DEV_LEAKS` set — off by default, because a dev build's output belongs to the program. Both are dev-build only. A release build records nothing and says so. ### Which lines allocate — `M-x flan-check-memory` The two commands above ask the running program what it *took*. This one asks the compiler what the source *says*, and draws the answer under the characters that say it. Clojure's `*warn-on-boxed*` crossed with Rider's heap-allocation squiggles. It needs no running program — a parked session answers it — and it is **off until you ask**. A program that means its allocations does not want them underlined while it is being written; you ask when the question is "where is this frame's memory going". **Two colours, because there are two heaps.** One face for the dyn runtime's collected heap: a string, a vec or a map crossing into `dyn`, the view record a typed container takes when it crosses, an `i64` too wide for a `dyn`'s payload. Another for an allocator the program named: a push or a `reserve` past capacity, a `clone`, a `slurp`, a new arena. The two underlines differ in style as well as in colour, so you can tell the classes apart without relying on colour alone. Both are fainter than an error: neither inherits the error face, and neither draws its message into the line the way a rejection does. Hover for the sentence; the count is in the echo area. **What is deliberately not marked is the half worth knowing.** A `dyn` immediate costs nothing, so nothing is drawn: `nil`, a `bool`, an `f64`, a keyword, and any integer inside ±2^47 live in the word itself. Neither is `(vec-new T)` or `(map-new K V)` — a typed container takes no block until something is put in it, and the line that is marked is the first push. A site that allocates only sometimes says so in its first two words: "may allocate". They last until you edit the buffer — they are a reading of the source, not feedback about an evaluation, so moving point through them leaves them alone. Asking again while they are up takes them down. **The full list goes to `*flan-diagnostics*`** as its own section, below whatever errors are logged there — every site, the ones in files you have open and the ones elsewhere, one `file:line:col: message` line each in the kind's face. `n`, `p` and `RET` navigate it like the errors above it. Asking again replaces the section; the errors above it stay. #### The same thing from the command line, and flycheck `flan check FILE --warn-memory` prints the same list in the standard `file:line:col: warning: ...` shape, on stderr, with the squiggle. `flan build` takes the flag too. **The exit status does not move** — these are warnings, and a program that only warns still builds. There is no flycheck checker in `flan.el` today. If you want one, the flag is what it should call, and the message shape is what it should parse: ```elisp (flycheck-define-checker flan-memory "Flan's allocation diagnostics." :command ("flan" "check" "--warn-memory" source-original) :error-patterns ((warning line-start (file-name) ":" line ":" column ": warning: " (message) line-end) (error line-start (file-name) ":" line ":" column ": " (message) line-end)) :modes flan-mode) ``` **Both patterns, and in that order.** A rejection is not labelled: `flan check` prints `file:line:col: message` and exits 1, so a checker with only the warning pattern reports "checker returned non-zero but no errors" on every real type error — the one case you most want it for. The error pattern is second because flycheck takes the first that matches a line, and the warning line would otherwise land in the error bucket with `warning: ` glued to the front of its message. The squiggle lines under each diagnostic and the closing `1 error` are matched by neither, which is fine. Only the file named on the command line is reported, so the prelude's own pushes — real, and none of your business — stay out of it. ### The watch buffer — values while the program runs Everything above is for a program you have stopped, or one you interrupt with a keystroke. **`M-x flan-watch`** is the other thing: a small buffer that shows values *while the game runs*, updating at frame rate. `M-x flan-watch-stop` closes it down, and killing the buffer does the same. **The program decides what is shown.** There is no watch list to maintain, no per-variable registration, no place to type an expression. The program says what it wants seen, from inside its own loop, with `(watch "name" value)`: ```flan (defn step [] i64 (set ticks (+ ticks 1)) (watch "ticks" ticks) (watch "player" player) ticks) ``` The value is rendered the way `print` renders it, so a struct, an array, a slice, an option or a dyn value watches as it prints: `(Pos {.x 3 .y 1.5})`, `[1 2 3]`. A string is quoted. The value is evaluated once whether or not a watch buffer is open. That is the whole of it. `C-c C-c` on `step` adds or removes a watched value the same way it changes anything else, so the watch list is edited in the place you were already looking. **Why it is pushed rather than polled.** Every other listing in this manual is a question the editor asks, which the daemon answers by compiling a small module and handing it to the program. That is fine at the rate you press a key and ruinous at the rate a HUD refreshes — each one is a new `.so`. So the direction is inverted: the program writes into a table, and the editor reads the table, which is memory. Two things follow that a poll could not have given. The values are as fresh as the last frame, whatever the repaint interval is set to. And **they are still there while the program is stopped** — a break loop is exactly when nothing can be run at a frame boundary, and exactly when you want to see what the last frame held. **What it costs when you are not watching.** Nothing writes the table until a watch buffer is open; `M-x flan-watch` tells the program somebody is looking and closing it tells the program to stop. So in a dev build a `watch` nobody is looking at costs a load and a branch that is not taken. In a release build `watch` makes no call at all: the value is evaluated and nothing else happens. A scalar entry point called through `declare-c` is an ordinary C call and costs the load and the branch in either build. **The limits.** The table holds **64 names**, and names past that are dropped rather than being fatal — the buffer says how many, because a value that simply never appeared would send you looking for a bug in the program. A name is truncated at 31 bytes and a rendered value at 192, with an ellipsis where a value was clipped. **A number sampled thousands of times a frame — `watch-num-i64`, `watch-num-f64`.** The scalar entry points keep one value per name, and from a hot inner loop that is nearly useless: you see whichever of the 91,200 cells ran last. These two are the other half. A slot keeps **count, min, max, last and mean**, and the buffer renders them as one line: ```flan (declare-c watch-num-i64 [name str x i64] i32 "flan_dev_watch_num_i64") (declare-c watch-num-f64 [name str x f64] i32 "flan_dev_watch_num_f64") (watch-num-i64 "cell" (at grid i)) ``` The slot renders as `n=… min=… max=… last=… mean=…`, one line per name. Two entry points rather than one so a program need not cast at the call site. The write path does **no formatting** — a sample is a load, five compares and the slot's seqlock — and the listener thread renders once per repaint, which is the whole reason this exists rather than a second `watch-i64`. **The window is since the last repaint**, not since the program started: a min and a max over a whole session reach the session's extremes within seconds and then never move again, so the two most useful of the five would go dead exactly when you start playing. A whole number prints as one, because a spy on an array index reading `66.0000` sends you looking for a rounding bug that is not there. **The scalar entry points.** `watch` is a form the compiler knows. The same table is also reachable as plain C functions, one per scalar type, which a program declares like any other: ```flan (declare-c watch-i64 [name str x i64] i32 "flan_dev_watch_i64") (declare-c watch-f64 [name str x f64] i32 "flan_dev_watch_f64") (declare-c watch-str [name str s str] i32 "flan_dev_watch_str") ``` Each returns 1 if the value was written and 0 if nobody is watching or the table is full. ### Ghost text — the same values, inline **`M-x flan-watch-ghost-mode`** shows each watched value *at the call that wrote it*, as faint text after the line, instead of in a buffer of its own: ```flan (defn step [] i64 (set ticks (+ ticks 1)) (watch-i64 "ticks" ticks) => 4812 ticks) ``` It is an **addition, not a replacement**, and the two can be on together. The buffer is what you want when you want everything at once; inline is what you want when you are reading one function and the value belongs beside the code rather than three windows away. `M-x flan-watch-ghost-mode` again turns it off, and turning either one off leaves the other running. **How it finds the place.** The table carries a name and a rendered string and no source location — that would need the `(watch ...)` form above. It does not have to: `(watch-i64 "ticks" ticks)` is *in the buffer you are looking at*, and the name in the table is the string literal in it, so the call site is found by searching the text. Nothing new is asked of the running program. If you bound your own entry points to names that do not start with `watch`, set `flan-watch-ghost-call-regexp` — the Flan name is yours, and only the C symbol behind it is fixed. **When it updates.** Each time the daemon sends the table, from the same frame as the buffer, so the two cannot disagree. As with the buffer, the interval decides how often the picture is repainted and not how fresh it is. Overlays are replaced wholesale each repaint rather than followed through your edits, so a line you moved never leaves one stranded, and a file you scroll to gets its values at the next repaint. Only buffers **shown in a window** are scanned, which is what keeps that cheap. **One name, two call sites.** Both show it, and both say `one slot, 2 sites`. That is the truth rather than a failure to choose: the table has one slot per name and the last call in the frame wins, so the value really is the same at both, and it is whichever ran last. In a **loop**, you see the last value written — the same answer the buffer gives. "The last of 4000 iterations" is often not the one you wanted, but every better answer is a UI for building a query, which is the thing this design exists not to have. **While the program is stopped**, the values are the last frame's — that is the point of pushing rather than polling — and each one says `last frame` and changes colour. The watch buffer does not need to, because you opened it on purpose and `flan:stopped(...)` is already in the modeline; inline, the value sits in the middle of code that looks perfectly live. **What gets no ghost text.** A watch call produced by a macro, or one whose name is not a literal: there is nothing in the source to find, and the editor is reading your file rather than debug info. Those still appear in the watch buffer. A site you have typed but not yet installed with `C-c C-c` has no value yet either — that is the ordinary stale-caller situation the modeline already tracks. And when the table is **full**, a site that found no slot says so rather than showing nothing, which is the one case where silence would send you looking for a bug in your program. --- ## A changed signature A function whose parameters or return type change is installed like any other change. The callers compiled against the old signature are still in the running program, and the reply names each one by file and line. They are listed in `*flan-diagnostics*`, where `next-error` visits them, and the echo area says how many there are. A stale caller never passes the old arguments to the new body. Each call to a Flan function in a dev build compares the signature the caller was compiled for with the one the function has now, and when they differ the program stops in the break buffer on a `StaleCall` condition. Its fields are the function called, the signature the call was compiled for, and the current one; the break names the call site. Evaluating the caller again compiles it against the new signature, and a restart resumes the program. ``` (defn scale [x i64] i64 (* x 2)) (defn step [] i64 (scale ticks)) ``` Evaluating `(defn scale [x i64 k i64] i64 (* x k))` installs the new `scale` and names `step` as a stale caller. The next call from `step` stops on `StaleCall`; evaluating `step` again, calling `(scale ticks 3)`, clears it. A call in `main` is the exception to that fix. While the program runs, it is inside the `main` it started with, and that body never returns to be called again, so evaluating `main` again does not reach the loop it is in. The listing says so for each such call, and the program keeps stopping there until `scale` is defined with the old signature again or the program is re-run with `M-x flan-rerun`. The same holds for any function the program never leaves. A function value taken before the change keeps the body it was taken from and goes on computing what it did. A value taken by a stale caller after the change stops where it is taken. Changing a signature back to the one a caller was compiled for makes that caller current again. `main` is the exception. The program's startup code calls it and was built for the signature it had, so changing `main`'s parameters or return needs a restart. A release build has none of this: it has no cells and no checks. ## When a change is refused ### A changed struct layout — the genuinely hard one Rejected while live values of that struct exist, and this one plan.org does still specify as a rejection. Storage already allocated has the old shape; a new body would read its fields at the wrong offsets and nothing at run time would say so. Managed classes are the planned way through — an explicit migration at a frame boundary — and they are not built either. ### The way out, for now `C-c C-x` stops the program, rebuilds from source, starts it again and reconnects. It costs the program's state, which is why it is a key you press rather than something `C-c C-c` quietly falls back to. ## Under the debugger **`C-c C-g`** (`flan-debug`, needs `flan-dape.el`) builds the current file with DWARF and stops it at `main` under lldb. Breakpoints are ordinary dape breakpoints set in the `.flan` buffer — the line table names your Flan file, not the generated LLVM IR. `dape-breakpoint-toggle` on a line, or `dape-breakpoint-global` to break on a function without hunting for its first line. lldb needs no plugin to read Flan values. A Flan struct *is* a C struct, a local is an ordinary stack slot, and there are no tag words or object headers anywhere, so lldb's own C support prints them correctly with nothing taught to it. Local variables show under their real names. One caveat: if you shadow a name — a `let` inside a `let`, both called `v` — both appear, the inner one as `v~2`, but plain `v` still answers with the *outer* one. Read `v~2` when you are inside the inner binding. --- ## Writing it `TAB` indents the line, and `C-M-q` the form under point. The rules are ported from `clojure-mode`'s, because Clojure has the shapes Flan has and Emacs Lisp does not — vectors that bind, bracket variety, and keys inside braces. **A binding vector lines up name under name.** The second and later bindings of a `let` sit under the first one's *name*, not under its value: ``` (let [vel (+ gravity (at velocity row col)) y (min (- rows 1) (+ row (i32 vel)))] …) ``` The same rule draws `defn` parameter lists, `restart-case` and `handler-bind` clause parameters, and both spellings of a struct literal — they are all a vector or a brace read in pairs, so they are all indented as one. **A body indents two.** `let`, `if`, `when`, `while`, `match`, `restart-case`, `handler-bind`, `defn` and the `def…` forms all put their body two columns in from the head. What differs between them is only how many forms come *before* the body and stay on the head's line — a `let`'s binding vector, an `if`'s test, a `restart-case`'s protected form — and the indenter knows that count per form. A form it has no entry for is treated as a call: the arguments line up under the first argument, not two in. That is the fallback, and it is what you want for `(rl/draw-rectangle x y w h)` and for every function you write. `defn` carries a return type between the parameter vector and the body, and it is optional. The indenter does not need to know which — everything after the head indents two, which is the right answer for the name, the parameters, a return type if one is written, and the body alike. **A field is drawn as a constant**, in the accessor `(.x v)` and as a label in `{.x 1.0}`. The colon is still a constant too; it means an enum member, `:green`, and a key in a map. **The keywords are the parser's.** Both lists — the forms that introduce a top-level name, and the forms with meaning to the checker — are read off `Parse.decl` and `Parse.form` in `lib/parse.ml` and kept whole, rather than topped up whenever somebody notices a gap. So `defmacro`, `defclass`, `defgeneric`, `defmulti`, `defmethod` and `declare-c` colour as definitions, and `when`, `cond`, `and`, `or`, `break`, `continue`, `recur`, `fn`, `quote`, `array`, `signal`, `error` and the four condition forms — `handler-bind`, `handler-case`, `restart-case`, `invoke-restart` — colour as keywords. A form the parser gives a meaning to and a function the compiler provides are two different things, and they are coloured apart: `let` and `match` are keywords, `push` and `slice` and `println` are builtins, and `true`, `false` and `None` stand for themselves. Types are types, `dyn` and a type variable like `$t` among them. And the package alias in `rl/draw-text` is drawn apart from the name after it, which is the one part of a qualified name you did not write. **A loop's label moves everything along by one.** `(dotimes :outer [i 3] …)` and `(until :count (> n 10) …)` indent their bodies two in, exactly as the unlabelled forms do — the label is not the thing the body lines up under. **`#_` greys out the form after it**, the way it reads: the discarded form is given the comment syntax class, so it is drawn as a comment and skipped by everything else that skips comments. Chaining works as the reader's does — `#_#_ a b` discards both, with nothing counting — and an unfinished form is left alone, so `#_(` does not grey the rest of the file while you are typing it. Nothing here needs a running program. Indentation and colouring are the major mode's, so they work in a file you have only opened. **With a program running, the names *it* knows are coloured too** — a macro as a macro, a function as a function, a global as a global, and a struct, data type, union, enum or alias as a type. That is the difference between a name the program has and a name you have misspelled: the misspelling stays grey. It follows the program, so a `defn` you have just evaluated is coloured from that moment, and a name you have not evaluated yet is not. This never repaints the language. A program that defines its own `length` does not get to change what `length` looks like; the language is drawn first and what is already drawn is left alone. Turn the whole of it off with `flan-font-lock-dynamically` if you would rather have one colour for every name, and a file with no session behind it looks the same either way. --- ## Getting around | Key | Does | |---|---| | `M-.` | jump to where a name is written | | `M-,` | jump back | | `C-c C-d` | what the running program currently defines | | `C-c C-v` | help on the name at point | | `C-c C-a` | disassemble a function; `C-u` first for its LLVM IR | | `C-c C-l` | every lowering of a function: IR, `-O0`, `-O2`, x86 backend | | `C-c C-m` | what the macro call at point expands to; `C-u` for all the way | Completion, eldoc, `M-.` and the colouring described under **Writing it** all read one cached answer rather than asking the program per keystroke. It refreshes at the two moments the answer can have changed: when you connect, and after an evaluation the daemon accepted. Each name in it comes with what kind of thing it is: `fn`, `macro`, `struct`, `data`, `union`, `enum`, `alias`, `var`, `const`, `extern` or `builtin`. That is what lets `C-c C-v` say which of them you are looking at, and what the colouring keys on. The answer covers the compiler's builtins as well as the program's own names, so `C-c C-v` on `arena-new` or `map-next` gives you its signature and a line about what it does. They are marked `builtin`, and they come after the program's names in a completion list. `M-.` on one refuses rather than jumping: it is written in the compiler, so there is no file to open. --- ## Every lowering of one function — `C-c C-l` `C-c C-l` on a name opens `*flan-lowering*`: the LLVM IR the frontend emits for that function, what `llc` makes of it at `-O0` and at `-O2`, and what the hand-written x86 backend emits, all narrowed to the one function. It is `tools/dump.sh` with a buffer around it. Reading one against another is the only way to check a lowering by eye, and the reason the second backend is trustworthy is that the two agree. The four are folding sections, so the buffer is a four-line summary when everything is shut: ``` ; every lowering of step ; file ~/src/game.flan ; compiler /home/joe/src/flan/_build/default/bin/main.exe ; flags none ; showing what this file compiles to, not what a running program is ; calling for this name -- for that, C-c C-a ▸ LLVM IR 8 lines ▸ LLVM -O0 13 lines ▸ LLVM -O2 10 lines ▾ x86 backend 19 lines 0000000000012d94 : 12d94: push %rbp ... ``` Which sections are open is remembered for the rest of the Emacs session, and it is remembered **by section, not by function**: if you are working on the backend this week, `C-c C-l` on a different name leaves the backend open and the other three shut. It is not written to disk — four booleans are not worth a preference file. | Key | Does | |---|---| | `TAB` | open or close the section point is in | | `S-TAB` | close everything, or open everything if it is all shut | | `c` | close every section | | `e` | open every section | | `n` / `p` | to the next or previous section heading | | `g` | compile the file again and redraw all four | | `r` | compile and redraw only the section point is in | | `q` | bury the buffer | `r` is worth the separate key because the four are not the same price: the IR is the frontend alone, and `llc -O2` over a whole program's worth of it is the one that is felt. Refreshing one section reuses the IR the other three were made from. Refreshing the IR drops everything, since everything is downstream of it. This is **not** `C-c C-a`, and no prefix argument turns one into the other. `C-c C-a` asks the *running program* what the body it is calling for a name actually came out as; only the daemon can answer that, because the daemon compiled that module and still has both its `.ll` and its `.so`. `C-c C-l` asks what the *file on disk* compiles to, by compiling it, and needs no program running at all. The two cannot be merged: neither `llc -O2` nor the x86 backend has ever been run over an installed body, so there is no four-way answer in the daemon to give. The `showing` line in each buffer's header says which of the two you are reading. `C-u C-c C-l` asks for the file as well as the name, for the case where the function you want to read is not in the buffer you are in. Every section is headed by the Flan forms it came from: each run of instructions has a comment above it quoting the form that produced it and where that form is written. The IR carries these comments itself. The two `llc` sections are compiled from the same IR with a line table added, which changes no instruction, and their line-table positions are shown as the forms. The x86 section reads the map from byte offsets to forms that the backend writes at the end of each function. At `-O2` the mapping is only as good as what LLVM keeps. `C-c C-a` shows source lines the same way, from what the daemon kept of each build. On the x86 backend, the default, every listing has them. On an LLVM session they need a line table, which only `flan dev --llvm --debug` has; a listing without one says so in its `note` line. The four outputs need `llc`, `as` and `objdump` on `PATH` — the backend writes machine code rather than mnemonics, so its section is assembled and disassembled to be readable, which is also a check that the bytes are well formed. A section whose tool is missing says so in place of its listing; the other three are unaffected. --- ## When something is wrong **An error draws an overlay** where it happened, with the message. It clears the next time that buffer's evaluation is accepted — so it disappears when you fix the thing rather than when you dismiss it. **And it is kept.** Every compiler message the editor is handed is also appended to `*flan-diagnostics*`, one entry per message with the time it arrived above it, and the buffer pops up when one lands — shown, not selected, so your typing stays where it was. The overlay is feedback about one command and is gone at the next keystroke; this is the copy you can still quote an hour later, without going through `*Messages*` for it. Nothing clears it — not connecting, not a successful evaluation, not quitting the program — until you say `M-x flan-clear-diagnostics`. Each entry's message line is left in the shape the compiler wrote it, `file:line:col: message`, so `RET`, `n` and `p` on one go to or between the code lines it names, and a line yanked out of the buffer reads the same as one pasted from a terminal. `M-x flan-show-diagnostics` brings it back by hand. **It is the one list for everything the compiler reports.** The errors are the log above; the allocation sites `M-x flan-check-memory` asks for are one section below them, replaced whole on every ask, navigable the same way. **A build that failed is in `*flan*`**, and it jumps too: the buffer is in `compilation-minor-mode`, so `M-g M-n`, `M-g M-p` and `RET` on a diagnostic take you to the line it names. That is the buffer to read when `M-x flan` reports a daemon that exited before it was ready — a `main` that does not compile means no socket at all, and this is the only account of why. **"No .flan-dev.sock found above this buffer"** — nothing is running, or you are outside the project. Start one with `M-x flan`. **The modeline says nothing about a program** — you are not connected. `C-c C-z`. **A restart you picked did nothing** — it should not happen silently any more, but if a restart is genuinely unreachable the buffer marks it and refuses with a reason. Read the reason. **The stack pane says it cannot show frames** — that is a real limit, not a bug. The conditions buffer reaches the program over a socket, and a socket cannot read another process's stack. The program stopped itself; it is not being debugged. Use `C-c C-g` if you need frames. --- ## Indented files (.fln) `.fln` files open in `flan-fln-mode`. The session keys (`C-c C-b`, `C-c C-i`, `C-c C-k`, the REPL, watch, dape) work as in a `.flan` file; these differ. | Holy | Evil | Does | |---|---|---| | `C-c C-c`, `C-M-x` | same | the top-level form: a declaration installed, anything else evaluated | | `C-u C-c C-c` | same | ...and stop at the innermost bracket group, else the statement on point's line: an elif's condition, an else's block or its value on the line, a match arm's value (`C-u C-u`: on entry) | | `C-x C-e` | same, cursor on the line's last character | at a line's end, the innermost statement ending there: a match arm's value, an if/elif/while condition, or the whole statement a header or clause line opens (a one-line `if c then a` with `else` under it included); elsewhere, the term before point | | `C-c C-e` | same | the statement at point with its body and clauses, or the region's whole lines; on a `let` or one of its binding lines, the `let`, its bindings and the rest of its block | | `C-c C-n` | same | `C-c C-e`, then move to the next statement | | `C-c C-s` | same | step through the top-level `fn` at point | | `C-c C-k` | same | the whole buffer | | `C-M-a` / `C-M-e` / `C-M-h` | `[[` / `]]` | top-level form: start, end, mark | | `M-a` / `M-e` | `(` / `)` | statement: start / end (`)`: start of the next) | | `C-M-u` | same | up to the enclosing bracket, or the line that owns the block | | `C-M-f` / `C-M-b` | same | brackets and terms, as everywhere | | `TAB` | same | a line at a valid column stays; an empty or misplaced line goes deepest; each repeat steps out a level. One level deeper only after a line that opens a block: never after a `let`, unless its value goes on under it (`= match x`, `= if c`, a lambda header). After a `let` line, the column under its first name is offered second, for its next binding (`let a = 1` and `b = 2` under `a`); after such a binding line it comes first, and `DEL` steps out to the `let`'s column. After a line ending in `=>`, one level in from that line, inside brackets too; a line of that block keeps to the block's columns. Inside open brackets, under the first element after the opener, or one level in from the opener's line when it ends that line; a line that starts with the closer goes there too, so `RET` in `and(a|)` leaves room for the next argument. The closer of a bracket that holds a lambda's block goes to the call's column | | `DEL` in indentation | same | drop one level | | `C-c <` / `C-c >` | `<` / `>` | shift the region's lines a level | | `M-` / `M-` | same | move the statement past its neighbour | | `M-` / `M-` | same | pull the next statement into this block / push its last one out | | `M-r` | same | replace the block's owner with the statement at point | | `M-k` | `das` | kill the statement's lines | | — | `ie` `ae` | term | | — | `is` `as` | statement (`as`: whole lines) | | — | `ii` `ai` | body / whole statement | | — | `ik` `ak` | clause's block / clause | | — | `id` `ad` | top-level form with the comment block directly above it (`ad`: and the empty lines after it, or before it for the last form) | `else`, `elif`, `on` and `restart` snap to their header's column as you type them, a one-line `if c then a` and a `let s = if c` included. `indent-region` and `C-y` move lines only as a block, never one line against another. expand-region steps term, group, statement, clause, enclosing statement, top-level form. - **term**: a run with no space outside brackets — `f(a, b)`, `grid[r, c]`, `p.x`. - **group**: a bracket pair and what is inside it. - **statement**: a line, the deeper lines under it, lines inside brackets it leaves open, lines an operator continues, and `else`/`elif`/`on`/`restart` at its column. A `let`'s binding lines, lined up under its first name, belong to the `let`. Blank and comment lines inside never end it. A lambda's block inside brackets, `sort-by(xs, fn(a, b) =>` and the lines under it, is part of the call's statement, and each of its lines is a statement too: `C-c C-e` or `C-u C-c C-c` there takes that line, not the call, and never the call's closing `)` at the end of it. - **body**: a statement's own block, up to its first clause. - **clause**: one `else`/`elif`/`on`/`restart` line and its block, or the value on its line (`else x`). - **top-level form**: a column-0 line that is code, not a clause and not a continuation, through the last code line before the next one. `flan-fln-indent-offset` (2) is one level. `flan-fln-smartparens` (`t`) turns on plain `smartparens-mode`, which pairs brackets and strings but not `'`. --- ## Full key reference | Key | Does | |---|---| | `C-c C-c` | the top-level form at point: a declaration installed, anything else evaluated | | `C-u C-c C-c` | ...and stop at the form point is inside (`C-u C-u`: on entry) | | `C-M-x` | the same as `C-c C-c`, on the binding SLIME and CIDER use | | `C-c C-k` | load the whole buffer, as one module; what does not compile is listed | | `C-c C-s` | install the defn at point to stop before each form of its body | | `C-x C-e` | the form before point, evaluated — or installed, if it is a declaration | | `C-u C-x C-e` | ...and stop at it instead of showing its value | | `C-c C-z` | connect (finds `.flan-dev.sock` upward) | | `C-c C-q` | disconnect | | `C-c C-o` | clear what the REPL's last send produced | | `C-c M-o` | clear the REPL transcript whole | | `C-c C-r` | a prompt on the running program | | `C-c C-b` | a stopped program: condition, restarts, stack | | `C-c C-M-b` | the same restarts, as a one-key prompt | | `C-c C-i` | inspect the expression at point | | `C-u C-c C-i` | ...or one you type in the minibuffer | | `C-c C-m` | what the macro call at point expands to | | `C-u C-c C-m` | ...all the way, rather than one step | | `C-c C-a` | disassemble; `C-u` first for LLVM IR | | `C-c C-l` | every lowering of a function, in folding sections | | `C-c C-g` | debug under lldb, through dape | | `C-c C-d` | what the running program defines | | `C-c C-v` | help on the name at point | | `C-c C-x` | rebuild, relaunch, reconnect | | `M-.` / `M-,` | where a name is written / back | Commands with no key: `M-x flan` (start a program), `M-x flan-quit` (stop it), `M-x flan-watch` (the watch buffer), `M-x flan-watch-stop`, `M-x flan-watch-ghost-mode` (the same values inline), `M-x flan-show-diagnostics` / `M-x flan-clear-diagnostics` (every compiler message this session has been handed, and emptying that), `M-x flan-inspect-address` (what is at an address), `M-x flan-macroexpand-all` (the `C-u` half of `C-c C-m`, by name), `M-x flan-allocations` / `M-x flan-leaks` (where the memory went, and what is still held), and `M-x flan-check-memory` / `M-x flan-clear-memory` (which lines allocate, marked in the buffer). --- ## Settings | Variable | Default | What it is | |---|---|---| | `flan-command` | `"flan"` | the compiler binary | | `flan-daemon-args` | `nil` | extra arguments for `flan dev` — `("--llvm")`, `("--debug")` | | `flan-socket-name` | `".flan-dev.sock"` | what `C-c C-z` searches for | | `flan-echo-result` | `t` | report an accepted evaluation in the echo area | | `flan-font-lock-dynamically` | `t` | colour names by what the running program says they are | | `flan-inline-result` | `t` | also show an expression's value at the end of its line | | `flan-names-shown` | `4` | how many names to list before summarising | | `flan-poll-interval` | `1.0` | seconds between checks for whether it stopped | | `flan-daemon-buffer` | `"*flan*"` | the daemon's own log; mirrors program output | | `flan-output-maximum-lines` | `5000` | lines `*flan*` and the REPL keep; older ones are deleted | | `flan-diagnostics-buffer` | `"*flan-diagnostics*"` | everything the compiler reports, kept | | `flan-start-timeout` | `60` | seconds to wait for a program to come up | | `flan-lower-buffer` | `"*flan-lowering*"` | where `C-c C-l` writes | | `flan-lower-program` | `"flan"` | the compiler `C-c C-l` shells out to | | `flan-lower-flags` | `nil` | flags for `flan emit` — `("--dev")` for the dev lowerings | | `flan-lower-llc` | `"llc"` | what the `-O0` and `-O2` sections are made with | | `flan-watch-buffer` | `"*flan-watch*"` | where watched values are painted | | `flan-watch-interval` | `0.2` | seconds between repaints — not the watch rate | | `flan-watch-ghost-call-regexp` | `"watch\(?:-[[:alnum:]]+\)?"` | the head of a call ghost text anchors on | Every one of these used to be spelled `flan-dev-…`, and so did the commands: `M-x flan-dev` is now `M-x flan`, `flan-dev-quit` is `flan-quit`, and so on through the file. Nothing answers to the old names — there are no aliases — so a `setq` or a keybinding in your config that names one will break, and the fix is to delete `-dev` from it. --- ## The files | File | What it is | |---|---| | `flan-mode.el` | the major mode: syntax, indentation, imenu, the keymap | | `flan-fln-mode.el` | the mode for indented `.fln` files: objects, keys, indentation | | `flan.el` | the client — the socket, evaluation, xref, eldoc, completion | | `flan-repl.el` | the `*flan-repl*` buffer | | `flan-watch.el` | watched values: the program pushes, this paints them in a buffer and inline | | `flan-cnr.el` | the conditions-and-restarts buffer | | `flan-inspect.el` | the value inspector | | `flan-lower.el` | the lowering buffer: four outputs, folding, and which was open | | `flan-dape.el` | lldb through dape; optional | There is no Flan parser in any of them. The client sends text and the compiler answers; anything that needs to know what a form means asks. ## The stack, and what a frame was holding `C-c C-b` opens the conditions-and-restarts buffer, and its Stack section is no longer empty. It lists the stopped program's frames, innermost first, each with where it is and how many named slots it has. `TAB` on a frame opens it (or `f`, which folds from anywhere on the frame) and shows what its locals hold — name, type and value, rendered the same way the inspector renders anything else. They are fetched the first time a frame is opened and then kept, because a stopped program's locals cannot change underneath you and a round trip behind a key that looks like folding would be a surprise. Two kinds of frame are marked. A `program` frame was on the stack when the error happened. An `eval` frame belongs to an expression you evaluated *inside* the break loop, sitting on top of them. Those are shown rather than hidden, on the same principle the unreachable restarts are: a frame you did not write is better explained than silently removed. Not everything can be shown, and what cannot is refused by name under the frame rather than left blank: - a slot the compiler invented, which has no name in your source — showing it as `s4` would put a variable in front of you that is not in the file; - a slot whose binding had not run yet when the error happened, which has no address to read; - a `Vec` or a pointer, which render as `` and `` here exactly as they do everywhere else. **A redefined body is refused, not guessed at.** Installing a fix while the program is stopped is deliberately allowed — it is the fix-it-and-retry loop — so the frame on the stack and the body the daemon holds can be two bodies of one function. A redefinition that renames a local changes neither the slot count nor the types, and showing the new names against the old storage would be a confident wrong answer in the one place someone is working out what went wrong. Each frame therefore carries a fingerprint of its body's slots, and a frame whose fingerprint no longer matches is refused by name with that reason. Redefining one function says nothing about the others, so every untouched frame still answers. ## The globals the stack is working on Under the stack there is one more section: the globals this stopped stack reaches. In this language that is often the more useful half — a game keeps most of its state in top-level `defonce`s, and `sand.flan` holds its entire grid that way. It is **one section, not a fold under each frame**. A global is not part of a frame; it is program state the frame happened to touch. Nesting it under one would imply an ownership that is not there and would repeat the name once per frame that reads it. It is **scoped to the stack, not to the program**. The contents are the union of the globals every frame on the stack references — the compiler already knows each function's reference set, so it does the choosing. Listing all of a program's globals instead would bury the one you want under the prelude's PRNG state. Each entry says **which frames touch it**, by the same number the Stack section labels them with. That recovers what per-frame nesting would have told you — "the whole chain is reading this" reads differently from "only the innermost does" — at no cost in duplication. And the order is **by the innermost frame that touches it**, because a deep stack makes the union large and proximity to the error is what puts the likely culprit on top. `i` works on a global line, and it roots differently there than on a local — which is the fix rather than an inconsistency. A global really is reached by name: the thunk the daemon loads binds to the program's own storage through the dynamic linker, so the name means the same thing wherever it is evaluated. A local is storage in one frame, and its name evaluated anywhere else may find a global, another binding of the same name, or nothing. So a local goes in by frame and slot index — the same two facts the listing was drawn from — and a global by name. See "Two ways to root a walk" above for what each can reach. Two things are said rather than left out. A global whose type the structural printer has no arm for is refused by name with the reason. And a frame the daemon could not attribute — one belonging to an expression you evaluated in the break loop, a lifted handler clause, or a body redefined since it was entered — is listed under "the union is incomplete", because a short list and a complete list look identical if nothing says which it is. **One hole worth knowing.** The redefinition check is a fingerprint over a body's *slots*. A new body that names different globals while binding exactly the same locals is not caught, and that frame's contribution will be the new body's reference set. The values shown are still read from the program's own storage and are still correct; what can be wrong is which frames an entry is annotated with. ## Stopping on purpose `(pause)` stops the program where it stands and hands it to the break loop. `C-c C-b` then shows the stack, `TAB` opens a frame's locals, and taking `continue` resumes at the call as though nothing happened. It is spelled `pause` rather than `break` because `break` is reserved for leaving a loop — the same word meaning "exit this loop" and "stop for inspection" in the same position would be the worst available collision. Nothing in the compiler implements it. It is an `error` under a `restart-case`, written in the prelude, which is what a breakpoint *is* in a language that already has conditions. One consequence worth knowing: a `handler-bind` above it can intercept a `Pause` and decline to stop, so a release build can neuter every breakpoint in the program without editing any of them. **Untested.** It compiles and the shape is right, but nobody has run it into a real break loop yet.