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.
358 lines
13 KiB
Markdown
358 lines
13 KiB
Markdown
# 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.
|
||
|
||
**The condition's fields are named and typed, and have no values.** Under the
|
||
condition you get the struct it is — `:path string`, `: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. 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
|
||
|
||
Two different things wear the same refusal today, and only one of them is the
|
||
design.
|
||
|
||
### A changed signature — a placeholder, not a rule
|
||
|
||
The intended behaviour, and what plan.org specifies, is that changing a
|
||
function's signature makes a **new version** of it: new callers resolve the new
|
||
one, existing callers and any stored `Fn` value stay safely on the old one, and
|
||
the session **warns** at each tracked stale caller site so you know what to
|
||
re-evaluate. Nothing should have to restart.
|
||
|
||
That needs function versions, trampolines and caller tracking, none of which are
|
||
built yet. Until they are, the session refuses rather than letting an
|
||
indirection cell hand old arguments to a new body — a wrong answer would be
|
||
worse than a refusal. `lib/session.ml` says so at the refusal itself, and
|
||
plan.org tracks it as open decision #6.
|
||
|
||
So if you hit this: it is a limitation with a date on it, not how the language
|
||
is meant to work.
|
||
|
||
### 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.
|
||
|
||
---
|
||
|
||
## 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.
|