The author: "I think I prefer length over len, because then I'll use len as the variable name". One arm in check.ml, one row in the table beside it, and every (len x) in lib, test, examples, vendor, spike, docs, web, emacs, plan.org and NEXT.md rewritten. Shadowing and builtin/ had already taken most of the sting out: a (defn len ...) was legal and won in its own file, and builtin/len reached past it. What was left is that len was still a builtin — the defn earned a warning, and a wrapper had to say builtin/ at every inner call. Now there is nothing under the short name: len is an ordinary identifier in every position, which is what (let [len (length xs)] ...) wants. length takes over as shadowing's worked example rather than the feature losing one. shadow-builtin.flan, builtin-qualified.flan, pkgs/shadowed and the builtin/ rows in test_flan move to it and go on testing shadowing. A call to a len nothing defines is answered where an unknown function is, after every table and after the shadowing guard, so a program with its own len never reaches it. The sentence is said rather than guessed at — len and length are three edits apart and the did-you-mean's net is one — and the call is written back out through spell_arg, as-slice's spelling lifted out of it and now shared, so what is printed compiles. sand.flan:33 still calls the old name and is the author's to change; until it does, test_acceptance and test_session abort there. Both were run green against a copy with that one line changed. FIX.org says so.
232 lines
9.0 KiB
Plaintext
232 lines
9.0 KiB
Plaintext
;;;; handler-case — the unwinding handler, spec-conditions.md.
|
|
;;;;
|
|
;;;; handler-bind runs its clause at the signal, with everything below still
|
|
;;;; standing, and carries on from there. This one is the other half: a listed
|
|
;;;; condition unwinds the stack back to the form, the clause runs *here*, and
|
|
;;;; its value is the value of the whole handler-case. Clojure's try/catch and
|
|
;;;; Common Lisp's handler-case, and built out of the two operators that were
|
|
;;;; already here — a handler-bind whose clause invokes a restart the form
|
|
;;;; established around itself.
|
|
;;;;
|
|
;;;; What this program pins is the list of things that only an unwind can get
|
|
;;;; wrong: the defers between the signal and the form, a condition nobody
|
|
;;;; listed carrying on outward untouched, the two nestings against
|
|
;;;; handler-bind, and a clause that signals — which must not be caught by the
|
|
;;;; handler-case it belongs to, because by the time it runs that form's frames
|
|
;;;; are off the stack.
|
|
;;;;
|
|
;;;; [log] is a digit trace rather than a running sum, which is cleanup.flan's
|
|
;;;; device and is here for cleanup.flan's reason: a sum commutes, so a backend
|
|
;;;; that ran the defers outermost-first would print exactly the same total as
|
|
;;;; one that got them right. A shift records the *order* and a wrong order is
|
|
;;;; a different number.
|
|
(defstruct Missing [id i32])
|
|
(defstruct Corrupt [id i32])
|
|
(defstruct Late [id i32])
|
|
|
|
(defonce log i64)
|
|
(defonce frame Allocator)
|
|
|
|
(defn note [n i64] () (set log (+ (* log 10) n)))
|
|
|
|
;;; Two frames below any handler-case here, each with a defer, so an unwind has
|
|
;;; something to cross and leaves a mark saying it crossed it — and says in
|
|
;;; which order it crossed them (§5: innermost first).
|
|
(defn inner [n i32] i32
|
|
(defer (note 1))
|
|
(error (Missing {.id n}))
|
|
0)
|
|
|
|
(defn middle [n i32] i32
|
|
(defer (note 2))
|
|
(+ (inner n) 1))
|
|
|
|
;;; Nothing signals: the body's own value stands, which is the case a form that
|
|
;;; only ever answers its clauses would quietly get wrong.
|
|
(defn quiet [n i32] i32
|
|
(handler-case (+ n 1)
|
|
[(Missing [c] -1)]))
|
|
|
|
;;; Caught, and the clause reads a local of the function that established the
|
|
;;; form. That is the whole difference from handler-bind, whose clause is
|
|
;;; lifted into a function of its own and can see no such thing.
|
|
(defn caught [n i32] i32
|
|
(let [bonus 100]
|
|
(handler-case (middle n)
|
|
[(Missing [c] (+ bonus (.id c)))])))
|
|
|
|
;;; Two clauses, and the one whose type was signalled is the one that runs.
|
|
(defn raise [k i32] i32
|
|
(cond
|
|
(= k 0) (error (Missing {.id 1}))
|
|
(= k 1) (error (Corrupt {.id 2}))
|
|
:else (error (Late {.id 3}))))
|
|
|
|
(defn two [k i32] i32
|
|
(handler-case (raise k)
|
|
[(Missing [c] (+ 100 (.id c)))
|
|
(Corrupt [c] (+ 200 (.id c)))]))
|
|
|
|
;;; A condition no clause lists installs no frame that matches it, so this form
|
|
;;; never sees it and it goes on outward unchanged. The outer handler-bind is
|
|
;;; what proves it arrived, and the 7 is what proves signal still returned ()
|
|
;;; and the body carried on from where it was.
|
|
(defn unmatched [n i32] i32
|
|
(handler-case
|
|
(do (signal (Corrupt {.id n}))
|
|
7)
|
|
[(Missing [c] -1)]))
|
|
|
|
;;; A handler-case inside a handler-bind. The Corrupt goes out to the
|
|
;;; handler-bind, which returns normally, so the body carries on; the Missing
|
|
;;; that follows unwinds to the handler-case in between.
|
|
(defn hc-in-hb [n i32] i32
|
|
(handler-bind [(Corrupt [c] (note 4))]
|
|
(handler-case
|
|
(do (signal (Corrupt {.id n}))
|
|
(error (Missing {.id n}))
|
|
0)
|
|
[(Missing [c] (.id c))])))
|
|
|
|
;;; And the other way round. The inner handler-bind is on the path the unwind
|
|
;;; takes, so its frame has to come off as the transfer passes through it —
|
|
;;; which is the same landing pad a restart transfer already uses.
|
|
(defn hb-in-hc [n i32] i32
|
|
(handler-case
|
|
(handler-bind [(Corrupt [c] (note 5))]
|
|
(do (signal (Corrupt {.id n}))
|
|
(error (Missing {.id n}))
|
|
0))
|
|
[(Missing [c] (* 2 (.id c)))]))
|
|
|
|
;;; A clause that signals. It runs with its own handler-case's frames already
|
|
;;; off the stack, so this Missing must not be caught here — that would be an
|
|
;;; unbounded loop rather than a wrong number. It has to leave this function,
|
|
;;; running the defer below on the way, and land further out.
|
|
(defn arm-signals [n i32] i32
|
|
(defer (note 3))
|
|
(handler-case (middle n)
|
|
[(Missing [c] (do (signal (Missing {.id 99}))
|
|
(.id c)))]))
|
|
|
|
(defn arm-caught-outside [n i32] i32
|
|
(handler-case (arm-signals n)
|
|
[(Missing [c] (+ 5000 (.id c)))]))
|
|
|
|
;;; A clause that returns. A clause runs at the form, in the function that
|
|
;;; wrote it, so a [return] there is an ordinary return from *this* function —
|
|
;;; not the refusal the body gets, where the frames are still standing. It has
|
|
;;; to run this function's own defer on the way out, after the two the unwind
|
|
;;; already ran, and it has to leave nothing on the handler stack: main signals
|
|
;;; once more afterwards with nothing listening, which would be a call into a
|
|
;;; frame that has gone if anything leaked.
|
|
(defn return-from-clause [n i32] i32
|
|
(defer (note 9))
|
|
(handler-case (middle n)
|
|
[(Missing [c] (return (+ 700 (.id c))))])
|
|
0)
|
|
|
|
;;; A handler-case inside a defer. A defer is itself the cleanup an unwind
|
|
;;; runs, so establishing frames in one has to work like establishing them
|
|
;;; anywhere — what a defer may not do is start a transfer that leaves it, and
|
|
;;; this one begins and ends inside. Two defers, so the order is pinned here
|
|
;;; too: the handler-case one is innermost and notes first.
|
|
(defn hc-in-defer [n i32] i32
|
|
(defer (note 8))
|
|
(defer (note (i64 (handler-case (error (Corrupt {.id n}))
|
|
[(Corrupt [d] (.id d))]))))
|
|
n)
|
|
|
|
;;; Two handler-cases written inside handler-bind clauses. Such a clause is
|
|
;;; lifted into a function of its own, so both of these mint their made-up
|
|
;;; restart name out of one shared bucket rather than out of a function's —
|
|
;;; which is exactly where two forms landing on the same name would show, the
|
|
;;; inner shadowing the outer wherever their extents overlapped. Each notes the
|
|
;;; id of the condition *it* caught, so the trace says they are two names.
|
|
(defn in-clause-a [] ()
|
|
(handler-bind
|
|
[(Late [c]
|
|
(note (i64 (handler-case (error (Corrupt {.id 6}))
|
|
[(Corrupt [d] (.id d))]))))]
|
|
(signal (Late {.id 0}))))
|
|
|
|
(defn in-clause-b [] ()
|
|
(handler-bind
|
|
[(Late [c]
|
|
(note (i64 (handler-case (error (Corrupt {.id 7}))
|
|
[(Corrupt [d] (.id d))]))))]
|
|
(signal (Late {.id 0}))))
|
|
|
|
;;; A with-allocator on the way out. It rebinds the context allocator for its
|
|
;;; extent and the transfer passes straight through it, so the restore has to
|
|
;;; happen on that path as well as on the normal one — otherwise the clause,
|
|
;;; and everything after the whole form, would be allocating out of a region
|
|
;;; nobody else knows about. main destroys the arena before it allocates
|
|
;;; again, so a context left pointing at it would not be a wrong number.
|
|
(defn scoped [n i32] i32
|
|
(handler-case
|
|
(with-allocator frame
|
|
(let [v (vec-new i32)]
|
|
(push v 1)
|
|
(error (Missing {.id n}))
|
|
(i32 (length v))))
|
|
[(Missing [c] (+ 20 (.id c)))]))
|
|
|
|
(defn main [] i32
|
|
;; Normal completion.
|
|
(print (quiet 41)) (println "")
|
|
(print log) (println "")
|
|
|
|
;; Caught, with both defers between the signal and the form having run, and
|
|
;; the trace saying inner's ran before middle's.
|
|
(print (caught 5)) (println "")
|
|
(print log) (println "")
|
|
|
|
;; The arm that matches is the arm that runs.
|
|
(print (two 0)) (println "")
|
|
(print (two 1)) (println "")
|
|
|
|
;; Unmatched: past this form and out to a handler-bind around it, and the
|
|
;; body's own value still stands.
|
|
(handler-bind [(Corrupt [c] (note 6))]
|
|
(print (unmatched 3)) (println ""))
|
|
(print log) (println "")
|
|
|
|
;; The two nestings. Neither signals from under [middle], so what each adds
|
|
;; to the trace is its own handler-bind's clause and nothing else.
|
|
(print (hc-in-hb 6)) (println "")
|
|
(print (hb-in-hc 8)) (println "")
|
|
(print log) (println "")
|
|
|
|
;; A clause that signals, caught by the handler-case outside it. Two unwinds,
|
|
;; so both defers under [middle] run and then the one in [arm-signals] does.
|
|
(print (arm-caught-outside 4)) (println "")
|
|
(print log) (println "")
|
|
|
|
;; A clause that returns, and then a signal nothing is listening for. The
|
|
;; second is the leak check: it must be the no-op §2 says it is.
|
|
(print (return-from-clause 5)) (println "")
|
|
(print log) (println "")
|
|
(signal (Missing {.id 0}))
|
|
(print log) (println "")
|
|
|
|
;; A handler-case established inside a defer.
|
|
(print (hc-in-defer 4)) (println "")
|
|
(print log) (println "")
|
|
|
|
;; Two forms out of the one bucket of made-up names, each answering its own.
|
|
(in-clause-a)
|
|
(in-clause-b)
|
|
(print log) (println "")
|
|
|
|
;; And the allocator scope. The arena is destroyed straight after, so the
|
|
;; heap allocation below is only possible if the context was put back.
|
|
(set frame (arena-new 4096))
|
|
(print (scoped 3)) (println "")
|
|
(arena-destroy frame)
|
|
(let [h (vec-new i32)]
|
|
(push h 9)
|
|
(print (length h)) (println "")
|
|
(free h))
|
|
0)
|