;;;; 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. (defvar 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)