flan/test/programs/files.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

108 lines
5.1 KiB
Plaintext

;;;; The file surface beyond slurp and barf: file-exists?, file-size,
;;;; delete-file, rename-file and make-directory.
;;;;
;;;; The split down the middle of that list is the whole design and this file
;;;; is arranged to show it. The two that ask a *question* — is it there, how
;;;; big is it — answer a value, because absence is a reply and not a fault;
;;;; they are prelude functions over one declare and the compiler knows
;;;; nothing about them. The three that *change* the filesystem answer () and
;;;; signal FileError with the two restarts slurp and barf already establish,
;;;; because each of their failures is one a handler can act on: make the
;;;; parent directory and retry, or supply another path.
;;;;
;;;; Everything is made and removed inside this program, so it leaves the
;;;; directory as it found it — checked at the end rather than assumed.
;; Handlers cannot see the locals of the function that established them, so the
;; observations are globals, as in slurp.flan.
(defonce seen i64)
(defonce last-reason i32)
(defonce last-op i32)
(defn main [] i32
;; ── The questions ─────────────────────────────────────────────────
(println (file-exists? "programs/assets/a.txt")) ; true
(println (file-exists? "programs/assets/nope")) ; false
;; A directory resolves, which is what the name asks and not "is a regular
;; file" — a caller wanting the narrower question is asking a second one.
(println (file-exists? "programs/assets")) ; true
(match (file-size "programs/assets/a.txt")
(Some n) (println n) ; 13
None (println "missing"))
;; None folds every reason into one answer, which is the trade a question
;; with no restart on it makes.
(match (file-size "programs/assets/nope")
(Some n) (println n)
None (println "none"))
;; ── make-directory, rename-file, delete-file ──────────────────────
(make-directory "files-tmp")
(println (file-exists? "files-tmp")) ; true
(barf "files-tmp/one.txt" (bytes-view "0123456789"))
(match (file-size "files-tmp/one.txt")
(Some n) (println n) ; 10
None (println "missing"))
(rename-file "files-tmp/one.txt" "files-tmp/two.txt")
(println (file-exists? "files-tmp/one.txt")) ; false
(println (file-exists? "files-tmp/two.txt")) ; true
(delete-file "files-tmp/two.txt")
(println (file-exists? "files-tmp/two.txt")) ; false
;; ── retry, after the handler made the parent ──────────────────────
;; The restart this family exists for. Writing into a directory that is not
;; there is ENOENT, which arrives as `missing`; the handler makes the
;; directory and takes `retry`, and the second attempt succeeds. Nothing in
;; the failing code knows any of that happened.
(handler-bind
[(FileError [c]
(set seen (+ seen 1))
(set last-reason (.reason c))
(set last-op (.op c))
(make-directory "files-tmp/sub")
(invoke-restart 'retry))]
(barf "files-tmp/sub/deep.txt" (bytes-view "deep")))
(println seen) ; 1
(println (= last-reason file-missing)) ; true
(println (= last-op file-op-write)) ; true
(println (file-exists? "files-tmp/sub/deep.txt")) ; true
;; ── use-value, on a delete ────────────────────────────────────────
;; The same restart slurp's read offers, on an operation that writes: the
;; handler names a path that is there and the delete resumes against it.
(set seen 0)
(handler-bind
[(FileError [c]
(set seen (+ seen 1))
(set last-op (.op c))
(invoke-restart 'use-value "files-tmp/sub/deep.txt"))]
(delete-file "files-tmp/sub/not-there.txt"))
(println seen) ; 1
(println (= last-op file-op-delete)) ; true
(println (file-exists? "files-tmp/sub/deep.txt")) ; false
;; ── A non-empty directory does not delete ─────────────────────────
;; remove() is unlink or rmdir depending on what the path names, so an empty
;; directory goes by the same call a file does — and a full one does not,
;; which is deliberate: a recursive delete is a loop the caller writes and
;; sees. Here the handler declines to answer, which is what an unhandled
;; condition would do, so it counts and lets the program carry on by
;; supplying the child path instead.
(set seen 0)
(handler-bind
[(FileError [c]
(set seen (+ seen 1))
(set last-op (.op c))
(invoke-restart 'use-value "files-tmp/sub"))]
(delete-file "files-tmp"))
(println seen) ; 1
(println (= last-op file-op-delete)) ; true
;; And now it is empty, so it goes.
(delete-file "files-tmp")
(println (file-exists? "files-tmp")) ; false
0)