The slot after a defn's parameters is unconditionally a type. Parse.decl no
longer takes a set of type names, and is_type_form, qualified_type, types_in,
declared_types and prelude_types are gone with the pre-pass that fed them.
What they were for: (Option f64) and (Some 1) are the same s-expression, so the
parser decided which it had by looking the head up in a set of the file's own
type names. Sound -- one top-level namespace means a name cannot be both a type
and a value -- and brittle, because the set had to be complete. It was wrong
twice in one day, the second time parsing (defn f [] (Rune {.code 65}) (bar))
as a function returning a Rune with a one-form body, silently, in every file in
the language.
Two things fall out. A type the parser could not have known -- a struct
declared further down the file, rl/Vector2 behind an unresolved alias, a
prelude type -- never needed recognising, only placing. And a mistyped type is
a mistyped type: (defn f [] f65 0.0) reaches the resolver's near-miss check and
says did you mean f64, where it used to be read as the first form of the body
and reported as an unknown name.
Unit is written (). The old spelling is refused with a message naming the new
one, the rule the colon-to-dot change followed. Internally it is still
Tname "Unit" and Types.Unit, so the resolver, the shim and the emitter did not
change; Cimport still builds Tname "Unit" for C's void without going through
the parser. Types.to_string prints () though -- that printer prints what a
person would write for every other type it knows, [i32], {K V}, (Ptr T), and
Unit was the odd one out once the source spelling moved.
Dropping prelude_types removes one of the two reasons Macro.reduce may only
drop defns: the memoised set a bootstrap build could have poisoned is gone, so
the remaining reason is the plain one.
4.5 KiB
Conditions and restarts — cheatsheet
Why it is shaped this way: spec-conditions.md. Something to poke at:
conditions-play.flan — flan dev conditions-play.flan, then C-c C-c.
Works
(signal c) ; (). 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 [p T ...] CLAUSE) ...)
(invoke-restart 'name arg ...) ; Never. Innermost frame offering the name wins.
(defn supplied [n i32] i32
(restart-case (middle n)
(use-value [v i32] (* v 2)) ; the answer comes from outside
(retry [] 7)))
(handler-bind [(AssetMissing [c] (invoke-restart 'use-value 21))]
(supplied 7)) ; 42
A clause's parameters are slots of the function that wrote it, and the invoker fills a buffer that function owns — by the time a clause runs, the invoking frame has gone. What a clause takes is compared with what was given at run time, count then spelling, because a restart is found by name on a dynamic stack and neither end can see the other.
(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
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 · a clause's report
string. Each refused by name with its reason.
A restart with parameters cannot be taken from the break loop: it aims at a
frame by position and has nothing to fill the parameters in with, so the clause
stops the program rather than running on values no one supplied. Choose one
that takes none, or abort.
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 aretryrepeats side effects after it. Put therestart-casewhere re-entry is safe. - An unknown restart name is a hard stop. No
find-restartto test with. - So are the wrong arguments, and for the same reason: nothing static can know what a name will find. The message names both signatures.
- Lookup is by name; the signature is checked after it. Nothing searches for
a frame the arguments would fit. An inner
(use-value [s string] ...)shadows an outer(use-value [v i32] ...), so(invoke-restart 'use-value 21)stops the program even though the outer clause would have taken it. - No supertype, so nothing can say "any condition".
signalcannot hand a value back. Deliberate (§1).- A condition must be a struct.
returnis refused inside either form.
Stops the program, exit 134
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 — ...
file.flan:9:12: restart use-value takes (i32), given (string)
file.flan:6:5: restart use-value takes (i32), and whatever took it supplied
no arguments — ...