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

71 lines
2.7 KiB
Plaintext

;;;; Dyn maps and keywords: the literal, the operations, the printing, and a
;;;; collection loop that outruns the GC floor.
;;;;
;;;; Every value here is dyn. {:a 1} is a heap map the collector traces; :a is
;;;; an interned keyword, so two spellings of one name are the same word and
;;;; equality never reads the bytes; [1 2] where a dyn is wanted is the
;;;; runtime's own vec. The printed forms are the dyn renderer's — a space
;;;; before every element, strings quoted when nested — pinned here across
;;;; both backends the way test/dyn_ops.c pins them from C.
;; A dyn global: rooted once at startup, so what main stores in it survives
;; every collection the churn loop below causes.
(defonce config dyn)
(defn main [] i32
;; The literal, and what it prints as.
(let [m {:a 1 :b "two" :xs [1 2 3] :inner {:c 2.5}}]
(println m)
(println (len m))
(println (get m :a))
(println (get m :b))
(println (get m :xs))
(println (get (get m :inner) :c))
;; Absence is nil — an answer, not a trap — and has-key? is the question
;; that stays askable when nil might also be stored.
(println (get m :missing))
(println (= (get m :missing) nil))
(println (has-key? m :a))
(println (has-key? m :missing))
(put m :flag nil)
(println (get m :flag))
(println (has-key? m :flag))
;; put replaces an equal key's value in place; the length holds still.
(put m :a 99)
(println (get m :a))
(println (len m))
;; Keywords: identity equality, printing, and the runtime constructor —
;; (keyword "a") has to be the same word as the literal :a.
(println (= :a :a))
(println (= :a :b))
(println (= :a "a"))
(println (= (keyword "a") :a))
(println :standalone)
;; Keys are whole values compared structurally: a text and a keyword are
;; two keys, and a vec of numbers can key a map.
(put m "a" "text key")
(println (len m))
(println (get m "a"))
(put m [1 2] "vec key")
(println (get m [1 2]))
;; Maps compare structurally, by lookup and not by insertion order.
(println (= {:x 1 :y 2} {:y 2 :x 1}))
(println (= {:x 1} {:x 2}))
(println (= {:x 1} {:x 1 :y 2})))
;; The churn: enough map, vec and text allocation to pass the 1 MiB floor
;; many times over, against one live map held in a rooted global. A marker
;; that lost track of a map's keys or values frees something live, and the
;; sum at the end comes out wrong — or ASan speaks, in the sanitize sweep.
(set config {:total 0})
(dotimes [i 200000]
(let [row {:i 1 :s "forty-seven bytes of text to fatten each row" :v [1 2 3]}]
(put config :total (+ (get config :total) (len row)))))
(println (get config :total))
0)