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

92 lines
3.5 KiB
Plaintext

;;;; The recursive dynamic value, held in an arena and released by one
;;;; free-all. A reader handed no target struct type has to answer *something*,
;;;; and the something is this: a data type naming itself through a (Vec Value)
;;;; and a (Map string Value).
;;;;
;;;; Five refusals used to stand between here and a type like this, and every
;;;; one of them gave the same reason: the type-erased runtime copies and
;;;; releases slots bytewise, so a free would release the slots and leave what
;;;; they point at stranded. That reason is about *teardown*, and an arena has
;;;; none — free-all takes the whole region, and the inner blocks are in it
;;;; because they came out of it. So the refusals moved from the type, where
;;;; the allocator is not knowable, to the construction, where it is a value.
;;;;
;;;; There is no drop, no destructor, no finalizer and no per-element teardown
;;;; anywhere below. The release at the bottom of main is one call.
(defonce frame Allocator)
(defdata Value
[(Nil [])
(Int [n i64])
(Text [s string])
(List [items (Vec Value)])
(Table [entries (Map string Value)])])
;; [0 1 .. n-1] as a dynamic list. No allocator is named: with-allocator in
;; main has rebound the context, and spec-memory.md puts the allocator in the
;; calling convention precisely so that a builder like this need not carry one
;; through its signature.
(defn number-list [n i32] Value
(let [items (vec-new Value)]
(dotimes [i n]
(push items (Value.Int {.n (i64 i)})))
(Value.List {.items items})))
;; A table whose values are themselves lists, so the graph is three levels
;; deep before it reaches a leaf: Table -> Vec -> List -> Vec -> Int.
(defn a-table [] Value
(let [entries (map-new string Value)]
(put entries "xs" (number-list 3))
(put entries "ys" (number-list 5))
(put entries "name" (Value.Text {.s "edn"}))
(Value.Table {.entries entries})))
;; Reading it back. (at v i) addresses the element in place and (get m k)
;; answers a copy of the value's bytes, and in a region both are the same
;; thing: an alias into storage nobody individually owns. That is the bargain
;; a region is, and it is why no accessor beyond the two already here is
;; needed to walk a parsed document.
(defn total [v Value] i64
(match v
(Int n) n
(List items)
(let [t (i64 0)]
(dotimes [i (len items)]
(set t (+ t (total (at items i)))))
t)
(Table entries)
(+ (match (get entries "xs") (Some x) (total x) None (i64 0))
(match (get entries "ys") (Some y) (total y) None (i64 0)))
_ (i64 0)))
(defn build [] i64
(let [outer (vec-new Value)]
(dotimes [i 3]
(push outer (number-list (+ i 2))))
(push outer (a-table))
(push outer Value.Nil)
(println (len outer))
(let [t (i64 0)]
(dotimes [i (len outer)]
(set t (+ t (total (at outer i)))))
t)))
(defn main [] i32
(set frame (arena-new 65536))
(with-allocator frame
(println (build)))
;; The whole graph, in one operation. Every Vec block, every Map block and
;; every string the values point at came out of this region, so this is all
;; of it — and the epoch moves, so anything still holding one of those
;; headers traps rather than reading released bytes.
(free-all frame)
;; And the region is reusable, which is what makes it the frame tier: the
;; pages stayed, the offset went back to zero, and a second document builds
;; in the same bytes the first one used.
(with-allocator frame
(println (build)))
(free-all frame)
(arena-destroy frame)
0)