;;;; slurp and barf — NEXT.md decisions 2 and 5. ;;;; ;;;; slurp reads a whole file and answers a (Vec u8). It allocates, which is ;;;; why it waited for Vec, and it follows spec-memory.md's rule to the letter: ;;;; no allocating operation returns an error, so there is no Result here and ;;;; no out-parameter anywhere. ;;;; ;;;; Failure signals a condition under a restart, which is the pattern ;;;; StorageExhausted set this session. Two restarts, and they are the pair ;;;; Common Lisp establishes for a file-error: ;;;; ;;;; retry the file may be there now ;;;; use-value [p string] try this other path instead ;;;; ;;;; use-value is a *typed* restart — the second thing this session bought — ;;;; and it is the first one the compiler itself emits. Its parameter is the ;;;; path slot the attempt reads, so the clause body is empty: the invoker's ;;;; argument lands in the slot, the clause falls through, and the loop ;;;; re-attempts against the new path. ;; Handlers cannot see the locals of the function that established them, so the ;; observations are globals — the same shape exhausted.flan uses. (defvar seen i64) (defvar last-reason i32) (defvar last-op i32) (defvar last-path string) (defn main [] i32 ;; ── The happy path ──────────────────────────────────────────────── (let [v (slurp "programs/assets/a.txt")] (println (len v)) ; 13 (print (string (as-slice v))) ; hello from a (free v)) ;; Byte-exact, the same as an embed: nothing here decodes anything. (let [v (slurp "programs/assets/raw.bin")] (println (len v)) ; 4 (println (at v 0)) ; 0 (println (at v 2)) ; 255 (free v)) ;; ── use-value: a missing file, answered with another path ───────── ;; The textbook case. The handler does not know what slurp was going to do ;; with the bytes and does not have to: it names a file that is there and ;; the read resumes as if that had been asked for all along. (handler-bind [(FileError [c] (set seen (+ seen 1)) (set last-reason (.reason c)) (set last-op (.op c)) (set last-path (.path c)) (invoke-restart 'use-value "programs/assets/b.bin"))] (let [v (slurp "programs/assets/does-not-exist")] (println (len v)) ; 3 (println (string (as-slice v))) ; BBB (free v))) (println seen) ; 1 (println (= last-reason file-missing)) ; true (println (= last-op file-op-read)) ; true ;; The condition carries the path that actually failed, not the one that ;; eventually worked — the handler is told what it is answering about. (println last-path) ; programs/assets/does-not-exist ;; ── barf, and reading back what it wrote ────────────────────────── (barf "slurp-out.txt" (bytes "round trip\n")) (let [v (slurp "slurp-out.txt")] (println (len v)) ; 11 (print (string (as-slice v))) ; round trip (free v)) ;; barf's own failure signals the same condition with op = write. A directory ;; that does not exist is the reachable case on every platform. (set seen 0) (handler-bind [(FileError [c] (set seen (+ seen 1)) (set last-op (.op c)) (invoke-restart 'use-value "slurp-out.txt"))] (barf "no-such-dir/x.txt" (bytes "second\n"))) (println seen) ; 1 (println (= last-op file-op-write)) ; true (let [v (slurp "slurp-out.txt")] (print (string (as-slice v))) ; second (free v)) ;; ── retry: the file was not there, so the handler makes it ──────── ;; The other restart, and the one use-value cannot stand in for: here the ;; path is right and the world is wrong. The handler fixes the world and ;; re-attempts the *same* request, which is exactly what retry means for a ;; failed allocation too. (set seen 0) (handler-bind [(FileError [c] (set seen (+ seen 1)) (set last-reason (.reason c)) (barf "slurp-made.txt" (bytes "made by the handler\n")) (invoke-restart 'retry))] (let [v (slurp "slurp-made.txt")] (print (string (as-slice v))) ; made by the handler (free v))) (println seen) ; 1 (println (= last-reason file-missing)) ; true 0)