flan/test/programs/global-init.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

90 lines
3.5 KiB
Plaintext

;;;; Computed global initialisers — plan.org, Data model.
;;;;
;;;; A global whose value is not something a linker can write into the image.
;;;; The initialiser is lifted into a function of its own and called at startup,
;;;; from main, after the runtime is up and before a line of the program's own
;;;; code — Odin's __$startup_runtime shape rather than a constructor. Both
;;;; backends do it the same way, which is what the x86 survey is comparing.
;;;;
;;;; What each half of this file is asserting:
;;;;
;;;; - an arena allocated at startup and used from main, which is the form
;;;; the author kept writing: (defonce frame Allocator (arena-new N)).
;;;; - the order. `derived` is written above the global it reads, so the
;;;; declaration order is the wrong one and the sort is what makes it 30.
;;;; - a dependency that runs through a call rather than through the text of
;;;; the initialiser: `via-fn` names no global at all, and the function it
;;;; calls reads one.
;;;; - control flow in an initialiser. Every one of these needs a frame, and
;;;; before the lift there was none — a `let` in an initialiser indexed a
;;;; slot array of length zero and took the compiler down with it.
;;;; - a container loaded by its own initialiser, and a data type case
;;;; written into a global. Both were refused while there was nowhere for
;;;; an initialiser to run.
(defdata Shape [Nothing (Circle [r i32]) (Square [side i32])])
;; Written above `base`, and it reads it.
(defonce derived i64 (* base 10))
(defonce base i64 (+ 1 2))
(defn twice-base [] i64 (* base 2))
;; Names no global; the function it calls does.
(defonce via-fn i64 (+ (twice-base) 1))
;; The author's arena, and the Vec it is meant to hold.
(defonce frame Allocator (arena-new 262144))
;; Control flow, each shape in its own global.
(defn maybe [] (Option i32) (Some 3))
(defonce matched i32 (match (maybe) (Some x) x None 0))
(defonce branched i64 (if (> base 2) (let [k (+ base 1)] (* k 2)) 0))
(defonce counted i64 (let [t (i64 0)] (while (< t 4) (set t (+ t 1))) t))
;; A data type case: a store at startup, where a constant would have needed a
;; byte-level encoder for the payload blob.
(defonce shape Shape (Shape.Circle {.r 7}))
;; And a container whose real value only exists behind an allocator.
(defonce names (Vec i64) (vec-new i64))
;; [def], the re-run form, in a plain run — where there is no re-run, so each
;; is simply its initialiser's value. What these four pin is that every
;; spelling lowers and starts identically on both backends, with the
;; initialiser lifted into [global/<n>] whatever it is (a def's always is,
;; zero and literal included) rather than written into the image.
(def d-zero [2 u32])
(def d-typed i64 (+ base 2))
(def d-const i64 5)
(def d-dyn 6)
(defn main [] i32
(println derived)
(println base)
(println via-fn)
(println matched)
(println branched)
(println counted)
(match shape
(Circle r) (println r)
(Square s) (println s)
Nothing (println -1))
;; The Vec was made with the default allocator at startup and is still the
;; program's when main runs.
(push names 11)
(push names 22)
(println (len (as-slice names)))
(println (at names 1))
(free names)
;; And the arena, used the way sand.flan means to use it.
(with-allocator frame
(let [v (vec-new i64)]
(push v 7)
(println (at v 0))))
(free-all frame)
(println (i32 (at d-zero 0)))
(println d-typed)
(println d-const)
(println d-dyn)
0)