diff --git a/emacs/MANUAL.md b/emacs/MANUAL.md new file mode 100644 index 0000000..95c5b98 --- /dev/null +++ b/emacs/MANUAL.md @@ -0,0 +1,323 @@ +# 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, `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 +``` + +You also need the `flan` binary on your `PATH`. If it is somewhere else, set +`flan-dev-command`. + +--- + +## Starting a program + +Two ways in, and they are different. + +**`M-x flan-dev`** 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. + +**`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-dev-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. + +--- + +## 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. + +### `C-x C-e` — evaluate an expression + +The expression before point is compiled, run **inside the running program**, and +its value printed in the echo area. Not a copy of the program, not a simulation — +the actual process, with its actual state. + +So in a game you can type `(len enemies)` and get the real number. + +### `C-c C-k` — the whole buffer + +The whole buffer, sent as **one** module rather than as a form at a time. That +matters: a `defvar` and the function that uses it have to arrive together, or the +function refers to storage that does not exist yet. + +Use this when you have changed several things at once, or when you have added a +new global. + +### 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. + +**`C-c C-o`** shows `*flan-output*` — whatever the program itself has printed. +That is separate from the REPL, because the program's stdout belongs to the +program. + +--- + +## 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 | +| `0`–`9` | take that restart by number | +| `TAB` / `n` | next restart | +| `S-TAB` / `p` | previous | +| `f` | fold a stack frame open or closed | +| `i` | inspect a local variable | +| `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. + +**`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. + +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. Give it an expression; you get its fields, one +per line. + +| 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 | +| `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 expression runs again on every step.** Going into a field sends a new +expression — `(.pos b)` where the last one was `b`. Appending a field name is +harmless, but the root 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. + +--- + +## When a change is refused + +Some changes cannot be made to a running program. Change a struct's layout, or a +function's signature, and the daemon refuses and tells you to restart. It is +being honest: the session's idea of your types has to describe the memory of the +process it is talking to, and after a layout change it no longer does. + +**`C-c C-x`** (`flan-dev-restart-program`) is the way out. It stops the program, +rebuilds from source, starts it again, and reconnects. + +The program's state goes with it. That is the whole cost, and it is why this is a +separate key rather than something `C-c C-c` quietly falls back to — losing your +game's state should be something you asked for. + +--- + +## 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. + +--- + +## 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 | + +Completion, eldoc and `M-.` 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. + +--- + +## 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. + +**"No .flan-dev.sock found above this buffer"** — nothing is running, or you are +outside the project. Start one with `M-x flan-dev`. + +**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. + +--- + +## Full key reference + +| Key | Does | +|---|---| +| `C-c C-c` | the top-level form at point, recompiled and installed | +| `C-c C-k` | the whole buffer, as one module | +| `C-x C-e` | the expression before point, evaluated in the running program | +| `C-c C-z` | connect (finds `.flan-dev.sock` upward) | +| `C-c C-q` | disconnect | +| `C-c C-o` | the running program's own output | +| `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 a value | +| `C-c C-a` | disassemble; `C-u` first for LLVM IR | +| `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-dev` (start a program), `M-x flan-dev-quit` +(stop it). + +--- + +## Settings + +| Variable | Default | What it is | +|---|---|---| +| `flan-dev-command` | `"flan"` | the compiler binary | +| `flan-dev-socket-name` | `".flan-dev.sock"` | what `C-c C-z` searches for | +| `flan-dev-echo-result` | `t` | print `C-x C-e`'s value in the echo area | +| `flan-dev-names-shown` | `4` | how many names to list before summarising | +| `flan-dev-output-buffer` | `"*flan-output*"` | where the program's output goes | +| `flan-dev-poll-interval` | `1.0` | seconds between checks for whether it stopped | +| `flan-dev-daemon-buffer` | `"*flan-dev*"` | the daemon's own log | +| `flan-dev-start-timeout` | `60` | seconds to wait for a program to come up | + +--- + +## The files + +| File | What it is | +|---|---| +| `flan-mode.el` | the major mode: syntax, indentation, imenu, the keymap | +| `flan-dev.el` | the client — the socket, evaluation, xref, eldoc, completion | +| `flan-repl.el` | the `*flan-repl*` buffer | +| `flan-cnr.el` | the conditions-and-restarts buffer | +| `flan-inspect.el` | the value inspector | +| `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.