flan/test/programs/slurp.flan
Joseph Ferano a4c6b996ff def re-runs its initialiser, and defvar is renamed defonce
The trio the author decided on 2026-09-20 is now all built: def is CL's
defparameter — its initialiser runs on every daemon re-run, unguarded, so
an edited initialiser repaints the same storage on C-c C-c plus re-run —
defonce (Clojure's name for CL's defvar, per the author) initialises once
behind the .init~once. flag, and defconst stays the image.

One parse arm reads both forms; the difference is Ast.reinit, carried to
Tast.global's grerun. Emit.startup_plan gives a def no guard flag, and
Check.check_global lifts every def initialiser — zero and literal
included — into global/<n>, so the host's startup reaches it through the
function cell and a re-evaluated def swaps it (Session's def_inits;
Emit.redefinition declares the cell for a non-sibling target). The old
defvar spelling is refused with the rename and both compiling spellings,
and every program, test, doc and editor list is swept — except sand.flan,
the author's live WIP, whose seven defvar lines are flagged in FIX.org
and keep its three dependent tests red on this branch.
2026-09-21 07:12:04 +07:00

104 lines
4.5 KiB
Plaintext

;;;; 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.
(defonce seen i64)
(defonce last-reason i32)
(defonce last-op i32)
(defonce 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-view "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-view "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-view "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)