flan/test/programs/arena-value.flan
Joseph Ferano ca031d6b38 Every tracked Flan file is what the indenter would write, and a with- head indents its body like a definer
clojure-mode decided each shape. A with- head, and a def or with- head behind a namespace, indents as a body; a qualified head finds its unqualified spec. The rest was hand formatting: cond and match results under their tests, ordinary call arguments under the first one, and lone ; continuation comments rewritten as ;; lines above the code.
2026-09-25 10:19:22 +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 (length 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 (length outer))
(let [t (i64 0)]
(dotimes [i (length 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)