spec-conditions.md §4 gains the rule the shadowing bug was hiding: a handler matches by name, a debugger identifies by position, and the two are not the same question. With it, the snapshot — a position means nothing against a stack that moves — and the fact that a visible restart may still be unreachable, which §6's explicit lowering makes possible. §3 records the open one: a clause should carry a report string. is what invoke-restart needs and not what a person reading a list needs. It wants settling before restarts with parameters, which is where a bare name is least sufficient. conditions.org had the break loop under "Not yet", which it has not been for some time, and now says why find-restart and compute-restarts still are: they are blocked on a Restart type and a list to return one in, not on effort.
78 lines
3.0 KiB
Org Mode
78 lines
3.0 KiB
Org Mode
#+TITLE: Conditions and restarts — cheatsheet
|
|
#+STARTUP: showeverything
|
|
|
|
Why it is shaped this way: [[file:spec-conditions.md][spec-conditions.md]]. Something to poke at:
|
|
=conditions-play.flan= — =flan dev conditions-play.flan=, then ~C-c C-c~.
|
|
|
|
* Works
|
|
|
|
#+begin_src lisp
|
|
(signal c) ; Unit. Handler returns -> carry on. No handler -> no-op.
|
|
(error c) ; Never. Only a transfer gets past; else the program stops.
|
|
|
|
(handler-bind [(Type [c] body ...) ...] body ...) ; match by type, no hierarchy
|
|
|
|
(restart-case BODY ; BODY and every clause have the same type = the form's
|
|
(name [] CLAUSE) ...)
|
|
|
|
(invoke-restart 'name) ; Never. Innermost frame offering the name wins.
|
|
#+end_src
|
|
|
|
#+begin_src lisp
|
|
(defn fetch [n i32] i32
|
|
(restart-case (middle n) ; its value if nothing transfers
|
|
(use-placeholder [] -1)
|
|
(retry [] 7)))
|
|
|
|
(handler-bind [(AssetMissing [c] (invoke-restart 'use-placeholder))]
|
|
(fetch 2)) ; -1
|
|
#+end_src
|
|
|
|
~defer~ between the invoke and the target runs, innermost first, before the
|
|
clause body. ~errdefer~ does not.
|
|
|
|
* The break loop
|
|
|
|
An unhandled ~error~ in a dev build stops on the frame that erred, with nothing
|
|
unwound, and waits. ~C-c C-b~ in Emacs lists what is on offer and resumes into
|
|
the choice; ~flan:stopped(Missing)~ in the modeline says it happened.
|
|
|
|
The list is *numbered*, and the number is what is chosen. Two frames offering
|
|
~retry~ both appear and §4's by-name walk can only ever reach the first, so a
|
|
name cannot say which one is meant — ~restart-at~ can.
|
|
|
|
A restart below the evaluation a break is inside is listed, marked, and
|
|
refused: ~C-x C-e~ runs its thunk through a C frame that holds its own transfer
|
|
channel, so an unwind aimed past it would stop at the thunk. Choose one offered
|
|
above it, or ~abort~.
|
|
|
|
* Not yet
|
|
|
|
~handler-case~ · ~find-restart~ · ~compute-restarts~ · restarts with
|
|
parameters. Each refused by name with its reason.
|
|
|
|
~find-restart~ and ~compute-restarts~ are blocked on a type rather than on
|
|
effort: §4 gives them ~(Option Restart)~ and a list, and there is no ~Restart~
|
|
type and no list to return one in. The break loop reads the same stack through
|
|
the agent's socket instead.
|
|
|
|
* Gotchas
|
|
|
|
- *A handler closes over nothing.* It is lifted into its own function.
|
|
Accumulate into a global, or put the value on the condition.
|
|
- *A restart re-runs whatever sits between it and the target.* Control resumes
|
|
at the ~restart-case~, so a ~retry~ repeats side effects after it. Put the
|
|
~restart-case~ where re-entry is safe.
|
|
- *An unknown restart name is a hard stop.* No ~find-restart~ to test with.
|
|
- *No supertype*, so nothing can say "any condition".
|
|
- *~signal~ cannot hand a value back.* Deliberate (§1).
|
|
- A condition must be a *struct*. ~return~ is refused inside either form.
|
|
|
|
* Stops the program, exit 134
|
|
|
|
#+begin_example
|
|
unhandled AssetMissing
|
|
file.flan:3:25: no restart named nope is active
|
|
file.flan:4:7: a defer invoked a restart, which a defer may not do — ...
|
|
#+end_example
|