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.
92 lines
3.5 KiB
Plaintext
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)
|