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.
This commit is contained in:
parent
9bb01b77df
commit
a4c6b996ff
73
FIX.org
73
FIX.org
@ -4343,3 +4343,76 @@ builds, which is a lane of its own.
|
|||||||
|
|
||||||
Not queued. The window is narrow, the fields are right, and the wrong sentence
|
Not queued. The window is narrow, the fields are right, and the wrong sentence
|
||||||
is a great deal better than the one it replaced.
|
is a great deal better than the one it replaced.
|
||||||
|
|
||||||
|
* def, and defvar renamed to defonce, 2026-09-20
|
||||||
|
|
||||||
|
** The gap, hit dogfooding
|
||||||
|
The author edited a ~defvar colors [4 u32] [...]~ initialiser and the colours
|
||||||
|
did not change on C-c C-c — which is defvar working exactly as designed, and
|
||||||
|
the wrong form for what he was doing. The re-run rule (the entry "Per-form
|
||||||
|
initialisation semantics on re-run" above) had already decided the trio in
|
||||||
|
his words: "It doesn't matter if you rerun that startup function, those 3
|
||||||
|
forms decide what happens." Two of the three were built; ~def~ — foreseen
|
||||||
|
there as "a def-style form that re-evaluates, recomputes on every run" — was
|
||||||
|
not. Now it is.
|
||||||
|
|
||||||
|
** The rename, author's words
|
||||||
|
"Rename to defonce, I think Clojure's name is better and more descriptive."
|
||||||
|
Clojure's ~defonce~ has exactly these semantics — define only if unbound — so
|
||||||
|
the name now says what the form does, next to a ~def~ that follows the
|
||||||
|
source. ~defvar~ is refused by name (~parse/defvar-renamed~) with the two
|
||||||
|
spellings that compile; every program, test, doc and editor list in the repo
|
||||||
|
is swept, except sand.flan, which is the author's live WIP and stays for the
|
||||||
|
merge.
|
||||||
|
|
||||||
|
** The trio
|
||||||
|
| form | initialiser runs | on a re-run |
|
||||||
|
|----------+-------------------------+------------------------------------|
|
||||||
|
| def | at startup, every run | repaints — the source's value wins |
|
||||||
|
| defonce | at startup, first run | keeps — the program's value wins |
|
||||||
|
| defconst | never — it is the image | untouched; it was never storage |
|
||||||
|
|
||||||
|
~def~ is Common Lisp's ~defparameter~, ~defonce~ is CL's ~defvar~ under
|
||||||
|
Clojure's name, and ~defconst~ is a compiler constant. Every spelling of the
|
||||||
|
third element (the four spellings pinned in test_flan.ml) holds for ~def~
|
||||||
|
exactly as for ~defonce~ — same parse arm, same collision rules, same
|
||||||
|
third-element dispatch — with one field of difference, [Ast.reinit], carried
|
||||||
|
to [Tast.global]'s [grerun].
|
||||||
|
|
||||||
|
** How a def reaches the next re-run
|
||||||
|
Two mechanisms, one per half of the promise:
|
||||||
|
|
||||||
|
- No guard flag. [Emit.startup_plan] gives a [defonce]'s computed initialiser
|
||||||
|
the [.init~once.] flag and gives a [def]'s none, so the store runs on every
|
||||||
|
entry into main. Shared plan, so the two backends cannot disagree.
|
||||||
|
- Always lifted. [Check.check_global] lifts *every* def initialiser into
|
||||||
|
[global/<n>] — zero and literal included, where a defonce keeps constants
|
||||||
|
inline in the image. The host's startup calls the initialiser through its
|
||||||
|
function cell, so when the author edits the form and C-c C-c's it,
|
||||||
|
[Session]'s [def_inits] hands [global/<n>] to the redefinition, the cell
|
||||||
|
swaps, and the next re-run stores the *edited* value into the same storage
|
||||||
|
— a native ~[4 u32]~ def repaints in place, so every reference sees the new
|
||||||
|
values. A constant left inline would have baked the stale value into the
|
||||||
|
host's startup body for ever. [Emit.redefinition] declares the cell for a
|
||||||
|
non-sibling target (a lifted initialiser's [fparent] is its global); the
|
||||||
|
x86 side already reached host cells through the GOT.
|
||||||
|
|
||||||
|
~uninit~ is the one exception on both forms: nothing to run, nothing to lift,
|
||||||
|
nothing repaints.
|
||||||
|
|
||||||
|
Pinned in test/programs/dev-rerun.flan (a ~(def c 3)~ printing 4, 4, 4, 4
|
||||||
|
beside a ~tally~ climbing 1..4; a ~(def hues [4 u32] ...)~ element printing 8
|
||||||
|
every run; then ~(def c 9)~ evaluated and the next re-run printing 10 while
|
||||||
|
the defonce beside it climbs on), in test_flan.ml (parse shapes for every
|
||||||
|
spelling of both forms, [grerun] on the pair, the lifted-initialiser claim,
|
||||||
|
def/defn and def/defonce collisions, the defvar teaching error verbatim), and
|
||||||
|
in programs/global-init.flan's last four lines (all def spellings start
|
||||||
|
identically on both backends and at both -O0 and -O2).
|
||||||
|
|
||||||
|
** Red on this branch, for the merger
|
||||||
|
sand.flan spells ~defvar~ at lines 15, 16, 24, 25, 26, 115 and 116 and was
|
||||||
|
not touched — same situation as the raylib keywords above. The three
|
||||||
|
sand-dependent tests (test_flan's parse pin, test_session's create,
|
||||||
|
test_acceptance's "a package's main is not visible") are red here and turn
|
||||||
|
green when those seven lines say ~defonce~ (or ~def~, where the author wants
|
||||||
|
the initialiser to follow the source — ~colors~ was the motivating one).
|
||||||
|
|||||||
20
NEXT.md
20
NEXT.md
@ -414,7 +414,7 @@ forms now and uses the one resolver that always existed.
|
|||||||
see [`docs/BUILT.md`](docs/BUILT.md), "`C-x C-e` expands too, and a declaration is not an expression". It was
|
see [`docs/BUILT.md`](docs/BUILT.md), "`C-x C-e` expands too, and a declaration is not an expression". It was
|
||||||
the wrap `Parse.decl` already had, and the decision it was waiting on came out as: an expression that
|
the wrap `Parse.decl` already had, and the decision it was waiting on came out as: an expression that
|
||||||
expands to a declaration is **refused by name**, in `Parse.expr`'s head dispatch, so a `defn` nested
|
expands to a declaration is **refused by name**, in `Parse.expr`'s head dispatch, so a `defn` nested
|
||||||
anywhere in the expansion and a `defvar` typed by hand get the same sentence. The spin refusal fires
|
anywhere in the expansion and a `defonce` typed by hand get the same sentence. The spin refusal fires
|
||||||
on this path; the ring never can, because a ring is refused while its own package is parsed. What
|
on this path; the ring never can, because a ring is refused while its own package is parsed. What
|
||||||
expands is the prelude's macros and the imported packages' — and, since the lane recorded just below,
|
expands is the prelude's macros and the imported packages' — and, since the lane recorded just below,
|
||||||
the file's own as well.
|
the file's own as well.
|
||||||
@ -455,7 +455,7 @@ the Emacs side". **Superseded** — the registry lane built that properly. Delet
|
|||||||
## For siam-farmer
|
## For siam-farmer
|
||||||
|
|
||||||
`docs/PORTING.md`'s verdict is unconditional now: the game's state is **fixed arrays with counts**, so it
|
`docs/PORTING.md`'s verdict is unconditional now: the game's state is **fixed arrays with counts**, so it
|
||||||
fits in `defvar` globals, nothing is move-only, and generics is off its critical path. Tier 0 and Tier 1
|
fits in `defonce` globals, nothing is move-only, and generics is off its critical path. Tier 0 and Tier 1
|
||||||
items 4, 5 and 6 are all done. Nothing blocks writing it.
|
items 4, 5 and 6 are all done. Nothing blocks writing it.
|
||||||
|
|
||||||
## Landed: a pointer from C needs a length before it can be indexed
|
## Landed: a pointer from C needs a length before it can be indexed
|
||||||
@ -555,10 +555,10 @@ field. Nor does a `pop` from a `Vec`. The case that wants both is **a fixed-capa
|
|||||||
allocation**, which is Odin's `Small_Array` and a good fit for a game that refuses to allocate in a frame:
|
allocation**, which is Odin's `Small_Array` and a good fit for a game that refuses to allocate in a frame:
|
||||||
|
|
||||||
```lisp
|
```lisp
|
||||||
(defvar enemies (Small-Array 64 Enemy))
|
(defonce enemies (Small-Array 64 Enemy))
|
||||||
```
|
```
|
||||||
|
|
||||||
`docs/PORTING.md`'s recommendation for the game's state was fixed arrays with counts held in `defvar` globals, kept
|
`docs/PORTING.md`'s recommendation for the game's state was fixed arrays with counts held in `defonce` globals, kept
|
||||||
deliberately away from `Vec` so nothing is move-only. `(Small-Array $n $t)` is that pattern with a type behind it
|
deliberately away from `Vec` so nothing is move-only. `(Small-Array $n $t)` is that pattern with a type behind it
|
||||||
instead of two variables kept in step by hand. That is the argument for building this, and it is stronger than
|
instead of two variables kept in step by hand. That is the argument for building this, and it is stronger than
|
||||||
"Odin has it".
|
"Odin has it".
|
||||||
@ -1420,7 +1420,7 @@ program, at that program's next frame boundary. Verified against sand: an unsave
|
|||||||
consecutive frames drew it.
|
consecutive frames drew it.
|
||||||
|
|
||||||
Steps 1, 2 and 3 are done — see *The reload primitive* in `docs/BUILT.md`. A list of top-level forms can be recompiled and installed
|
Steps 1, 2 and 3 are done — see *The reload primitive* in `docs/BUILT.md`. A list of top-level forms can be recompiled and installed
|
||||||
into a running process; call sites compiled before they existed follow them, and a `defn` or `defvar` the process was
|
into a running process; call sites compiled before they existed follow them, and a `defn` or `defonce` the process was
|
||||||
never built with can be added and then redefined again. That is the whole of `C-c C-c`, minus an editor: sand.flan takes
|
never built with can be added and then redefined again. That is the whole of `C-c C-c`, minus an editor: sand.flan takes
|
||||||
a redefinition over a socket and installs it between frames.
|
a redefinition over a socket and installs it between frames.
|
||||||
|
|
||||||
@ -2064,7 +2064,7 @@ binding takes no type, so `(let [pts [4 rl/Vector2]] ...)` reads `[4 rl/Vector2]
|
|||||||
fails with *unknown name rl/Vector2*. It cost 32 hand-written `Vector2`s in one raylib example.
|
fails with *unknown name rl/Vector2*. It cost 32 hand-written `Vector2`s in one raylib example.
|
||||||
|
|
||||||
`[4 T]` is not a special syntax — it is the ordinary type syntax and already works everywhere a type is expected:
|
`[4 T]` is not a special syntax — it is the ordinary type syntax and already works everywhere a type is expected:
|
||||||
`(defvar points [4 rl/Vector2] ...)`, `(defn draw [pts [4 rl/Vector2]] ...)`. A `let` binding is the single position
|
`(defonce points [4 rl/Vector2] ...)`, `(defn draw [pts [4 rl/Vector2]] ...)`. A `let` binding is the single position
|
||||||
with no type slot, which is the whole of the bug.
|
with no type slot, which is the whole of the bug.
|
||||||
|
|
||||||
`(zeroed [4 rl/Vector2])` was proposed first and rejected on how it reads: in argument position the bracket form is
|
`(zeroed [4 rl/Vector2])` was proposed first and rejected on how it reads: in argument position the bracket form is
|
||||||
@ -2228,13 +2228,13 @@ Ranked by how often they were hit, top two first because they are walls rather t
|
|||||||
chose between the three surfaces, and the first of them is not what was taken — see docs/BUILT.md, "`(array 4
|
chose between the three surfaces, and the first of them is not what was taken — see docs/BUILT.md, "`(array 4
|
||||||
rl/Vector2)`, and the one position with no type slot". The original entry:
|
rl/Vector2)`, and the one position with no type slot". The original entry:
|
||||||
|
|
||||||
A fixed array is either a top-level `defvar` or a literal with
|
A fixed array is either a top-level `defonce` or a literal with
|
||||||
every element spelled out. `(let [pts [4 rl/Vector2]] …)` parses as a two-element array literal and fails with
|
every element spelled out. `(let [pts [4 rl/Vector2]] …)` parses as a two-element array literal and fails with
|
||||||
*unknown name rl/Vector2*. Cost: 32 hand-written `Vector2`s in one example. **Looked at and stopped — it is a
|
*unknown name rl/Vector2*. Cost: 32 hand-written `Vector2`s in one example. **Looked at and stopped — it is a
|
||||||
grammar question, not a missing feature.** Everything under the surface is already there: `Ast.binding` carries a
|
grammar question, not a missing feature.** Everything under the surface is already there: `Ast.binding` carries a
|
||||||
`bty`, `load.ml` renames through it, and `check.ml:723` consumes it as the `want` for the value. Only the way it
|
`bty`, `load.ml` renames through it, and `check.ml:723` consumes it as the `want` for the value. Only the way it
|
||||||
is written is open, and the parser says so where it refuses (`parse.ml:366`): `let` is a flat list of pairs, so it
|
is written is open, and the parser says so where it refuses (`parse.ml:366`): `let` is a flat list of pairs, so it
|
||||||
cannot disambiguate by *count* the way `defvar` and `defconst` do — those read `[n t v]` as three arguments to a
|
cannot disambiguate by *count* the way `defonce` and `defconst` do — those read `[n t v]` as three arguments to a
|
||||||
form, and there is no such boundary between one pair and the next. Three surfaces, in the order they are worth
|
form, and there is no such boundary between one pair and the next. Three surfaces, in the order they are worth
|
||||||
considering:
|
considering:
|
||||||
- **`(zeroed [4 rl/Vector2])` — `zeroed` takes its type as an argument.** Recommended. It is one extra branch in
|
- **`(zeroed [4 rl/Vector2])` — `zeroed` takes its type as an argument.** Recommended. It is one extra branch in
|
||||||
@ -2264,7 +2264,7 @@ crosses as a parameter — a C function that returns one returns something Flan
|
|||||||
**The negative result is worth as much.** None of the gaps expected blocked anything — no generics, no allocator, no
|
**The negative result is worth as much.** None of the gaps expected blocked anything — no generics, no allocator, no
|
||||||
`Vec`/`Map`, no escaping closures, and function-scoped `defer` never came up. Input-and-draw over fixed-size state is
|
`Vec`/`Map`, no escaping closures, and function-scoped `defer` never came up. Input-and-draw over fixed-size state is
|
||||||
the shape the language already has. Three constructs unexercised anywhere else in the repo worked first try: a fixed
|
the shape the language already has. Three constructs unexercised anywhere else in the repo worked first try: a fixed
|
||||||
array with a struct element, a 2-D struct array, and `[N string]` as both `defconst` and mutable `defvar`.
|
array with a struct element, a 2-D struct array, and `[N string]` as both `defconst` and mutable `defonce`.
|
||||||
|
|
||||||
### The web target: what it does not reach yet
|
### The web target: what it does not reach yet
|
||||||
|
|
||||||
@ -2768,7 +2768,7 @@ index works — anything above 2³¹ truncates to a negative `i32` and the unsig
|
|||||||
is refused with the reason: 2³²+5 truncates to 5 and would read the wrong element with no trap at all.
|
is refused with the reason: 2³²+5 truncates to 5 and would read the wrong element with no trap at all.
|
||||||
- **There is one top-level namespace, and `check.ml` now enforces it.** The environment's tables are per-kind — structs,
|
- **There is one top-level namespace, and `check.ml` now enforces it.** The environment's tables are per-kind — structs,
|
||||||
unions, aliases, enums, functions, externs and globals each have their own — so only a function was ever checked for a
|
unions, aliases, enums, functions, externs and globals each have their own — so only a function was ever checked for a
|
||||||
duplicate. `(defn item …)` beside `(defvar item …)` type checked and then died in LLVM as `redefinition of function
|
duplicate. `(defn item …)` beside `(defonce item …)` type checked and then died in LLVM as `redefinition of function
|
||||||
'@flan.item'`, a message about an emitted symbol with no source location left, and two colliding *type* declarations
|
'@flan.item'`, a message about an emitted symbol with no source location left, and two colliding *type* declarations
|
||||||
were not caught anywhere. One pass over `Ast.declared_name` now runs before every other collection pass and rejects the
|
were not caught anywhere. One pass over `Ast.declared_name` now runs before every other collection pass and rejects the
|
||||||
second declaration of a name whatever kind either one is. `declared_name` lives in `ast.ml` because `Load` needs exactly
|
second declaration of a name whatever kind either one is. `declared_name` lives in `ast.ml` because `Load` needs exactly
|
||||||
|
|||||||
@ -83,7 +83,8 @@ let summarise (d : Flan.Ast.decl) =
|
|||||||
| Defdata (n, vs) -> Printf.sprintf "defdata %s (%d cases)" n (List.length vs)
|
| Defdata (n, vs) -> Printf.sprintf "defdata %s (%d cases)" n (List.length vs)
|
||||||
| Defunion (n, ms) ->
|
| Defunion (n, ms) ->
|
||||||
Printf.sprintf "defunion %s (%d members)" n (List.length ms)
|
Printf.sprintf "defunion %s (%d members)" n (List.length ms)
|
||||||
| Defvar (n, _, _) -> Printf.sprintf "defvar %s" n
|
| Defvar (n, _, _, Once) -> Printf.sprintf "defonce %s" n
|
||||||
|
| Defvar (n, _, _, Every) -> Printf.sprintf "def %s" n
|
||||||
| Defconst (n, _, _) -> Printf.sprintf "defconst %s" n
|
| Defconst (n, _, _) -> Printf.sprintf "defconst %s" n
|
||||||
| Declare (fn, csym) ->
|
| Declare (fn, csym) ->
|
||||||
Printf.sprintf "declare %s (%d params) = %s" fn.name (List.length fn.params)
|
Printf.sprintf "declare %s (%d params) = %s" fn.name (List.length fn.params)
|
||||||
|
|||||||
@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
;;; Handlers cannot see the establishing function's locals yet, so anything a
|
;;; Handlers cannot see the establishing function's locals yet, so anything a
|
||||||
;;; handler accumulates into has to be a global.
|
;;; handler accumulates into has to be a global.
|
||||||
(defvar seen i64)
|
(defonce seen i64)
|
||||||
(defvar ticks i64)
|
(defonce ticks i64)
|
||||||
|
|
||||||
;;; Two frames under the restart-case, so a transfer has something to cross.
|
;;; Two frames under the restart-case, so a transfer has something to cross.
|
||||||
(defn probe [n i32] i32
|
(defn probe [n i32] i32
|
||||||
|
|||||||
@ -723,11 +723,11 @@ forms handed to an evaluation are the one thing that was sent. `C-c C-c` has alw
|
|||||||
reason, and a `defmacro` typed at the REPL becomes an ordinary `Ast.Defn` that nothing records as a macro. Left where
|
reason, and a `defmacro` typed at the REPL becomes an ordinary `Ast.Defn` that nothing records as a macro. Left where
|
||||||
it was rather than half-fixed here, and pinned in `test_session` so that changing it is a decision.
|
it was rather than half-fixed here, and pinned in `test_session` so that changing it is a decision.
|
||||||
|
|
||||||
**An expression that expands to a declaration is refused, by name.** `defn`, `defvar`, `defconst`, `defstruct`,
|
**An expression that expands to a declaration is refused, by name.** `defn`, `def`, `defonce`, `defconst`, `defstruct`,
|
||||||
`defdata`, `defenum`, `defalias`, `defmacro` and `import` are heads `Parse.expr` now rejects — the arm that used to
|
`defdata`, `defenum`, `defalias`, `defmacro` and `import` are heads `Parse.expr` now rejects — the arm that used to
|
||||||
say it for `defmacro` alone, generalised. It sits in the head dispatch and not in a walk over what the expander
|
say it for `defmacro` alone, generalised. It sits in the head dispatch and not in a walk over what the expander
|
||||||
answered, so it catches a declaration nested anywhere in the expansion for free, catches one **typed** by hand with the
|
answered, so it catches a declaration nested anywhere in the expansion for free, catches one **typed** by hand with the
|
||||||
same sentence instead of "unknown name defvar", and cannot drift out of sync with `decl`'s list the way a second copy
|
same sentence instead of "unknown name defonce", and cannot drift out of sync with `decl`'s list the way a second copy
|
||||||
would. A *quasiquoted* declaration is deliberately not caught: after desugaring, the name in `` `(defn ...) `` is a
|
would. A *quasiquoted* declaration is deliberately not caught: after desugaring, the name in `` `(defn ...) `` is a
|
||||||
string inside a `Form.Sym` argument rather than a head — the same property that makes a quasiquoted call output rather
|
string inside a `Form.Sym` argument rather than a head — the same property that makes a quasiquoted call output rather
|
||||||
than a dependency. Building a declaration as a value is what a macro is for; evaluating one is not a thing an
|
than a dependency. Building a declaration as a value is what a macro is for; evaluating one is not a thing an
|
||||||
@ -808,7 +808,7 @@ fact without cutting anything in half. See "The browser is the third target" bel
|
|||||||
- `(defconst gravity 0.05)` → `(defconst gravity f32 0.05)`. An untyped float constant is `f64`, `velocity` is `[f32]`,
|
- `(defconst gravity 0.05)` → `(defconst gravity f32 0.05)`. An untyped float constant is `f64`, `velocity` is `[f32]`,
|
||||||
and `f64` into `f32` is a narrowing — still written, and still written after implicit widening landed (FIX.org
|
and `f64` into `f32` is a narrowing — still written, and still written after implicit widening landed (FIX.org
|
||||||
2026-09-20), because widening is only the conversions that cannot change the number and this one can.
|
2026-09-20), because widening is only the conversions that cannot change the number and this one can.
|
||||||
- `(defvar current-color u32)` → `i32`. It is an index into `colors`, and `(len colors)` is an `i32`.
|
- `(defonce current-color u32)` → `i32`. It is an index into `colors`, and `(len colors)` is an `i32`.
|
||||||
- `(defn main [])` is unchanged — the short form, as plan.org says.
|
- `(defn main [])` is unchanged — the short form, as plan.org says.
|
||||||
|
|
||||||
Painting is on **hold left mouse button** rather than on space, since the mouse bindings exist now. Space is still what
|
Painting is on **hold left mouse button** rather than on space, since the mouse bindings exist now. Space is still what
|
||||||
@ -836,7 +836,7 @@ of those is not what passing `-fsanitize=address` to the clang run over the `.ll
|
|||||||
|
|
||||||
**AddressSanitizer is an LLVM pass, but it instruments only functions carrying the `sanitize_address` attribute.** That
|
**AddressSanitizer is an LLVM pass, but it instruments only functions carrying the `sanitize_address` attribute.** That
|
||||||
attribute is put there by clang's C frontend. `Emit` writes `.ll` by hand, so it wrote none, so the pass walked past
|
attribute is put there by clang's C frontend. `Emit` writes `.ll` by hand, so it wrote none, so the pass walked past
|
||||||
every Flan function and instrumented `flan_rt.c`. The measurement that settled it: an out-of-bounds read of a `defvar`
|
every Flan function and instrumented `flan_rt.c`. The measurement that settled it: an out-of-bounds read of a `defonce`
|
||||||
array in a `--no-bounds-checks` build printed its garbage and exited 0; with an `attributes #0 = { sanitize_address }`
|
array in a `--no-bounds-checks` build printed its garbage and exited 0; with an `attributes #0 = { sanitize_address }`
|
||||||
group named on every `define`, the same program reports `global-buffer-overflow in flan.main`. Globals are the exception
|
group named on every `define`, the same program reports `global-buffer-overflow in flan.main`. Globals are the exception
|
||||||
— the module pass redzones them whether or not any function is attributed — which is why the *shape* of a sanitized
|
— the module pass redzones them whether or not any function is attributed — which is why the *shape* of a sanitized
|
||||||
@ -862,7 +862,7 @@ wrong answer instead of touching memory. Same family as `(<< 1 32)` compiling to
|
|||||||
`bounds.flan`'s six deliberate out-of-bounds cases are caught. A negative index into a global is not, and the reason is
|
`bounds.flan`'s six deliberate out-of-bounds cases are caught. A negative index into a global is not, and the reason is
|
||||||
layout rather than anything about the access: ASan lays a global out as `{data, redzone}`, so reading before one lands
|
layout rather than anything about the access: ASan lays a global out as `{data, redzone}`, so reading before one lands
|
||||||
in whatever precedes it, which is a redzone if something instrumented is there and ordinary memory if nothing is.
|
in whatever precedes it, which is a redzone if something instrumented is there and ordinary memory if nothing is.
|
||||||
Measured both ways — silent in `bounds.flan`, reported as soon as another `defvar` is declared in front of `arr`. A
|
Measured both ways — silent in `bounds.flan`, reported as soon as another `defonce` is declared in front of `arr`. A
|
||||||
reversed slice is not caught either, having computed a negative length and read nothing at all. And ASan sees
|
reversed slice is not caught either, having computed a negative length and read nothing at all. And ASan sees
|
||||||
out-of-*object* access, not out-of-subobject, so a slice into the middle of a larger array can overrun its logical
|
out-of-*object* access, not out-of-subobject, so a slice into the middle of a larger array can overrun its logical
|
||||||
bounds without crossing a redzone. Three of six is a ceiling on what it covers, not a measurement of the risk. It is a
|
bounds without crossing a redzone. Three of six is a ceiling on what it covers, not a measurement of the risk. It is a
|
||||||
@ -1097,7 +1097,7 @@ the sand hash is the case that matters, since it is the one result that would no
|
|||||||
|
|
||||||
### Names that did not exist when the process started
|
### Names that did not exist when the process started
|
||||||
|
|
||||||
Editing a `defvar` or a `defn` is a symbol the host exports. *Adding* one is not: there is no symbol to bind to and ELF
|
Editing a `defonce` or a `defn` is a symbol the host exports. *Adding* one is not: there is no symbol to bind to and ELF
|
||||||
cannot grow one. Those go through `runtime/flan_dev.c`, which is two lookups and nothing else:
|
cannot grow one. Those go through `runtime/flan_dev.c`, which is two lookups and nothing else:
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -1230,7 +1230,7 @@ Open decision #6 records it the same way. None of the three parts exists: there
|
|||||||
the alternative to refusing is not the new design, it is a silent argument mismatch. It is a stopgap and the message
|
the alternative to refusing is not the new design, it is a silent argument mismatch. It is a stopgap and the message
|
||||||
should not be read as the final answer.
|
should not be read as the final answer.
|
||||||
|
|
||||||
A `defvar`'s *initial value* is deliberately **not** in that table. Its storage holds live state the program moved past
|
A `defonce`'s *initial value* is deliberately **not** in that table. Its storage holds live state the program moved past
|
||||||
long ago, and refusing to change the initialiser would be refusing "edit the code, keep the sand". Same `Tast.global`
|
long ago, and refusing to change the initialiser would be refusing "edit the code, keep the sand". Same `Tast.global`
|
||||||
record as a `defconst`, opposite answers, told apart by `gconst`. The enum comparison runs over declarations rather than
|
record as a `defconst`, opposite answers, told apart by `gconst`. The enum comparison runs over declarations rather than
|
||||||
the checked program, because `Tast.program` carries no enums at all — they are erased to `i32` in the checker, which is
|
the checked program, because `Tast.program` carries no enums at all — they are erased to `i32` in the checker, which is
|
||||||
@ -1238,7 +1238,7 @@ the same fact that makes them unreloadable.
|
|||||||
|
|
||||||
Note what the checker catches on its own: change `helper`'s parameter type and the *caller* fails to type check first,
|
Note what the checker catches on its own: change `helper`'s parameter type and the *caller* fails to type check first,
|
||||||
loudly. The session's rules only get a turn on a change the checker accepts — one to a name nothing else in the program
|
loudly. The session's rules only get a turn on a change the checker accepts — one to a name nothing else in the program
|
||||||
uses, which is exactly where the silent version lives. The fixtures carry an unused `defvar` and a C-called `defn` for
|
uses, which is exactly where the silent version lives. The fixtures carry an unused `defonce` and a C-called `defn` for
|
||||||
that reason.
|
that reason.
|
||||||
|
|
||||||
A `defconst` the checker never consumed is a different matter and **can** be changed: it is only ever bytes in memory. A
|
A `defconst` the checker never consumed is a different matter and **can** be changed: it is only ever bytes in memory. A
|
||||||
@ -1276,7 +1276,7 @@ the real buffer path, which is the same key CIDER's `eval` carries.
|
|||||||
### The daemon — `flan dev`
|
### The daemon — `flan dev`
|
||||||
|
|
||||||
`flan dev <program.flan>` holds one `Session`, builds the program, launches it, and listens on `.flan-dev.sock` beside
|
`flan dev <program.flan>` holds one `Session`, builds the program, launches it, and listens on `.flan-dev.sock` beside
|
||||||
the source. What it adds over `flan reload` is that the session *persists* — a `defvar` added by one evaluation is part
|
the source. What it adds over `flan reload` is that the session *persists* — a `defonce` added by one evaluation is part
|
||||||
of what the next one is checked against — and that it **owns the build**, which is what makes its layout rules describe
|
of what the next one is checked against — and that it **owns the build**, which is what makes its layout rules describe
|
||||||
the process that is actually running rather than a guess about it.
|
the process that is actually running rather than a guess about it.
|
||||||
|
|
||||||
@ -1557,7 +1557,7 @@ which is ordinary rather than an error — `flan dev` ends when its program does
|
|||||||
socket it was on. Strictly *before* a send, never after one: a connection that died mid-request may have died after the
|
socket it was on. Strictly *before* a send, never after one: a connection that died mid-request may have died after the
|
||||||
daemon ran what it was given, and resending would install it twice or evaluate a side-effecting expression twice.
|
daemon ran what it was given, and resending would install it twice or evaluate a side-effecting expression twice.
|
||||||
|
|
||||||
`C-c C-k` sends one module rather than a form at a time on purpose: a `defvar` and the function that uses it have to
|
`C-c C-k` sends one module rather than a form at a time on purpose: a `defonce` and the function that uses it have to
|
||||||
arrive in the same load, or the first refers to storage that does not exist yet.
|
arrive in the same load, or the first refers to storage that does not exist yet.
|
||||||
|
|
||||||
**Framing is in bytes and Emacs counts characters.** Every length goes through `string-bytes` and the process is binary,
|
**Framing is in bytes and Emacs counts characters.** Every length goes through `string-bytes` and the process is binary,
|
||||||
@ -2107,7 +2107,7 @@ size of the slot — 40KB for sand's grid — and the walk only ever shows eight
|
|||||||
### Globals of a stopped stack
|
### Globals of a stopped stack
|
||||||
|
|
||||||
The other half, and in this language arguably the more useful one: a game keeps most of its state in top-level
|
The other half, and in this language arguably the more useful one: a game keeps most of its state in top-level
|
||||||
`defvar`s and `sand.flan` holds its entire grid that way, so "what is this program's state right now" was a question
|
`defonce`s and `sand.flan` holds its entire grid that way, so "what is this program's state right now" was a question
|
||||||
with nowhere to ask it.
|
with nowhere to ask it.
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -2477,7 +2477,7 @@ container and not a Vec question.
|
|||||||
`let` has no type annotation — `parse.ml` settles that a triple binding is ambiguous and that types are inferred — so a
|
`let` has no type annotation — `parse.ml` settles that a triple binding is ambiguous and that types are inferred — so a
|
||||||
local Vec has nowhere to say what it holds and the element type is written at the call: `(vec-new i32)`. This is *not*
|
local Vec has nowhere to say what it holds and the element type is written at the call: `(vec-new i32)`. This is *not*
|
||||||
the explicit instantiation syntax the generics section rules out: nothing here is generic, and the name resolves as an
|
the explicit instantiation syntax the generics section rules out: nothing here is generic, and the name resolves as an
|
||||||
ordinary type rather than binding a type variable. Where the context does say — a `defvar`'s type, a return type, an
|
ordinary type rather than binding a type variable. Where the context does say — a `defonce`'s type, a return type, an
|
||||||
argument — it may be left out.
|
argument — it may be left out.
|
||||||
|
|
||||||
A zeroed Vec has a null allocator, and the first operation that needs storage adopts the context allocator, which is
|
A zeroed Vec has a null allocator, and the first operation that needs storage adopts the context allocator, which is
|
||||||
@ -2833,7 +2833,7 @@ cannot collide. `(Name {.field value})` is one syntax for both, and which it is,
|
|||||||
|
|
||||||
Tag *n* is the *n*th declared case, from zero. The consequence is that an all-bytes-zero union is **the first declared
|
Tag *n* is the *n*th declared case, from zero. The consequence is that an all-bytes-zero union is **the first declared
|
||||||
case with a zeroed payload** — which is exactly the rule that makes an `Option`'s zero a `None`, and it is what makes
|
case with a zeroed payload** — which is exactly the rule that makes an `Option`'s zero a `None`, and it is what makes
|
||||||
`(defvar u U)` and an omitted union-typed struct field mean something rather than nothing. Reordering a union's cases
|
`(defonce u U)` and an omitted union-typed struct field mean something rather than nothing. Reordering a union's cases
|
||||||
is a layout change, the same way reordering a struct's fields is.
|
is a layout change, the same way reordering a struct's fields is.
|
||||||
|
|
||||||
### What is refused, and why each
|
### What is refused, and why each
|
||||||
@ -2856,7 +2856,7 @@ is a layout change, the same way reordering a struct's fields is.
|
|||||||
cannot be reached. That is the one place where garbage becomes "the optimiser may do anything".
|
cannot be reached. That is the one place where garbage becomes "the optimiser may do anything".
|
||||||
- **`(.x u)`.** A union's fields belong to a case, and which case is being held is what the tag says. `match` is how
|
- **`(.x u)`.** A union's fields belong to a case, and which case is being held is what the tag says. `match` is how
|
||||||
one is opened, and its arms bind the fields of the case they matched.
|
one is opened, and its arms bind the fields of the case they matched.
|
||||||
- **A global initialised with a case.** `(defvar g U (U.B {.x 1}))` would mean serialising the fields into the payload
|
- **A global initialised with a case.** `(defonce g U (U.B {.x 1}))` would mean serialising the fields into the payload
|
||||||
blob at link time, which is a byte-level encoder this compiler does not have and which could not express a `string`
|
blob at link time, which is a byte-level encoder this compiler does not have and which could not express a `string`
|
||||||
field at all — that is a pointer the linker has to relocate and a byte array has nowhere to put a relocation. A
|
field at all — that is a pointer the linker has to relocate and a byte array has nowhere to put a relocation. A
|
||||||
*zeroed* global is fine and needs none of it: it is the first declared case.
|
*zeroed* global is fine and needs none of it: it is the first declared case.
|
||||||
@ -3510,7 +3510,7 @@ on the way round, so the rule is not about them; `in_loop` takes the loop's own
|
|||||||
|
|
||||||
## `(array 4 rl/Vector2)`, and the one position with no type slot
|
## `(array 4 rl/Vector2)`, and the one position with no type slot
|
||||||
|
|
||||||
`[4 T]` is the ordinary type spelling and is unchanged. It already works everywhere a type is expected — `(defvar
|
`[4 T]` is the ordinary type spelling and is unchanged. It already works everywhere a type is expected — `(defonce
|
||||||
points [4 rl/Vector2])`, `(defn draw [pts [4 rl/Vector2]] ...)`, a `defstruct` field, a return. A **`let` binding is
|
points [4 rl/Vector2])`, `(defn draw [pts [4 rl/Vector2]] ...)`, a `defstruct` field, a return. A **`let` binding is
|
||||||
the single position with no type slot**, and there the brackets are read as what they are in expression position: an
|
the single position with no type slot**, and there the brackets are read as what they are in expression position: an
|
||||||
array *literal* of two elements, whose second element is a type name nothing declares. So `(let [pts [4 rl/Vector2]]
|
array *literal* of two elements, whose second element is a type name nothing declares. So `(let [pts [4 rl/Vector2]]
|
||||||
@ -3688,7 +3688,7 @@ call already went through.
|
|||||||
(map double xs)
|
(map double xs)
|
||||||
```
|
```
|
||||||
|
|
||||||
and not Common Lisp's `#'double`. **This is a Lisp-1 — one top-level namespace, enforced, so a `defn` and a `defvar`
|
and not Common Lisp's `#'double`. **This is a Lisp-1 — one top-level namespace, enforced, so a `defn` and a `defonce`
|
||||||
cannot share a name** — which is exactly what makes the bare name safe to read: there is no second binding of
|
cannot share a name** — which is exactly what makes the bare name safe to read: there is no second binding of
|
||||||
`double` it could have meant instead, so the sharp quote would be punctuation answering a question the language does
|
`double` it could have meant instead, so the sharp quote would be punctuation answering a question the language does
|
||||||
not ask.
|
not ask.
|
||||||
@ -3857,7 +3857,7 @@ the thing it calls. `Prelude.bootstrap` is the hook, a ref rather than a paramet
|
|||||||
`Check.program` and it cannot be told.
|
`Check.program` and it cannot be told.
|
||||||
|
|
||||||
Only `defn`s are dropped: the functions that survive still mention the prelude's types, and a reduced prelude missing
|
Only `defn`s are dropped: the functions that survive still mention the prelude's types, and a reduced prelude missing
|
||||||
them would not check. A `defstruct`, `defdata`, `defalias`, `defenum` or `defvar` therefore stays whatever it names.
|
them would not check. A `defstruct`, `defdata`, `defalias`, `defenum` or `defonce` therefore stays whatever it names.
|
||||||
There used to be a sharper reason — `Parse.prelude_types` memoised the prelude's type names for the parser's
|
There used to be a sharper reason — `Parse.prelude_types` memoised the prelude's type names for the parser's
|
||||||
return-type guess, and it can be forced for the first time inside a bootstrap build, so a reduced set cached there
|
return-type guess, and it can be forced for the first time inside a bootstrap build, so a reduced set cached there
|
||||||
would have been wrong for every compile afterwards. That set is gone with the guess; see *The return type is the slot*
|
would have been wrong for every compile afterwards. That set is gone with the guess; see *The return type is the slot*
|
||||||
@ -4113,7 +4113,7 @@ something else ever needs to talk to it.
|
|||||||
|
|
||||||
- ~~**Do cells cover globals, or only functions?**~~ **Settled by step 1: functions only.** A redefinition module
|
- ~~**Do cells cover globals, or only functions?**~~ **Settled by step 1: functions only.** A redefinition module
|
||||||
declares every global `external`, so globals live in the host and survive a reload — which is what "edit the code, keep
|
declares every global `external`, so globals live in the host and survive a reload — which is what "edit the code, keep
|
||||||
the sand" needs. The consequence to watch is the other half: adding a `defvar` to a file cannot take effect on reload,
|
the sand" needs. The consequence to watch is the other half: adding a `defonce` to a file cannot take effect on reload,
|
||||||
and changing one's type is a silent mismatch against storage the host already laid out. Nothing detects that yet.
|
and changing one's type is a silent mismatch against storage the host already laid out. Nothing detects that yet.
|
||||||
- **What is a redefinition unit — one function, or a file?** A file is much easier to make correct and is what
|
- **What is a redefinition unit — one function, or a file?** A file is much easier to make correct and is what
|
||||||
`load-file` wants anyway; one function is what `C-c C-c` wants and is where the 16ms number comes from.
|
`load-file` wants anyway; one function is what `C-c C-c` wants and is where the 16ms number comes from.
|
||||||
@ -5081,7 +5081,7 @@ escaped `alloca` is one mem2reg will not promote. So a release build's IR is the
|
|||||||
|
|
||||||
### It is armed by a constructor, and that is not fastidiousness
|
### It is armed by a constructor, and that is not fastidiousness
|
||||||
|
|
||||||
`@llvm.global_ctors` in the dev module, not a line at the top of `main`. A `defvar` initialiser can allocate, and it
|
`@llvm.global_ctors` in the dev module, not a line at the top of `main`. A `defonce` initialiser can allocate, and it
|
||||||
runs before `main` does; a note that arrived before the flag was set would be a block the table never heard of, which
|
runs before `main` does; a note that arrived before the flag was set would be a block the table never heard of, which
|
||||||
is a live pointer the inspector would call dead. That is the one failure mode worse than no registry at all.
|
is a live pointer the inspector would call dead. That is the one failure mode worse than no registry at all.
|
||||||
|
|
||||||
@ -5752,7 +5752,7 @@ and `Parse.expr`'s head dispatch already names `defmacro` first among the heads
|
|||||||
top-level declaration, not an expression". It matters more now than when it was written: before this, nobody would
|
top-level declaration, not an expression". It matters more now than when it was written: before this, nobody would
|
||||||
type a `defmacro` at `C-x C-e`, because the session forgot it either way. Now that one typed at the editor means
|
type a `defmacro` at `C-x C-e`, because the session forgot it either way. Now that one typed at the editor means
|
||||||
something, reaching for `C-x C-e` on it is a reflex, and what comes back says what it is rather than complaining
|
something, reaching for `C-x C-e` on it is a reflex, and what comes back says what it is rather than complaining
|
||||||
about an unknown function. `test_repl` pins it beside the `defvar` case.
|
about an unknown function. `test_repl` pins it beside the `defonce` case.
|
||||||
|
|
||||||
Both editor paths work and they are different wraps — `Parse.expr`'s for `C-x C-e` and `Parse.decl`'s for
|
Both editor paths work and they are different wraps — `Parse.expr`'s for `C-x C-e` and `Parse.decl`'s for
|
||||||
`C-c C-c`. `test/test_session.ml` covers both, plus a `defmacro` the file never had followed by a call to it,
|
`C-c C-c`. `test/test_session.ml` covers both, plus a `defmacro` the file never had followed by a call to it,
|
||||||
@ -5913,7 +5913,7 @@ become incorrect because its callers improved.
|
|||||||
`(/ 0.0 0.0)` printed `nan` through LLVM and `-nan` through the x86 backend. Neither is
|
`(/ 0.0 0.0)` printed `nan` through LLVM and `-nan` through the x86 backend. Neither is
|
||||||
wrong about the arithmetic: IEEE 754 does not specify the sign of a NaN any operation
|
wrong about the arithmetic: IEEE 754 does not specify the sign of a NaN any operation
|
||||||
produces, LLVM's constant folder answers with the positive quiet NaN at compile time, and
|
produces, LLVM's constant folder answers with the positive quiet NaN at compile time, and
|
||||||
`divsd` answers with the negative one at run time. Putting the operands in `defvar`
|
`divsd` answers with the negative one at run time. Putting the operands in `defonce`
|
||||||
globals so nothing folds makes both say `-nan`, which is what confirms the divergence is
|
globals so nothing folds makes both say `-nan`, which is what confirms the divergence is
|
||||||
the folding path and not a disagreement about float arithmetic.
|
the folding path and not a disagreement about float arithmetic.
|
||||||
|
|
||||||
@ -6188,3 +6188,28 @@ depends on.
|
|||||||
A string still does not cross to a `[u8]` parameter, and `(slice s)` does not make it one. That crossing is
|
A string still does not cross to a `[u8]` parameter, and `(slice s)` does not make it one. That crossing is
|
||||||
`bytes-view`'s. And `slice` over a `dyn` is still refused while `(at d i)` over one works — the asymmetry is
|
`bytes-view`'s. And `slice` over a `dyn` is still refused while `(at d i)` over one works — the asymmetry is
|
||||||
pre-existing and wants closing, since the two spaces are meant to compute the same thing.
|
pre-existing and wants closing, since the two spaces are meant to compute the same thing.
|
||||||
|
## `def` re-runs its initialiser; `defvar` is renamed `defonce`
|
||||||
|
|
||||||
|
Three forms define a global, and the difference between them is entirely what a daemon re-run does. `defonce` — Common
|
||||||
|
Lisp's `defvar` under Clojure's name, renamed because the old name said nothing — initialises once and keeps its value:
|
||||||
|
the value the *program* built is the one that survives, which is "edit the code, keep the sand". `def` is CL's
|
||||||
|
`defparameter`: its initialiser runs on every re-run, so the value the *source* spells is the one that wins — edit a
|
||||||
|
`(def colors [4 u32] [...])`, `C-c C-c`, `M-x flan-rerun`, and the colours change, which is the gap the author hit
|
||||||
|
dogfooding and the reason the form exists. `defconst` is the image and no run reaches it. The old `defvar` spelling is
|
||||||
|
refused with the rename and both new spellings, each of which compiles as written.
|
||||||
|
|
||||||
|
Both forms take the same spellings — `(def x Type)` zeroed, `(def x Type v)`, `(def x v)` a dyn global — through the
|
||||||
|
same parse arm and the same third-element dispatch, and collide with a `defn` under the same Lisp-1 rule. The
|
||||||
|
difference is one field, `Ast.reinit`, and two consequences of it. In `Emit.startup_plan` a `defonce`'s computed
|
||||||
|
initialiser hides behind its `.init~once.` flag and a `def`'s store is bare, so re-entering main repaints a `def` in
|
||||||
|
place — same storage, no reallocation, every reference sees the new bytes. And `Check.check_global` lifts *every*
|
||||||
|
`def` initialiser into `global/<n>`, constants and zeros included, where a `defonce` keeps constants inline: the host's
|
||||||
|
startup calls the initialiser through its function cell, so a re-evaluated `def` swaps the cell (`Session`'s
|
||||||
|
`def_inits`, plus `Emit.redefinition` declaring the cell for a target whose `fparent` is a global) and the next re-run
|
||||||
|
stores the edited value. A constant left inline would have baked the stale number into the host's startup for ever.
|
||||||
|
`uninit` opts out on both forms — nothing to run, nothing repaints.
|
||||||
|
|
||||||
|
One shared plan feeds both backends, so x86 and LLVM cannot disagree; `programs/global-init.flan`'s last four lines pin
|
||||||
|
every `def` spelling's startup on both, at `-O0` and `-O2`, and `programs/dev-rerun.flan` pins the live loop — a
|
||||||
|
`(def c 3)` printing 4 on every run beside a defonce that climbs, and the edited `(def c 9)` printing 10 after the next
|
||||||
|
re-run.
|
||||||
|
|||||||
@ -196,7 +196,7 @@ nowhere in a buffer to draw one, as at the REPL. `flan-echo-result` decides the
|
|||||||
echo area, and it also covers the sentence an *install* reports — which names
|
echo area, and it also covers the sentence an *install* reports — which names
|
||||||
landed and what it cost — since an install has no value to draw.
|
landed and what it cost — since an install has no value to draw.
|
||||||
|
|
||||||
The form before point can also be a declaration — a `defvar` typed at the top of
|
The form before point can also be a declaration — a `defonce` typed at the top of
|
||||||
a file — in which case `C-x C-e` installs it rather than refusing it, and says
|
a file — in which case `C-x C-e` installs it rather than refusing it, and says
|
||||||
which names changed instead of printing a value.
|
which names changed instead of printing a value.
|
||||||
|
|
||||||
@ -267,7 +267,7 @@ drawn on the call — it does not hang.
|
|||||||
### `C-c C-k` — the whole buffer
|
### `C-c C-k` — the whole buffer
|
||||||
|
|
||||||
The whole buffer, sent as **one** module rather than as a form at a time. That
|
The whole buffer, sent as **one** module rather than as a form at a time. That
|
||||||
matters: a `defvar` and the function that uses it have to arrive together, or the
|
matters: a `defonce` and the function that uses it have to arrive together, or the
|
||||||
function refers to storage that does not exist yet.
|
function refers to storage that does not exist yet.
|
||||||
|
|
||||||
Use this when you have changed several things at once, or when you have added a
|
Use this when you have changed several things at once, or when you have added a
|
||||||
@ -1174,7 +1174,7 @@ still answers.
|
|||||||
|
|
||||||
Under the stack there is one more section: the globals this stopped stack
|
Under the stack there is one more section: the globals this stopped stack
|
||||||
reaches. In this language that is often the more useful half — a game keeps
|
reaches. In this language that is often the more useful half — a game keeps
|
||||||
most of its state in top-level `defvar`s, and `sand.flan` holds its entire grid
|
most of its state in top-level `defonce`s, and `sand.flan` holds its entire grid
|
||||||
that way.
|
that way.
|
||||||
|
|
||||||
It is **one section, not a fold under each frame**. A global is not part of a
|
It is **one section, not a fold under each frame**. A global is not part of a
|
||||||
|
|||||||
@ -119,7 +119,7 @@
|
|||||||
;; patched only when somebody notices a gap is the list that is always a
|
;; patched only when somebody notices a gap is the list that is always a
|
||||||
;; release behind the parser.
|
;; release behind the parser.
|
||||||
(defconst flan--definers
|
(defconst flan--definers
|
||||||
'("defn" "defmacro" "defvar" "defconst" "defstruct" "defdata" "defunion"
|
'("defn" "defmacro" "def" "defonce" "defconst" "defstruct" "defdata" "defunion"
|
||||||
"defenum" "defalias"
|
"defenum" "defalias"
|
||||||
;; The object and dispatch heads (lib/parse.ml:1277-1334).
|
;; The object and dispatch heads (lib/parse.ml:1277-1334).
|
||||||
"defclass" "defgeneric" "defmulti" "defmethod"
|
"defclass" "defgeneric" "defmulti" "defmethod"
|
||||||
@ -185,7 +185,9 @@ below — and not again here.")
|
|||||||
("Types" ,(concat "^(def\\(?:struct\\|data\\|union\\|enum\\|alias\\)\\s-+"
|
("Types" ,(concat "^(def\\(?:struct\\|data\\|union\\|enum\\|alias\\)\\s-+"
|
||||||
flan--name-re)
|
flan--name-re)
|
||||||
1)
|
1)
|
||||||
("Variables" ,(concat "^(def\\(?:var\\|const\\)\\s-+" flan--name-re) 1)
|
;; `def', `defonce' and `defconst'. `def' has to be matched as itself —
|
||||||
|
;; `\_>' keeps it from swallowing every other definer's prefix.
|
||||||
|
("Variables" ,(concat "^(def\\(?:once\\|const\\)?\\_>\\s-+" flan--name-re) 1)
|
||||||
;; A forward declaration is not a definition, and a file with both would
|
;; A forward declaration is not a definition, and a file with both would
|
||||||
;; otherwise show the same name twice with nothing to tell them apart.
|
;; otherwise show the same name twice with nothing to tell them apart.
|
||||||
("Declared" ,(concat "^(declare\\s-+" flan--name-re) 1))
|
("Declared" ,(concat "^(declare\\s-+" flan--name-re) 1))
|
||||||
@ -601,7 +603,7 @@ decision to `calculate-lisp-indent'."
|
|||||||
((eq method :defn) (+ lisp-body-indent head-column))
|
((eq method :defn) (+ lisp-body-indent head-column))
|
||||||
;; No spec. Anything else spelled `def…' is a definition and indents
|
;; No spec. Anything else spelled `def…' is a definition and indents
|
||||||
;; like one, which covers `defstruct', `defdata', `defunion',
|
;; like one, which covers `defstruct', `defdata', `defunion',
|
||||||
;; `defenum', `defvar', `defconst' and `defalias' without naming
|
;; `defenum', `defonce', `defconst' and `defalias' without naming
|
||||||
;; them.
|
;; them.
|
||||||
((and name (string-match-p "\\`def" name))
|
((and name (string-match-p "\\`def" name))
|
||||||
(+ lisp-body-indent head-column))
|
(+ lisp-body-indent head-column))
|
||||||
|
|||||||
@ -51,10 +51,10 @@
|
|||||||
;;
|
;;
|
||||||
;; A *declaration* before point takes the other path and is installed, the way
|
;; A *declaration* before point takes the other path and is installed, the way
|
||||||
;; C-c C-c installs one. The key is dispatched on the form it would send, so
|
;; C-c C-c installs one. The key is dispatched on the form it would send, so
|
||||||
;; a `defvar' at the top of a file evaluates as a declaration while an
|
;; a `defonce' at the top of a file evaluates as a declaration while an
|
||||||
;; expression inside a `defn' body still evaluates as an expression. The
|
;; expression inside a `defn' body still evaluates as an expression. The
|
||||||
;; split used to be by keybinding, which meant a top-level `defvar' under
|
;; split used to be by keybinding, which meant a top-level `defonce' under
|
||||||
;; C-x C-e came back as "defvar is a top-level declaration, not an
|
;; C-x C-e came back as "defonce is a top-level declaration, not an
|
||||||
;; expression" — an editor artifact, not a limit of the compiler, which has
|
;; expression" — an editor artifact, not a limit of the compiler, which has
|
||||||
;; had both evaluators all along.
|
;; had both evaluators all along.
|
||||||
|
|
||||||
@ -1857,7 +1857,7 @@ Nothing is offered when nothing is known — an empty table would look like
|
|||||||
in this program; C-c C-v describes it" (car d)))
|
in this program; C-c C-v describes it" (car d)))
|
||||||
((equal (nth 3 d) "")
|
((equal (nth 3 d) "")
|
||||||
;; Tast.global and Tast.extern carry no Loc, so there is nothing to go
|
;; Tast.global and Tast.extern carry no Loc, so there is nothing to go
|
||||||
;; to. Guessing by searching for "(defvar ticks" would find the wrong
|
;; to. Guessing by searching for "(defonce ticks" would find the wrong
|
||||||
;; one in a program of several files, which is worse than refusing.
|
;; one in a program of several files, which is worse than refusing.
|
||||||
(user-error "flan: %s is a %s, and the daemon reports no location for one"
|
(user-error "flan: %s is a %s, and the daemon reports no location for one"
|
||||||
(car d) (nth 1 d)))
|
(car d) (nth 1 d)))
|
||||||
@ -2031,7 +2031,7 @@ someone editing Flan with no program running and this file never loaded."
|
|||||||
;; silently, so every accepted evaluation reports what landed in the running
|
;; silently, so every accepted evaluation reports what landed in the running
|
||||||
;; program and what it cost. The names come from the reply rather than from
|
;; program and what it cost. The names come from the reply rather than from
|
||||||
;; what was typed: the daemon is the one that knows which of them it installed,
|
;; what was typed: the daemon is the one that knows which of them it installed,
|
||||||
;; and a `defvar' the program already had is not among them.
|
;; and a `defonce' the program already had is not among them.
|
||||||
|
|
||||||
(defun flan--names-phrase (names fallback)
|
(defun flan--names-phrase (names fallback)
|
||||||
"NAMES as a phrase for the echo area, or FALLBACK when there are none.
|
"NAMES as a phrase for the echo area, or FALLBACK when there are none.
|
||||||
@ -2091,7 +2091,7 @@ has nothing to sit beside."
|
|||||||
;; everything the evaluation declared; a buffer of five functions
|
;; everything the evaluation declared; a buffer of five functions
|
||||||
;; and two vars should not report as "five".
|
;; and two vars should not report as "five".
|
||||||
;;
|
;;
|
||||||
;; A declaration with no body at all — a `defvar', a `defstruct' —
|
;; A declaration with no body at all — a `defonce', a `defstruct' —
|
||||||
;; has none of the first, and leading with WHAT then read as "form
|
;; has none of the first, and leading with WHAT then read as "form
|
||||||
;; installed in 4 ms (also ticks)": the one name that actually
|
;; installed in 4 ms (also ticks)": the one name that actually
|
||||||
;; changed, parenthesised as an afterthought, beside a label that
|
;; changed, parenthesised as an afterthought, beside a label that
|
||||||
@ -2266,7 +2266,7 @@ daemon reads that as stopping on entry instead."
|
|||||||
;; and `package' is a single named exception rather than the edge of a subtler
|
;; and `package' is a single named exception rather than the edge of a subtler
|
||||||
;; rule that was never quite true.
|
;; rule that was never quite true.
|
||||||
(defconst flan--declaration-heads
|
(defconst flan--declaration-heads
|
||||||
'("defmacro" "defn" "defvar" "defconst"
|
'("defmacro" "defn" "def" "defonce" "defconst"
|
||||||
"defstruct" "defdata" "defunion" "defenum" "defalias"
|
"defstruct" "defdata" "defunion" "defenum" "defalias"
|
||||||
;; The object and dispatch heads (lib/parse.ml:1277-1334), which are
|
;; The object and dispatch heads (lib/parse.ml:1277-1334), which are
|
||||||
;; declarations in exactly the way `defn' is: each introduces a top-level
|
;; declarations in exactly the way `defn' is: each introduces a top-level
|
||||||
@ -2378,7 +2378,7 @@ this draws on success would otherwise be competing with one drawn at line 1."
|
|||||||
(defun flan-eval-defun (&optional arg)
|
(defun flan-eval-defun (&optional arg)
|
||||||
"Evaluate the top-level form at point in the running program.
|
"Evaluate the top-level form at point in the running program.
|
||||||
|
|
||||||
A declaration — a `defn', a `defvar', anything in `flan--defun-heads' — is
|
A declaration — a `defn', a `defonce', anything in `flan--defun-heads' — is
|
||||||
recompiled and installed, which is what this key has always done. Anything
|
recompiled and installed, which is what this key has always done. Anything
|
||||||
else is an expression, and is evaluated and its value shown, because a bare
|
else is an expression, and is evaluated and its value shown, because a bare
|
||||||
`(+ 1 1)' written at the top of a file is a form like any other and refusing
|
`(+ 1 1)' written at the top of a file is a form like any other and refusing
|
||||||
@ -2464,11 +2464,11 @@ arrive in the same load or the first refers to storage that does not exist."
|
|||||||
"Evaluate the form before point in the running program and report it.
|
"Evaluate the form before point in the running program and report it.
|
||||||
|
|
||||||
An expression is compiled into a thunk the program runs at its next frame
|
An expression is compiled into a thunk the program runs at its next frame
|
||||||
boundary, and its value is shown. A top-level declaration — a `defvar', a
|
boundary, and its value is shown. A top-level declaration — a `defonce', a
|
||||||
`defn', anything in `flan--declaration-heads' — is compiled and installed
|
`defn', anything in `flan--declaration-heads' — is compiled and installed
|
||||||
instead, and the reply names what changed. The compiler has always had both
|
instead, and the reply names what changed. The compiler has always had both
|
||||||
paths; this key used to reach only the first, so a `defvar' typed at the top
|
paths; this key used to reach only the first, so a `defonce' typed at the top
|
||||||
of a file came back as \"defvar is a top-level declaration, not an
|
of a file came back as \"defonce is a top-level declaration, not an
|
||||||
expression\" and the only way to evaluate it was `C-c C-c'. That was an
|
expression\" and the only way to evaluate it was `C-c C-c'. That was an
|
||||||
editor artifact and not a property of the language.
|
editor artifact and not a property of the language.
|
||||||
|
|
||||||
|
|||||||
@ -344,7 +344,7 @@ already rely on it — so nothing here is a stand-in for the real thing."
|
|||||||
;; A declaration the program already has installs nothing, and must say so
|
;; A declaration the program already has installs nothing, and must say so
|
||||||
;; rather than reporting a time for a build that did not happen.
|
;; rather than reporting a time for a build that did not happen.
|
||||||
(let ((said (test-flan--said
|
(let ((said (test-flan--said
|
||||||
(flan--eval "(defvar ticks i64)" "form"))))
|
(flan--eval "(defonce ticks i64)" "form"))))
|
||||||
(test-flan--check "an evaluation with nothing to install says so"
|
(test-flan--check "an evaluation with nothing to install says so"
|
||||||
(and said (string-match-p "nothing to install" said)
|
(and said (string-match-p "nothing to install" said)
|
||||||
(not (string-match-p "installed" said)))))
|
(not (string-match-p "installed" said)))))
|
||||||
@ -482,9 +482,9 @@ already rely on it — so nothing here is a stand-in for the real thing."
|
|||||||
;; only looked at the head. No daemon round trip here — nothing is sent —
|
;; only looked at the head. No daemon round trip here — nothing is sent —
|
||||||
;; which is why all four are affordable.
|
;; which is why all four are affordable.
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(search-forward "(defvar ticks i64)")
|
(search-forward "(defonce ticks i64)")
|
||||||
(test-flan--check "a top-level defvar takes the declaration path"
|
(test-flan--check "a top-level defonce takes the declaration path"
|
||||||
(equal (car (flan--declaration-before-point)) "defvar"))
|
(equal (car (flan--declaration-before-point)) "defonce"))
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(search-forward " ticks)")
|
(search-forward " ticks)")
|
||||||
(test-flan--check "and so does a top-level defn"
|
(test-flan--check "and so does a top-level defn"
|
||||||
@ -658,7 +658,7 @@ already rely on it — so nothing here is a stand-in for the real thing."
|
|||||||
;; back. Read off `Parse.decl', so the check is the derivation.
|
;; back. Read off `Parse.decl', so the check is the derivation.
|
||||||
(with-temp-buffer
|
(with-temp-buffer
|
||||||
(flan-mode)
|
(flan-mode)
|
||||||
(dolist (head '("defmacro" "defn" "defvar" "defconst" "defstruct"
|
(dolist (head '("defmacro" "defn" "def" "defonce" "defconst" "defstruct"
|
||||||
"defdata" "defunion" "defenum" "defalias"
|
"defdata" "defunion" "defenum" "defalias"
|
||||||
"defclass" "defgeneric" "defmulti" "defmethod"
|
"defclass" "defgeneric" "defmulti" "defmethod"
|
||||||
"import" "declare" "declare-c"))
|
"import" "declare" "declare-c"))
|
||||||
@ -748,7 +748,7 @@ already rely on it — so nothing here is a stand-in for the real thing."
|
|||||||
;; was the only thing being asked and a sentence would have been compiled.
|
;; was the only thing being asked and a sentence would have been compiled.
|
||||||
(goto-char (point-max))
|
(goto-char (point-max))
|
||||||
(let ((beg (point)))
|
(let ((beg (point)))
|
||||||
(insert "\n;; (defvar commented i64 1)\n\"(defvar inside i64 1)\"")
|
(insert "\n;; (defonce commented i64 1)\n\"(defonce inside i64 1)\"")
|
||||||
;; Point after the form's own closing paren rather than at the end of the
|
;; Point after the form's own closing paren rather than at the end of the
|
||||||
;; line: `backward-sexp' walks over a whole comment, so from the end of
|
;; line: `backward-sexp' walks over a whole comment, so from the end of
|
||||||
;; one it never reaches the paren inside it and the guard is never asked.
|
;; one it never reaches the paren inside it and the guard is never asked.
|
||||||
@ -777,15 +777,15 @@ already rely on it — so nothing here is a stand-in for the real thing."
|
|||||||
(user-error (setq raised (error-message-string err))))
|
(user-error (setq raised (error-message-string err))))
|
||||||
(and raised (string-match-p "no form before point" raised))))
|
(and raised (string-match-p "no form before point" raised))))
|
||||||
|
|
||||||
;; The bug this key had: a `defvar' typed at the top of a file could only be
|
;; The bug this key had: a `defonce' typed at the top of a file could only be
|
||||||
;; evaluated with C-c C-c, because C-x C-e sent it to the expression
|
;; evaluated with C-c C-c, because C-x C-e sent it to the expression
|
||||||
;; evaluator and the parser refused it as a declaration. One round trip, on
|
;; evaluator and the parser refused it as a declaration. One round trip, on
|
||||||
;; the form the report has to be able to name.
|
;; the form the report has to be able to name.
|
||||||
(goto-char (point-max))
|
(goto-char (point-max))
|
||||||
(let ((beg (point)))
|
(let ((beg (point)))
|
||||||
(insert "\n(defvar spark i64 9)")
|
(insert "\n(defonce spark i64 9)")
|
||||||
(let ((said (test-flan--said (flan-eval-last-sexp))))
|
(let ((said (test-flan--said (flan-eval-last-sexp))))
|
||||||
(test-flan--check "C-x C-e on a top-level defvar installs it"
|
(test-flan--check "C-x C-e on a top-level defonce installs it"
|
||||||
(and said (string-match-p "\\_<spark\\_>" said)
|
(and said (string-match-p "\\_<spark\\_>" said)
|
||||||
(string-match-p "installed" said)))
|
(string-match-p "installed" said)))
|
||||||
;; The two paths share a key now, so the echo area is the only thing
|
;; The two paths share a key now, so the echo area is the only thing
|
||||||
@ -795,18 +795,18 @@ already rely on it — so nothing here is a stand-in for the real thing."
|
|||||||
(and said (not (string-match-p "=>" said)))))
|
(and said (not (string-match-p "=>" said)))))
|
||||||
(delete-region beg (point-max)))
|
(delete-region beg (point-max)))
|
||||||
|
|
||||||
;; And the literal complaint, on the `defvar' actually written in the file
|
;; And the literal complaint, on the `defonce' actually written in the file
|
||||||
;; rather than on one typed in for the occasion. What is asserted is only
|
;; rather than on one typed in for the occasion. What is asserted is only
|
||||||
;; what the client decides: the var is named and no value is printed.
|
;; what the client decides: the var is named and no value is printed.
|
||||||
;; Whether the daemon answers "installed" or "nothing to install" is its
|
;; Whether the daemon answers "installed" or "nothing to install" is its
|
||||||
;; call and depends on what this session has been through by now, and a test
|
;; call and depends on what this session has been through by now, and a test
|
||||||
;; that pinned one of them here would be testing the order of the checks
|
;; that pinned one of them here would be testing the order of the checks
|
||||||
;; above it. A defvar declares no functions either way, which is what makes
|
;; above it. A defonce declares no functions either way, which is what makes
|
||||||
;; the name — and not the kind of form — the subject of the sentence.
|
;; the name — and not the kind of form — the subject of the sentence.
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(search-forward "(defvar ticks i64)")
|
(search-forward "(defonce ticks i64)")
|
||||||
(let ((said (test-flan--said (flan-eval-last-sexp))))
|
(let ((said (test-flan--said (flan-eval-last-sexp))))
|
||||||
(test-flan--check "C-x C-e on the file's own defvar reports the var"
|
(test-flan--check "C-x C-e on the file's own defonce reports the var"
|
||||||
(and said (string-match-p "\\_<ticks\\_>" said)
|
(and said (string-match-p "\\_<ticks\\_>" said)
|
||||||
(not (string-match-p "=>" said)))))
|
(not (string-match-p "=>" said)))))
|
||||||
|
|
||||||
@ -1536,7 +1536,8 @@ already rely on it — so nothing here is a stand-in for the real thing."
|
|||||||
(with-temp-buffer
|
(with-temp-buffer
|
||||||
(insert ";;;; A file with one of everything.\n"
|
(insert ";;;; A file with one of everything.\n"
|
||||||
"(defstruct Missing [id i32])\n"
|
"(defstruct Missing [id i32])\n"
|
||||||
"(defvar ticks i64)\n"
|
"(defonce ticks i64)\n"
|
||||||
|
"(def speed 2)\n"
|
||||||
"(defconst limit i64 10)\n"
|
"(defconst limit i64 10)\n"
|
||||||
"(declare later [] i64)\n"
|
"(declare later [] i64)\n"
|
||||||
"(defn step [] i64\n"
|
"(defn step [] i64\n"
|
||||||
@ -1557,6 +1558,8 @@ already rely on it — so nothing here is a stand-in for the real thing."
|
|||||||
(test-flan--check "and both kinds of global"
|
(test-flan--check "and both kinds of global"
|
||||||
(and (assoc "ticks" (funcall group "Variables"))
|
(and (assoc "ticks" (funcall group "Variables"))
|
||||||
(assoc "limit" (funcall group "Variables"))))
|
(assoc "limit" (funcall group "Variables"))))
|
||||||
|
(test-flan--check "and a def, which is a variable too"
|
||||||
|
(assoc "speed" (funcall group "Variables")))
|
||||||
;; A forward declaration is not a definition; listing it beside one
|
;; A forward declaration is not a definition; listing it beside one
|
||||||
;; would show the same name twice with nothing to tell them apart.
|
;; would show the same name twice with nothing to tell them apart.
|
||||||
(test-flan--check "and a declaration, said to be one"
|
(test-flan--check "and a declaration, said to be one"
|
||||||
@ -1901,7 +1904,7 @@ already rely on it — so nothing here is a stand-in for the real thing."
|
|||||||
;; mark outlives the next command where a rejection does not.
|
;; mark outlives the next command where a rejection does not.
|
||||||
(with-temp-buffer
|
(with-temp-buffer
|
||||||
(flan-mode)
|
(flan-mode)
|
||||||
(insert "(defvar ticks i64)\n\n(defn step [] i64\n (set ticks (+ ticks 1))\n ticks)\n")
|
(insert "(defonce ticks i64)\n\n(defn step [] i64\n (set ticks (+ ticks 1))\n ticks)\n")
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(search-forward "(+ ticks 1)")
|
(search-forward "(+ ticks 1)")
|
||||||
(goto-char (1- (match-end 0))) ; inside the (+ ...), before its ")"
|
(goto-char (1- (match-end 0))) ; inside the (+ ...), before its ")"
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
;;;; acceptance table pins that layout with get-screen-to-world-2d, and this
|
;;;; acceptance table pins that layout with get-screen-to-world-2d, and this
|
||||||
;;;; is the same struct going into the call that actually draws with it.
|
;;;; is the same struct going into the call that actually draws with it.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; The hundred buildings are a `defvar` of fixed arrays rather than a Vec:
|
;;;; The hundred buildings are a `defonce` of fixed arrays rather than a Vec:
|
||||||
;;;; a global cannot hold a Vec (docs/PORTING.md §3) and does not need to here,
|
;;;; a global cannot hold a Vec (docs/PORTING.md §3) and does not need to here,
|
||||||
;;;; because the count is a constant in the C too. `(array n T)` is the zeroed
|
;;;; because the count is a constant in the C too. `(array n T)` is the zeroed
|
||||||
;;;; fixed array, and the C's `= { 0 }` is exactly that.
|
;;;; fixed array, and the C's `= { 0 }` is exactly that.
|
||||||
@ -29,10 +29,10 @@
|
|||||||
|
|
||||||
(defconst max-buildings 100)
|
(defconst max-buildings 100)
|
||||||
|
|
||||||
(defvar buildings [100 rl/Rectangle])
|
(defonce buildings [100 rl/Rectangle])
|
||||||
(defvar build-colors [100 rl/Color])
|
(defonce build-colors [100 rl/Color])
|
||||||
(defvar player rl/Rectangle)
|
(defonce player rl/Rectangle)
|
||||||
(defvar camera rl/Camera2D)
|
(defonce camera rl/Camera2D)
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(rl/init-window screen-width screen-height
|
(rl/init-window screen-width screen-height
|
||||||
|
|||||||
@ -41,16 +41,16 @@
|
|||||||
(defconst screen-width 800)
|
(defconst screen-width 800)
|
||||||
(defconst screen-height 450)
|
(defconst screen-height 450)
|
||||||
|
|
||||||
(defvar camera rl/Camera3D)
|
(defonce camera rl/Camera3D)
|
||||||
|
|
||||||
(defvar cube-position rl/Vector3)
|
(defonce cube-position rl/Vector3)
|
||||||
(defvar cube-size rl/Vector3)
|
(defonce cube-size rl/Vector3)
|
||||||
|
|
||||||
;; The picking ray, kept between frames because draw-ray draws it every frame
|
;; The picking ray, kept between frames because draw-ray draws it every frame
|
||||||
;; whether or not it hit anything — that is how the example shows where the
|
;; whether or not it hit anything — that is how the example shows where the
|
||||||
;; click went.
|
;; click went.
|
||||||
(defvar ray rl/Ray)
|
(defonce ray rl/Ray)
|
||||||
(defvar collision rl/RayCollision)
|
(defonce collision rl/RayCollision)
|
||||||
|
|
||||||
;; The same centre/size to min/max conversion as in
|
;; The same centre/size to min/max conversion as in
|
||||||
;; examples/models-box-collisions.flan. Written out here rather than shared
|
;; examples/models-box-collisions.flan. Written out here rather than shared
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
;;;; here rather than instead of it, so the file is not quietly asserting that
|
;;;; here rather than instead of it, so the file is not quietly asserting that
|
||||||
;;;; the original was right.
|
;;;; the original was right.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; The two circle positions are `defvar`s for the usual reason: they are
|
;;;; The two circle positions are `defonce`s for the usual reason: they are
|
||||||
;;;; state between frames and a `let` inside the loop would reset them.
|
;;;; state between frames and a `let` inside the loop would reset them.
|
||||||
|
|
||||||
(import rl "vendor:raylib")
|
(import rl "vendor:raylib")
|
||||||
@ -31,9 +31,9 @@
|
|||||||
(defconst speed f32 10.0)
|
(defconst speed f32 10.0)
|
||||||
(defconst circle-radius f32 32.0)
|
(defconst circle-radius f32 32.0)
|
||||||
|
|
||||||
(defvar delta-circle rl/Vector2)
|
(defonce delta-circle rl/Vector2)
|
||||||
(defvar frame-circle rl/Vector2)
|
(defonce frame-circle rl/Vector2)
|
||||||
(defvar current-fps i32)
|
(defonce current-fps i32)
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(rl/init-window screen-width screen-height
|
(rl/init-window screen-width screen-height
|
||||||
|
|||||||
@ -47,7 +47,7 @@
|
|||||||
(defconst stick-deadzone f32 0.1)
|
(defconst stick-deadzone f32 0.1)
|
||||||
(defconst trigger-deadzone f32 -0.9)
|
(defconst trigger-deadzone f32 -0.9)
|
||||||
|
|
||||||
(defvar gamepad i32)
|
(defonce gamepad i32)
|
||||||
|
|
||||||
;; The C's deadzone test, which is a band around zero and not a clamp: inside
|
;; The C's deadzone test, which is a band around zero and not a clamp: inside
|
||||||
;; it the axis reads exactly 0, outside it the raw value passes through
|
;; it the axis reads exactly 0, outside it the raw value passes through
|
||||||
|
|||||||
@ -48,9 +48,9 @@
|
|||||||
;;;;
|
;;;;
|
||||||
;;;; **No local fixed arrays.** The C declares `char gestureLog[20][12]` and
|
;;;; **No local fixed arrays.** The C declares `char gestureLog[20][12]` and
|
||||||
;;;; `Vector2 touchPosition[32]` inside main. A `let` binding takes no type
|
;;;; `Vector2 touchPosition[32]` inside main. A `let` binding takes no type
|
||||||
;;;; annotation, so a fixed array can only be a top-level `defvar` or a literal
|
;;;; annotation, so a fixed array can only be a top-level `defonce` or a literal
|
||||||
;;;; with every element written out — thirty-two Vector2s, here. They are
|
;;;; with every element written out — thirty-two Vector2s, here. They are
|
||||||
;;;; `defvar`s, which is what the C's storage amounts to anyway.
|
;;;; `defonce`s, which is what the C's storage amounts to anyway.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; The log itself came out simpler than the C's: the names are compile-time
|
;;;; The log itself came out simpler than the C's: the names are compile-time
|
||||||
;;;; literals, so a slot holds a `string` and there is no TextCopy and no
|
;;;; literals, so a slot holds a `string` and there is no TextCopy and no
|
||||||
@ -67,18 +67,18 @@
|
|||||||
(defconst gesture-log-size 20)
|
(defconst gesture-log-size 20)
|
||||||
(defconst max-touch-count 32)
|
(defconst max-touch-count 32)
|
||||||
|
|
||||||
(defvar gesture-log [gesture-log-size string])
|
(defonce gesture-log [gesture-log-size string])
|
||||||
;; The C's inverted circular queue: the index counts DOWN and wraps at the top,
|
;; The C's inverted circular queue: the index counts DOWN and wraps at the top,
|
||||||
;; so the newest entry is always at gesture-log-index and the draw loop walks
|
;; so the newest entry is always at gesture-log-index and the draw loop walks
|
||||||
;; forward from there. Starting at the size rather than at size-1 is the C's
|
;; forward from there. Starting at the size rather than at size-1 is the C's
|
||||||
;; too — the first write decrements before storing.
|
;; too — the first write decrements before storing.
|
||||||
(defvar gesture-log-index i32)
|
(defonce gesture-log-index i32)
|
||||||
(defvar previous-gesture rl/Gesture)
|
(defonce previous-gesture rl/Gesture)
|
||||||
(defvar last-gesture rl/Gesture)
|
(defonce last-gesture rl/Gesture)
|
||||||
(defvar gesture-color rl/Color)
|
(defonce gesture-color rl/Color)
|
||||||
(defvar log-mode i32)
|
(defonce log-mode i32)
|
||||||
(defvar current-angle f32)
|
(defonce current-angle f32)
|
||||||
(defvar touch-positions [max-touch-count rl/Vector2])
|
(defonce touch-positions [max-touch-count rl/Vector2])
|
||||||
|
|
||||||
;; ── The comparisons the C makes on the raw bitfield ─────────────────
|
;; ── The comparisons the C makes on the raw bitfield ─────────────────
|
||||||
;;
|
;;
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
;;;; catch.
|
;;;; catch.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; A slot that has not been written yet holds a zero-length string, because a
|
;;;; A slot that has not been written yet holds a zero-length string, because a
|
||||||
;;;; `defvar` with no initialiser is all-bytes-zero and a string is ptr+len —
|
;;;; `defonce` with no initialiser is all-bytes-zero and a string is ptr+len —
|
||||||
;;;; a null pointer with a length of 0. draw-text draws nothing for it. That is
|
;;;; a null pointer with a length of 0. draw-text draws nothing for it. That is
|
||||||
;;;; the C's `{ "" }` initialiser arriving by a different route, and it is why
|
;;;; the C's `{ "" }` initialiser arriving by a different route, and it is why
|
||||||
;;;; resetting the log below only has to reset the counter.
|
;;;; resetting the log below only has to reset the counter.
|
||||||
@ -30,10 +30,10 @@
|
|||||||
|
|
||||||
(defconst max-gesture-strings 20)
|
(defconst max-gesture-strings 20)
|
||||||
|
|
||||||
(defvar gesture-log [max-gesture-strings string])
|
(defonce gesture-log [max-gesture-strings string])
|
||||||
(defvar gestures-count i32)
|
(defonce gestures-count i32)
|
||||||
(defvar current-gesture rl/Gesture)
|
(defonce current-gesture rl/Gesture)
|
||||||
(defvar last-gesture rl/Gesture)
|
(defonce last-gesture rl/Gesture)
|
||||||
|
|
||||||
;; The C's switch, as a function. `:else` is its `default:` — an unnamed
|
;; The C's switch, as a function. `:else` is its `default:` — an unnamed
|
||||||
;; gesture logs the empty string, which draws nothing, which is what falling
|
;; gesture logs the empty string, which draws nothing, which is what falling
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
;;;;
|
;;;;
|
||||||
;;;; The C moves the ball by writing `ballPosition.x += 2.0f` on a local
|
;;;; The C moves the ball by writing `ballPosition.x += 2.0f` on a local
|
||||||
;;;; struct. Flan has the same thing — a local IS an assignable place
|
;;;; struct. Flan has the same thing — a local IS an assignable place
|
||||||
;;;; (spec-memory.md) — but the local has to be a `defvar` here rather than a
|
;;;; (spec-memory.md) — but the local has to be a `defonce` here rather than a
|
||||||
;;;; `let` inside the loop, because a `let` binding is rebound every iteration
|
;;;; `let` inside the loop, because a `let` binding is rebound every iteration
|
||||||
;;;; and the position has to survive between frames. The C's variable is
|
;;;; and the position has to survive between frames. The C's variable is
|
||||||
;;;; outside its while loop for the same reason; this is that, spelled with the
|
;;;; outside its while loop for the same reason; this is that, spelled with the
|
||||||
@ -19,7 +19,7 @@
|
|||||||
(defconst screen-width 800)
|
(defconst screen-width 800)
|
||||||
(defconst screen-height 450)
|
(defconst screen-height 450)
|
||||||
|
|
||||||
(defvar ball rl/Vector2)
|
(defonce ball rl/Vector2)
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(rl/init-window screen-width screen-height
|
(rl/init-window screen-width screen-height
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
(defconst scroll-speed 4)
|
(defconst scroll-speed 4)
|
||||||
|
|
||||||
(defvar box-y i32)
|
(defonce box-y i32)
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(rl/init-window screen-width screen-height
|
(rl/init-window screen-width screen-height
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
;;;; matters in both — two buttons pressed on the same frame give the earlier
|
;;;; matters in both — two buttons pressed on the same frame give the earlier
|
||||||
;;;; one, which is the C's behaviour and not an accident of the port.
|
;;;; one, which is the C's behaviour and not an accident of the port.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; The colour has to be a `defvar` rather than a `let`, for the reason
|
;;;; The colour has to be a `defonce` rather than a `let`, for the reason
|
||||||
;;;; core-input-keys' position does: it is state between frames.
|
;;;; core-input-keys' position does: it is state between frames.
|
||||||
|
|
||||||
(import rl "vendor:raylib")
|
(import rl "vendor:raylib")
|
||||||
@ -17,7 +17,7 @@
|
|||||||
(defconst screen-width 800)
|
(defconst screen-width 800)
|
||||||
(defconst screen-height 450)
|
(defconst screen-height 450)
|
||||||
|
|
||||||
(defvar ball-color rl/Color)
|
(defonce ball-color rl/Color)
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(rl/init-window screen-width screen-height
|
(rl/init-window screen-width screen-height
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
;;;; assigned a whole struct. Nothing in the repository used one before, so it
|
;;;; assigned a whole struct. Nothing in the repository used one before, so it
|
||||||
;;;; is worth saying that it does.
|
;;;; is worth saying that it does.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; It is a top-level `defvar` and not a local, which is NOT a stylistic
|
;;;; It is a top-level `defonce` and not a local, which is NOT a stylistic
|
||||||
;;;; choice. A `let` binding takes no type annotation, so the only way to make
|
;;;; choice. A `let` binding takes no type annotation, so the only way to make
|
||||||
;;;; a fixed array inside a function is to initialise it from a literal with
|
;;;; a fixed array inside a function is to initialise it from a literal with
|
||||||
;;;; every element written out — ten `(rl/Vector2 {.x 0.0 .y 0.0})`s here, and
|
;;;; every element written out — ten `(rl/Vector2 {.x 0.0 .y 0.0})`s here, and
|
||||||
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
(defconst max-touch-points 10)
|
(defconst max-touch-points 10)
|
||||||
|
|
||||||
(defvar touch-positions [max-touch-points rl/Vector2])
|
(defonce touch-positions [max-touch-points rl/Vector2])
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(rl/init-window screen-width screen-height
|
(rl/init-window screen-width screen-height
|
||||||
|
|||||||
@ -33,7 +33,7 @@
|
|||||||
;;;;
|
;;;;
|
||||||
;;;; The button geometry is `defconst` arrays of struct literals. That works —
|
;;;; The button geometry is `defconst` arrays of struct literals. That works —
|
||||||
;;;; and it is the only way a fixed array can be made inside anything but a
|
;;;; and it is the only way a fixed array can be made inside anything but a
|
||||||
;;;; top-level `defvar`, since a `let` binding takes no type annotation.
|
;;;; top-level `defonce`, since a `let` binding takes no type annotation.
|
||||||
|
|
||||||
(import rl "vendor:raylib")
|
(import rl "vendor:raylib")
|
||||||
|
|
||||||
@ -67,7 +67,7 @@
|
|||||||
|
|
||||||
;;;; ── The part with no raylib in it ──────────────────────────────────
|
;;;; ── The part with no raylib in it ──────────────────────────────────
|
||||||
|
|
||||||
(defvar player rl/Vector2)
|
(defonce player rl/Vector2)
|
||||||
|
|
||||||
(defn reset-player [] ()
|
(defn reset-player [] ()
|
||||||
(set player (rl/Vector2 {.x (/ (f32 screen-width) 2.0)
|
(set player (rl/Vector2 {.x (/ (f32 screen-width) 2.0)
|
||||||
|
|||||||
@ -29,8 +29,8 @@
|
|||||||
(defconst screen-width 800)
|
(defconst screen-width 800)
|
||||||
(defconst screen-height 450)
|
(defconst screen-height 450)
|
||||||
|
|
||||||
(defvar scissor rl/Rectangle)
|
(defonce scissor rl/Rectangle)
|
||||||
(defvar scissor-mode bool)
|
(defonce scissor-mode bool)
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(rl/init-window screen-width screen-height
|
(rl/init-window screen-width screen-height
|
||||||
|
|||||||
@ -38,9 +38,9 @@
|
|||||||
;; window somewhere it cannot be typed at, so the only way back is a count.
|
;; window somewhere it cannot be typed at, so the only way back is a count.
|
||||||
(defconst restore-after 240)
|
(defconst restore-after 240)
|
||||||
|
|
||||||
(defvar ball-pos rl/Vector2)
|
(defonce ball-pos rl/Vector2)
|
||||||
(defvar ball-speed rl/Vector2)
|
(defonce ball-speed rl/Vector2)
|
||||||
(defvar frames i32)
|
(defonce frames i32)
|
||||||
|
|
||||||
(defconst ball-radius f32 20.0)
|
(defconst ball-radius f32 20.0)
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
;;;; flag raylib latches: it is "the close button was clicked, or the exit key
|
;;;; flag raylib latches: it is "the close button was clicked, or the exit key
|
||||||
;;;; is down", recomputed each frame, and it goes back to false on its own.
|
;;;; is down", recomputed each frame, and it goes back to false on its own.
|
||||||
;;;; That is what lets this program ask for confirmation and then carry on —
|
;;;; That is what lets this program ask for confirmation and then carry on —
|
||||||
;;;; and it is also why the loop is driven by a `defvar` of its own rather
|
;;;; and it is also why the loop is driven by a `defonce` of its own rather
|
||||||
;;;; than by the predicate, which is the one structural difference from every
|
;;;; than by the predicate, which is the one structural difference from every
|
||||||
;;;; other example in this directory.
|
;;;; other example in this directory.
|
||||||
;;;;
|
;;;;
|
||||||
@ -25,8 +25,8 @@
|
|||||||
(defconst screen-width 800)
|
(defconst screen-width 800)
|
||||||
(defconst screen-height 450)
|
(defconst screen-height 450)
|
||||||
|
|
||||||
(defvar exit-requested bool)
|
(defonce exit-requested bool)
|
||||||
(defvar exiting bool)
|
(defonce exiting bool)
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(rl/init-window screen-width screen-height
|
(rl/init-window screen-width screen-height
|
||||||
|
|||||||
@ -39,8 +39,8 @@
|
|||||||
(defconst screen-width 800)
|
(defconst screen-width 800)
|
||||||
(defconst screen-height 450)
|
(defconst screen-height 450)
|
||||||
|
|
||||||
(defvar camera rl/Camera3D)
|
(defonce camera rl/Camera3D)
|
||||||
(defvar cube rl/Vector3)
|
(defonce cube rl/Vector3)
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(rl/init-window screen-width screen-height
|
(rl/init-window screen-width screen-height
|
||||||
|
|||||||
@ -38,15 +38,15 @@
|
|||||||
(defconst screen-width 800)
|
(defconst screen-width 800)
|
||||||
(defconst screen-height 450)
|
(defconst screen-height 450)
|
||||||
|
|
||||||
(defvar camera rl/Camera3D)
|
(defonce camera rl/Camera3D)
|
||||||
|
|
||||||
(defvar player-position rl/Vector3)
|
(defonce player-position rl/Vector3)
|
||||||
(defvar player-size rl/Vector3)
|
(defonce player-size rl/Vector3)
|
||||||
(defvar player-color rl/Color)
|
(defonce player-color rl/Color)
|
||||||
|
|
||||||
(defvar enemy-box-pos rl/Vector3)
|
(defonce enemy-box-pos rl/Vector3)
|
||||||
(defvar enemy-box-size rl/Vector3)
|
(defonce enemy-box-size rl/Vector3)
|
||||||
(defvar enemy-sphere-pos rl/Vector3)
|
(defonce enemy-sphere-pos rl/Vector3)
|
||||||
|
|
||||||
(defconst enemy-sphere-size f32 1.5)
|
(defconst enemy-sphere-size f32 1.5)
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
(defconst col-mid 400)
|
(defconst col-mid 400)
|
||||||
(defconst col-right 600)
|
(defconst col-right 600)
|
||||||
|
|
||||||
(defvar rotation f32)
|
(defonce rotation f32)
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(rl/init-window screen-width screen-height
|
(rl/init-window screen-width screen-height
|
||||||
|
|||||||
@ -28,12 +28,12 @@
|
|||||||
;; not allowed above.
|
;; not allowed above.
|
||||||
(defconst screen-upper-limit 40)
|
(defconst screen-upper-limit 40)
|
||||||
|
|
||||||
(defvar box-a rl/Rectangle) ; moves by itself, bounces off the sides
|
(defonce box-a rl/Rectangle) ; moves by itself, bounces off the sides
|
||||||
(defvar box-b rl/Rectangle) ; follows the mouse
|
(defonce box-b rl/Rectangle) ; follows the mouse
|
||||||
(defvar box-collision rl/Rectangle) ; their overlap, valid only while touching
|
(defonce box-collision rl/Rectangle) ; their overlap, valid only while touching
|
||||||
(defvar box-a-speed-x i32)
|
(defonce box-a-speed-x i32)
|
||||||
(defvar paused bool)
|
(defonce paused bool)
|
||||||
(defvar collision bool)
|
(defonce collision bool)
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(rl/init-window screen-width screen-height
|
(rl/init-window screen-width screen-height
|
||||||
|
|||||||
@ -45,10 +45,10 @@
|
|||||||
(defconst sclera-radius f32 80.0)
|
(defconst sclera-radius f32 80.0)
|
||||||
(defconst iris-radius f32 24.0)
|
(defconst iris-radius f32 24.0)
|
||||||
|
|
||||||
(defvar sclera-left rl/Vector2)
|
(defonce sclera-left rl/Vector2)
|
||||||
(defvar sclera-right rl/Vector2)
|
(defonce sclera-right rl/Vector2)
|
||||||
(defvar iris-left rl/Vector2)
|
(defonce iris-left rl/Vector2)
|
||||||
(defvar iris-right rl/Vector2)
|
(defonce iris-right rl/Vector2)
|
||||||
|
|
||||||
;; One eye's pupil, given where the mouse is and where the eye is. Answers the
|
;; One eye's pupil, given where the mouse is and where the eye is. Answers the
|
||||||
;; mouse position unchanged while it is inside the eye, and the point on the
|
;; mouse position unchanged while it is inside the eye, and the point on the
|
||||||
|
|||||||
@ -90,8 +90,8 @@
|
|||||||
;; ceiling on: this is the number that decides how big the font texture gets.
|
;; ceiling on: this is the number that decides how big the font texture gets.
|
||||||
(defconst max-codepoints 128)
|
(defconst max-codepoints 128)
|
||||||
|
|
||||||
(defvar unique-codepoints [max-codepoints i32])
|
(defonce unique-codepoints [max-codepoints i32])
|
||||||
(defvar unique-count i32)
|
(defonce unique-count i32)
|
||||||
|
|
||||||
;; Is `cp` already in the first `n` of the table?
|
;; Is `cp` already in the first `n` of the table?
|
||||||
(defn seen? [cp i32 n i32] bool
|
(defn seen? [cp i32 n i32] bool
|
||||||
@ -174,15 +174,15 @@
|
|||||||
(do (rl/get-codepoint-previous b off (addr size))
|
(do (rl/get-codepoint-previous b off (addr size))
|
||||||
(if (< (- off size) 0) 0 (- off size))))))
|
(if (< (- off size) 0) 0 (- off size))))))
|
||||||
|
|
||||||
(defvar font rl/Font)
|
(defonce font rl/Font)
|
||||||
;; Whether the TTF was there, asked once. A `defvar` and not the call itself
|
;; Whether the TTF was there, asked once. A `defonce` and not the call itself
|
||||||
;; in the draw loop: path-file? is a stat, and a syscall per frame to answer a
|
;; in the draw loop: path-file? is a stat, and a syscall per frame to answer a
|
||||||
;; question whose answer cannot change while the program runs is exactly what
|
;; question whose answer cannot change while the program runs is exactly what
|
||||||
;; the per-frame rule in docs/PORTING.md is about.
|
;; the per-frame rule in docs/PORTING.md is about.
|
||||||
(defvar font-present bool)
|
(defonce font-present bool)
|
||||||
(defvar show-font-atlas bool)
|
(defonce show-font-atlas bool)
|
||||||
(defvar cursor i32)
|
(defonce cursor i32)
|
||||||
(defvar codepoint-count i32)
|
(defonce codepoint-count i32)
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(rl/init-window screen-width screen-height
|
(rl/init-window screen-width screen-height
|
||||||
|
|||||||
@ -52,12 +52,12 @@
|
|||||||
(defconst max-input-chars 9)
|
(defconst max-input-chars 9)
|
||||||
|
|
||||||
;; No +1: see the header comment. There is no NUL to leave room for.
|
;; No +1: see the header comment. There is no NUL to leave room for.
|
||||||
(defvar name [9 u8])
|
(defonce name [9 u8])
|
||||||
(defvar letter-count i32)
|
(defonce letter-count i32)
|
||||||
|
|
||||||
(defvar text-box rl/Rectangle)
|
(defonce text-box rl/Rectangle)
|
||||||
(defvar mouse-on-text bool)
|
(defonce mouse-on-text bool)
|
||||||
(defvar frames-counter i32)
|
(defonce frames-counter i32)
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(rl/init-window screen-width screen-height
|
(rl/init-window screen-width screen-height
|
||||||
|
|||||||
@ -40,7 +40,7 @@
|
|||||||
(defconst message
|
(defconst message
|
||||||
"This sample illustrates a text writing\nanimation effect! Check it out! ;)")
|
"This sample illustrates a text writing\nanimation effect! Check it out! ;)")
|
||||||
|
|
||||||
(defvar frames-counter i32)
|
(defonce frames-counter i32)
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(rl/init-window screen-width screen-height
|
(rl/init-window screen-width screen-height
|
||||||
|
|||||||
@ -58,18 +58,18 @@
|
|||||||
(defconst tiles-y 15)
|
(defconst tiles-y 15)
|
||||||
|
|
||||||
;; 0 or 1, picked once: which of the two blues a tile is drawn in.
|
;; 0 or 1, picked once: which of the two blues a tile is drawn in.
|
||||||
(defvar tile-ids [375 u8])
|
(defonce tile-ids [375 u8])
|
||||||
|
|
||||||
;; 0 = never seen (solid black), 1 = visible now (no fog), 2 = seen before
|
;; 0 = never seen (solid black), 1 = visible now (no fog), 2 = seen before
|
||||||
;; (mostly black). The three-way state is why this is a byte per tile and not
|
;; (mostly black). The three-way state is why this is a byte per tile and not
|
||||||
;; a bit.
|
;; a bit.
|
||||||
(defvar tile-fog [375 u8])
|
(defonce tile-fog [375 u8])
|
||||||
|
|
||||||
(defvar player-position rl/Vector2)
|
(defonce player-position rl/Vector2)
|
||||||
(defvar player-tile-x i32)
|
(defonce player-tile-x i32)
|
||||||
(defvar player-tile-y i32)
|
(defonce player-tile-y i32)
|
||||||
|
|
||||||
(defvar fog-of-war rl/RenderTexture2D)
|
(defonce fog-of-war rl/RenderTexture2D)
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(rl/init-window screen-width screen-height
|
(rl/init-window screen-width screen-height
|
||||||
|
|||||||
@ -37,8 +37,8 @@
|
|||||||
;; same generator with a different `direction`.
|
;; same generator with a different `direction`.
|
||||||
(defconst num-textures 9)
|
(defconst num-textures 9)
|
||||||
|
|
||||||
(defvar textures [9 rl/Texture2D])
|
(defonce textures [9 rl/Texture2D])
|
||||||
(defvar current-texture i32)
|
(defonce current-texture i32)
|
||||||
|
|
||||||
;; Generate on the CPU, upload, drop the pixels. The texture holds a GL name
|
;; Generate on the CPU, upload, drop the pixels. The texture holds a GL name
|
||||||
;; and nothing of the Image, so the CPU copy can go as soon as the upload is
|
;; and nothing of the Image, so the CPU copy can go as soon as the upload is
|
||||||
|
|||||||
@ -80,10 +80,10 @@
|
|||||||
(defconst proc-flip-vertical 7)
|
(defconst proc-flip-vertical 7)
|
||||||
(defconst proc-flip-horizontal 8)
|
(defconst proc-flip-horizontal 8)
|
||||||
|
|
||||||
(defvar process-names [num-processes string])
|
(defonce process-names [num-processes string])
|
||||||
|
|
||||||
;; The nine toggle buttons down the left-hand side, laid out once at startup.
|
;; The nine toggle buttons down the left-hand side, laid out once at startup.
|
||||||
(defvar toggle-recs [num-processes rl/Rectangle])
|
(defonce toggle-recs [num-processes rl/Rectangle])
|
||||||
|
|
||||||
;; The generated picture's size. Small on purpose — see the header comment.
|
;; The generated picture's size. Small on purpose — see the header comment.
|
||||||
(defconst source-width 200)
|
(defconst source-width 200)
|
||||||
@ -127,11 +127,11 @@
|
|||||||
(= which proc-flip-vertical) (rl/image-flip-vertical img)
|
(= which proc-flip-vertical) (rl/image-flip-vertical img)
|
||||||
(= which proc-flip-horizontal) (rl/image-flip-horizontal img)))
|
(= which proc-flip-horizontal) (rl/image-flip-horizontal img)))
|
||||||
|
|
||||||
(defvar texture rl/Texture2D)
|
(defonce texture rl/Texture2D)
|
||||||
(defvar im-origin rl/Image)
|
(defonce im-origin rl/Image)
|
||||||
(defvar im-copy rl/Image)
|
(defonce im-copy rl/Image)
|
||||||
(defvar current-process i32)
|
(defonce current-process i32)
|
||||||
(defvar mouse-hover-rec i32)
|
(defonce mouse-hover-rec i32)
|
||||||
|
|
||||||
;; Throw the working copy away, take a fresh one from the original, run the
|
;; Throw the working copy away, take a fresh one from the original, run the
|
||||||
;; filter over it, and push the result at the texture that is already on the
|
;; filter over it, and push the result at the texture that is already on the
|
||||||
|
|||||||
@ -45,21 +45,21 @@
|
|||||||
;; The palette strip along the top. colors[0] is also the canvas's clear
|
;; The palette strip along the top. colors[0] is also the canvas's clear
|
||||||
;; colour and the "eraser", which is why raywhite is first and not simply
|
;; colour and the "eraser", which is why raywhite is first and not simply
|
||||||
;; another entry.
|
;; another entry.
|
||||||
(defvar colors [23 rl/Color])
|
(defonce colors [23 rl/Color])
|
||||||
(defvar colors-recs [23 rl/Rectangle])
|
(defonce colors-recs [23 rl/Rectangle])
|
||||||
|
|
||||||
(defvar color-selected i32)
|
(defonce color-selected i32)
|
||||||
(defvar color-selected-prev i32)
|
(defonce color-selected-prev i32)
|
||||||
(defvar color-mouse-hover i32)
|
(defonce color-mouse-hover i32)
|
||||||
(defvar brush-size f32)
|
(defonce brush-size f32)
|
||||||
(defvar mouse-was-pressed bool)
|
(defonce mouse-was-pressed bool)
|
||||||
|
|
||||||
(defvar btn-save-rec rl/Rectangle)
|
(defonce btn-save-rec rl/Rectangle)
|
||||||
(defvar btn-save-mouse-hover bool)
|
(defonce btn-save-mouse-hover bool)
|
||||||
(defvar show-save-message bool)
|
(defonce show-save-message bool)
|
||||||
(defvar save-message-counter i32)
|
(defonce save-message-counter i32)
|
||||||
|
|
||||||
(defvar target rl/RenderTexture2D)
|
(defonce target rl/RenderTexture2D)
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(rl/init-window screen-width screen-height
|
(rl/init-window screen-width screen-height
|
||||||
|
|||||||
29
lib/ast.ml
29
lib/ast.ml
@ -241,8 +241,16 @@ and decl_kind =
|
|||||||
(* Inline name/value pairs, as everywhere else. The members are what a
|
(* Inline name/value pairs, as everywhere else. The members are what a
|
||||||
keyword at a call site resolves against. *)
|
keyword at a call site resolves against. *)
|
||||||
| Defenum of string * (string * int64) list
|
| Defenum of string * (string * int64) list
|
||||||
(* value is optional: ZII. `uninit` opts out and is recorded as Uninit. *)
|
(* value is optional: ZII. `uninit` opts out and is recorded as Uninit.
|
||||||
| Defvar of string * texpr option * init
|
One constructor for the two defining forms that declare a mutable
|
||||||
|
global, told apart by the [reinit]: [defonce] is [Once] — its
|
||||||
|
initialiser runs only if the global is not already initialised, so the
|
||||||
|
value survives a daemon re-run — and [def] is [Every], Common Lisp's
|
||||||
|
defparameter: the initialiser runs on every re-run, so an edited one
|
||||||
|
takes effect on the next C-c C-c + re-run. They share everything else —
|
||||||
|
the spellings, the collision rules, the lowering — which is why the
|
||||||
|
difference is a field and not a second constructor. *)
|
||||||
|
| Defvar of string * texpr option * init * reinit
|
||||||
| Defconst of string * texpr option * expr
|
| Defconst of string * texpr option * expr
|
||||||
(* ── The dyn side's classes and generic functions ──────────────────
|
(* ── The dyn side's classes and generic functions ──────────────────
|
||||||
None of these four reaches [Check]. [Classes.expand] turns the whole set
|
None of these four reaches [Check]. [Classes.expand] turns the whole set
|
||||||
@ -293,7 +301,7 @@ and methd = { mgen : string; mkey : dispatch; mfn : fn; mkloc : Loc.t }
|
|||||||
|
|
||||||
and variant = { vname : string; vfields : field list; vloc : Loc.t }
|
and variant = { vname : string; vfields : field list; vloc : Loc.t }
|
||||||
|
|
||||||
(* [Ambiguous] is the three-element [(defvar x foo)] and [(defvar x (f y))]:
|
(* [Ambiguous] is the three-element [(defonce x foo)] and [(defonce x (f y))]:
|
||||||
forms whose third element parses as a type *and* as an expression, so which
|
forms whose third element parses as a type *and* as an expression, so which
|
||||||
one it is cannot be decided until names exist. The [texpr] beside it in
|
one it is cannot be decided until names exist. The [texpr] beside it in
|
||||||
[Defvar] is the type reading and this is the value reading; [Check.collect]
|
[Defvar] is the type reading and this is the value reading; [Check.collect]
|
||||||
@ -303,6 +311,11 @@ and variant = { vname : string; vfields : field list; vloc : Loc.t }
|
|||||||
these. *)
|
these. *)
|
||||||
and init = Zeroed | Uninit | Init of expr | Ambiguous of expr
|
and init = Zeroed | Uninit | Init of expr | Ambiguous of expr
|
||||||
|
|
||||||
|
(* What a daemon re-run does to the global: [Once] is [defonce] — initialise
|
||||||
|
if not already initialised, keep the value otherwise — and [Every] is
|
||||||
|
[def], which runs its initialiser on every re-run. *)
|
||||||
|
and reinit = Once | Every
|
||||||
|
|
||||||
(* Every top-level name a declaration introduces, whatever kind it is. There is
|
(* Every top-level name a declaration introduces, whatever kind it is. There is
|
||||||
one top-level namespace, so this is both the set [Load] renames on an import
|
one top-level namespace, so this is both the set [Load] renames on an import
|
||||||
and the set [Check] refuses to see twice — one definition, so the two cannot
|
and the set [Check] refuses to see twice — one definition, so the two cannot
|
||||||
@ -333,7 +346,7 @@ let method_name (m : methd) = m.mgen ^ "@" ^ dispatch_text m.mkey
|
|||||||
let declared_name (d : decl) =
|
let declared_name (d : decl) =
|
||||||
match d.d with
|
match d.d with
|
||||||
| Defenum (n, _) | Defalias (n, _) | Defstruct (n, _) | Defdata (n, _)
|
| Defenum (n, _) | Defalias (n, _) | Defstruct (n, _) | Defdata (n, _)
|
||||||
| Defunion (n, _) | Defvar (n, _, _) | Defconst (n, _, _)
|
| Defunion (n, _) | Defvar (n, _, _, _) | Defconst (n, _, _)
|
||||||
| Defclass (n, _) -> Some n
|
| Defclass (n, _) -> Some n
|
||||||
| Declare (fn, _) | DeclareC (fn, _) | Defn fn
|
| Declare (fn, _) | DeclareC (fn, _) | Defn fn
|
||||||
| Defgeneric fn | Defmulti fn -> Some fn.name
|
| Defgeneric fn | Defmulti fn -> Some fn.name
|
||||||
@ -452,12 +465,12 @@ let mark_pause ~line ~col (ds : decl list) : decl list option =
|
|||||||
| Defmethod m ->
|
| Defmethod m ->
|
||||||
{ d with d = Defmethod { m with mfn = { m.mfn with fbody = body m.mfn.fbody } } }
|
{ d with d = Defmethod { m with mfn = { m.mfn with fbody = body m.mfn.fbody } } }
|
||||||
| Defmulti f -> { d with d = Defmulti { f with fbody = body f.fbody } }
|
| Defmulti f -> { d with d = Defmulti { f with fbody = body f.fbody } }
|
||||||
| Defvar (n, t, Init e) -> { d with d = Defvar (n, t, Init (walk e)) }
|
| Defvar (n, t, Init e, k) -> { d with d = Defvar (n, t, Init (walk e), k) }
|
||||||
(* The value reading of an undecided [defvar] is walked too: if it is the
|
(* The value reading of an undecided [defonce] is walked too: if it is the
|
||||||
one that wins it is an initialiser like any other, and if the type
|
one that wins it is an initialiser like any other, and if the type
|
||||||
reading wins the expression is dropped whole and the mark with it. *)
|
reading wins the expression is dropped whole and the mark with it. *)
|
||||||
| Defvar (n, t, Ambiguous e) ->
|
| Defvar (n, t, Ambiguous e, k) ->
|
||||||
{ d with d = Defvar (n, t, Ambiguous (walk e)) }
|
{ d with d = Defvar (n, t, Ambiguous (walk e), k) }
|
||||||
| Defconst (n, t, e) -> { d with d = Defconst (n, t, walk e) }
|
| Defconst (n, t, e) -> { d with d = Defconst (n, t, walk e) }
|
||||||
| _ -> d
|
| _ -> d
|
||||||
in
|
in
|
||||||
|
|||||||
174
lib/check.ml
174
lib/check.ml
@ -833,7 +833,7 @@ let map_type ?(preds = []) loc (k : Types.t) (v : Types.t) =
|
|||||||
they are all the same position: something zeroes it.
|
they are all the same position: something zeroes it.
|
||||||
|
|
||||||
ZII is the language's rule — an omitted struct field, a fixed array's
|
ZII is the language's rule — an omitted struct field, a fixed array's
|
||||||
elements, a [defvar] with no initialiser are all all-bytes-zero — and a
|
elements, a [defonce] with no initialiser are all all-bytes-zero — and a
|
||||||
zeroed function value is a null pointer with a signature on it, which is the
|
zeroed function value is a null pointer with a signature on it, which is the
|
||||||
one kind of zero that cannot be used for anything. Every other type's zero
|
one kind of zero that cannot be used for anything. Every other type's zero
|
||||||
is a value: 0, false, an empty slice, [None], a data type's first case. So these
|
is a value: 0, false, an empty slice, [None], a data type's first case. So these
|
||||||
@ -986,7 +986,7 @@ let rec resolve env ?(seen = []) (t : Ast.texpr) : Types.t =
|
|||||||
is no longer a typo, it is a guess. *)
|
is no longer a typo, it is a guess. *)
|
||||||
and near_miss env ?(also = []) n =
|
and near_miss env ?(also = []) n =
|
||||||
(* [also] widens the candidate list past the types, and exactly one caller
|
(* [also] widens the candidate list past the types, and exactly one caller
|
||||||
passes it: the defvar whose third element has to be a type *or* a value,
|
passes it: the defonce whose third element has to be a type *or* a value,
|
||||||
whose suggestion is worth nothing if it can only ever name a type. *)
|
whose suggestion is worth nothing if it can only ever name a type. *)
|
||||||
let candidates =
|
let candidates =
|
||||||
also
|
also
|
||||||
@ -1256,24 +1256,24 @@ let pair_decls env (decls : Ast.decl list) : Ast.decl list =
|
|||||||
| _ -> d)
|
| _ -> d)
|
||||||
decls
|
decls
|
||||||
|
|
||||||
(* ── The third element of a defvar, decided ────────────────────────────
|
(* ── The third element of a defonce, decided ────────────────────────────
|
||||||
|
|
||||||
The author's rule, 2026-09-20: "if it's 3 atoms then it's dyn", and
|
The author's rule, 2026-09-20: "if it's 3 atoms then it's dyn", and
|
||||||
"dispatch the if it's a type do the right thing". [(defvar current-color
|
"dispatch the if it's a type do the right thing". [(defonce current-color
|
||||||
i32)] is the zeroed static it has always been, and [(defvar score 0)] is a
|
i32)] is the zeroed static it has always been, and [(defonce score 0)] is a
|
||||||
dyn global holding 0 — the same thing [(defvar score dyn 0)] spells out,
|
dyn global holding 0 — the same thing [(defonce score dyn 0)] spells out,
|
||||||
lowered by the same path and not by a second one.
|
lowered by the same path and not by a second one.
|
||||||
|
|
||||||
[Parse] settled every shape a shape can settle and handed the rest over
|
[Parse] settled every shape a shape can settle and handed the rest over
|
||||||
carrying both readings ([Ast.Ambiguous], beside the type reading in the
|
carrying both readings ([Ast.Ambiguous], beside the type reading in the
|
||||||
same [Defvar]). What is left is the two forms only a name can settle, and
|
same [Defvar]). What is left is the two forms only a name can settle, and
|
||||||
this is the first point where every type name is in hand: the same point
|
this is the first point where every type name is in hand: the same point
|
||||||
[pair_params] reads, for the same reason — a defvar may name a struct
|
[pair_params] reads, for the same reason — a defonce may name a struct
|
||||||
declared fifty lines below it.
|
declared fifty lines below it.
|
||||||
|
|
||||||
The type reading wins wherever there is one. That is what keeps today's
|
The type reading wins wherever there is one. That is what keeps today's
|
||||||
programs meaning today's thing: [(defvar p Point)] is a zeroed [Point],
|
programs meaning today's thing: [(defonce p Point)] is a zeroed [Point],
|
||||||
[(defvar v (Vec i32))] is a zeroed [Vec], and a wrong type argument inside
|
[(defonce v (Vec i32))] is a zeroed [Vec], and a wrong type argument inside
|
||||||
one stays a type error rather than becoming an unknown function. It is also
|
one stays a type error rather than becoming an unknown function. It is also
|
||||||
why a built-in constructor is checked by name rather than by whether
|
why a built-in constructor is checked by name rather than by whether
|
||||||
[resolve] happens to accept it — [(Vec i32 i32)] is a malformed [Vec] and
|
[resolve] happens to accept it — [(Vec i32 i32)] is a malformed [Vec] and
|
||||||
@ -1307,14 +1307,14 @@ let defvar_reads_as_type env (t : Ast.texpr) =
|
|||||||
name; and a plain type typo, where a confident one-edit suggestion turns a
|
name; and a plain type typo, where a confident one-edit suggestion turns a
|
||||||
one-line answer into four lines of unrelated reading. The paragraph is for
|
one-line answer into four lines of unrelated reading. The paragraph is for
|
||||||
the name that genuinely could have been either and is neither. *)
|
the name that genuinely could have been either and is neither. *)
|
||||||
let defvar_neither env loc gname n ~values ~cases =
|
let defvar_neither env loc ~form gname n ~values ~cases =
|
||||||
(match List.assoc_opt n cases with
|
(match List.assoc_opt n cases with
|
||||||
| Some dname ->
|
| Some dname ->
|
||||||
Loc.failk "check/defvar-case-not-type" loc
|
Loc.failk "check/defvar-case-not-type" loc
|
||||||
"%s is a case of the data type %s, and a case is not a type of its \
|
"%s is a case of the data type %s, and a case is not a type of its \
|
||||||
own — the global's type is the data type: (defvar %s %s). Assign the \
|
own — the global's type is the data type: (%s %s %s). Assign the \
|
||||||
case you want, as (set %s (%s.%s {.field value ...}))"
|
case you want, as (set %s (%s.%s {.field value ...}))"
|
||||||
n dname gname dname gname dname n
|
n dname form gname dname gname dname n
|
||||||
| None -> ());
|
| None -> ());
|
||||||
(match foreign_spelling n with
|
(match foreign_spelling n with
|
||||||
| Some m ->
|
| Some m ->
|
||||||
@ -1330,15 +1330,15 @@ let defvar_neither env loc gname n ~values ~cases =
|
|||||||
| None -> ""
|
| None -> ""
|
||||||
in
|
in
|
||||||
Loc.failk "check/defvar-neither-type-nor-value" loc
|
Loc.failk "check/defvar-neither-type-nor-value" loc
|
||||||
"%s is neither a type nor a value, and the third element of a defvar has \
|
"%s is neither a type nor a value, and the third element of a %s has \
|
||||||
to be one or the other: a type there declares a zeroed global of that \
|
to be one or the other: a type there declares a zeroed global of that \
|
||||||
type — (defvar %s i64) — and a value there declares a dyn global holding \
|
type — (%s %s i64) — and a value there declares a dyn global holding \
|
||||||
it — (defvar %s 0). Nothing named %s is declared as either%s"
|
it — (%s %s 0). Nothing named %s is declared as either%s"
|
||||||
n gname gname n hint
|
n form form gname form gname n hint
|
||||||
|
|
||||||
(* Every name a value could be written under, which is every declaration that
|
(* Every name a value could be written under, which is every declaration that
|
||||||
is not a type plus whatever a session already has. The list is only ever
|
is not a type plus whatever a session already has. The list is only ever
|
||||||
asked "is this name declared at all", so a global that is itself a defvar
|
asked "is this name declared at all", so a global that is itself a defonce
|
||||||
still undecided belongs on it: what it resolves to is the next pass's
|
still undecided belongs on it: what it resolves to is the next pass's
|
||||||
question, not this one's. *)
|
question, not this one's. *)
|
||||||
(* Case name -> the data type it belongs to, read off the declarations rather
|
(* Case name -> the data type it belongs to, read off the declarations rather
|
||||||
@ -1361,7 +1361,7 @@ let value_names env (decls : Ast.decl list) =
|
|||||||
List.filter_map
|
List.filter_map
|
||||||
(fun (d : Ast.decl) ->
|
(fun (d : Ast.decl) ->
|
||||||
match d.Ast.d with
|
match d.Ast.d with
|
||||||
| Ast.Defvar (n, _, _) | Ast.Defconst (n, _, _) -> Some n
|
| Ast.Defvar (n, _, _, _) | Ast.Defconst (n, _, _) -> Some n
|
||||||
| Ast.Defn fn | Ast.Declare (fn, _) | Ast.DeclareC (fn, _) ->
|
| Ast.Defn fn | Ast.Declare (fn, _) | Ast.DeclareC (fn, _) ->
|
||||||
Some fn.Ast.name
|
Some fn.Ast.name
|
||||||
| _ -> None)
|
| _ -> None)
|
||||||
@ -1371,21 +1371,21 @@ let value_names env (decls : Ast.decl list) =
|
|||||||
@ Hashtbl.fold (fun k _ acc -> k :: acc) env.globals []
|
@ Hashtbl.fold (fun k _ acc -> k :: acc) env.globals []
|
||||||
@ Hashtbl.fold (fun k _ acc -> k :: acc) env.fns []
|
@ Hashtbl.fold (fun k _ acc -> k :: acc) env.fns []
|
||||||
|
|
||||||
(* The decision, applied: an undecided defvar leaves this pass as one of the
|
(* The decision, applied: an undecided defonce leaves this pass as one of the
|
||||||
two forms that already existed, so no pass after it — the signature loop
|
two forms that already existed, so no pass after it — the signature loop
|
||||||
below, [check_global], either backend — has a third case to know about. The
|
below, [check_global], either backend — has a third case to know about. The
|
||||||
dyn reading is rewritten into exactly [(defvar x dyn <expr>)], which is the
|
dyn reading is rewritten into exactly [(defonce x dyn <expr>)], which is the
|
||||||
whole of "it lowers to the same thing": the startup lifting, the re-run
|
whole of "it lowers to the same thing": the startup lifting, the re-run
|
||||||
guard and the collector root are the ones that form already had. *)
|
guard and the collector root are the ones that form already had. *)
|
||||||
(* A bracket form whose element names a value. [(defvar g [a b])] parses as a
|
(* A bracket form whose element names a value. [(defonce g [a b])] parses as a
|
||||||
type and stays one — type wins wherever there is a type reading, which is
|
type and stays one — type wins wherever there is a type reading, which is
|
||||||
the rule — so the element had to name an element type, and [b] names a
|
the rule — so the element had to name an element type, and [b] names a
|
||||||
defvar. Left alone this reaches [resolve_name], where a lowercase name that
|
defonce. Left alone this reaches [resolve_name], where a lowercase name that
|
||||||
is no type is a type variable, and the answer is a paragraph about generic
|
is no type is a type variable, and the answer is a paragraph about generic
|
||||||
code the writer was not asking for.
|
code the writer was not asking for.
|
||||||
|
|
||||||
Both readings, and both spellings, at the element that decided it. The dyn
|
Both readings, and both spellings, at the element that decided it. The dyn
|
||||||
spelling is the one that actually works: [(defvar g dyn [a b])] is a dyn
|
spelling is the one that actually works: [(defonce g dyn [a b])] is a dyn
|
||||||
global holding a vector, which is what the brackets meant to whoever wrote
|
global holding a vector, which is what the brackets meant to whoever wrote
|
||||||
them. *)
|
them. *)
|
||||||
let rec bracket_value_element env values (t : Ast.texpr) =
|
let rec bracket_value_element env values (t : Ast.texpr) =
|
||||||
@ -1407,35 +1407,37 @@ let settle_defvars env (decls : Ast.decl list) : Ast.decl list =
|
|||||||
[Zeroed] outright, because a bracket that parses as a type has no second
|
[Zeroed] outright, because a bracket that parses as a type has no second
|
||||||
reading to carry. So the element check runs on both, and it is the only
|
reading to carry. So the element check runs on both, and it is the only
|
||||||
thing the [Zeroed] arm does. *)
|
thing the [Zeroed] arm does. *)
|
||||||
let brackets gname (t : Ast.texpr) =
|
let brackets ~form gname (t : Ast.texpr) =
|
||||||
match bracket_value_element env (Lazy.force values) t with
|
match bracket_value_element env (Lazy.force values) t with
|
||||||
| Some (v, vloc) ->
|
| Some (v, vloc) ->
|
||||||
Loc.failk "check/defvar-bracket-element-is-a-value" vloc
|
Loc.failk "check/defvar-bracket-element-is-a-value" vloc
|
||||||
"%s names a value, not a type, and the brackets around it were read \
|
"%s names a value, not a type, and the brackets around it were read \
|
||||||
as a type — a defvar's third element is a type wherever there is a \
|
as a type — a %s's third element is a type wherever there is a \
|
||||||
type reading, so %s had to be the element type. Write a type there \
|
type reading, so %s had to be the element type. Write a type there \
|
||||||
for a zeroed global, or put dyn in front of the same brackets — \
|
for a zeroed global, or put dyn in front of the same brackets — \
|
||||||
(defvar %s dyn ...) — for a dyn global holding the vector you wrote"
|
(%s %s dyn ...) — for a dyn global holding the vector you wrote"
|
||||||
v v gname
|
v form v form gname
|
||||||
| None -> ()
|
| None -> ()
|
||||||
in
|
in
|
||||||
|
let word = function Ast.Once -> "defonce" | Ast.Every -> "def" in
|
||||||
List.map
|
List.map
|
||||||
(fun (d : Ast.decl) ->
|
(fun (d : Ast.decl) ->
|
||||||
match d.Ast.d with
|
match d.Ast.d with
|
||||||
| Ast.Defvar (n, Some t, Ast.Zeroed) -> brackets n t; d
|
| Ast.Defvar (n, Some t, Ast.Zeroed, k) -> brackets ~form:(word k) n t; d
|
||||||
| Ast.Defvar (n, Some t, Ast.Ambiguous e) ->
|
| Ast.Defvar (n, Some t, Ast.Ambiguous e, k) ->
|
||||||
|
let form = word k in
|
||||||
if defvar_reads_as_type env t then begin
|
if defvar_reads_as_type env t then begin
|
||||||
brackets n t;
|
brackets ~form n t;
|
||||||
{ d with Ast.d = Ast.Defvar (n, Some t, Ast.Zeroed) }
|
{ d with Ast.d = Ast.Defvar (n, Some t, Ast.Zeroed, k) }
|
||||||
end
|
end
|
||||||
else begin
|
else begin
|
||||||
(match t.Ast.t with
|
(match t.Ast.t with
|
||||||
| Ast.Tname s when not (List.mem s (Lazy.force values)) ->
|
| Ast.Tname s when not (List.mem s (Lazy.force values)) ->
|
||||||
defvar_neither env t.Ast.tloc n s ~values:(Lazy.force values)
|
defvar_neither env t.Ast.tloc ~form n s
|
||||||
~cases:(Lazy.force cases)
|
~values:(Lazy.force values) ~cases:(Lazy.force cases)
|
||||||
| _ -> ());
|
| _ -> ());
|
||||||
let dyn = { Ast.t = Ast.Tname "dyn"; tloc = t.Ast.tloc } in
|
let dyn = { Ast.t = Ast.Tname "dyn"; tloc = t.Ast.tloc } in
|
||||||
{ d with Ast.d = Ast.Defvar (n, Some dyn, Ast.Init e) }
|
{ d with Ast.d = Ast.Defvar (n, Some dyn, Ast.Init e, k) }
|
||||||
end
|
end
|
||||||
| _ -> d)
|
| _ -> d)
|
||||||
decls
|
decls
|
||||||
@ -1871,7 +1873,7 @@ let to_bytes ctx loc pr (x : Tast.expr) =
|
|||||||
Emitted at every site that can *allocate* for such a container, not only at
|
Emitted at every site that can *allocate* for such a container, not only at
|
||||||
its construction, and the extra sites are not belt and braces. ZII means a
|
its construction, and the extra sites are not belt and braces. ZII means a
|
||||||
container can exist without ever passing through [vec-new]: a data type
|
container can exist without ever passing through [vec-new]: a data type
|
||||||
case's field left out of a literal, a [(defvar xs (Vec Value))] a global
|
case's field left out of a literal, a [(defonce xs (Vec Value))] a global
|
||||||
starts as. Those are zeroed, they have no allocator at all, and the first
|
starts as. Those are zeroed, they have no allocator at all, and the first
|
||||||
[push] is what adopts the context — so a guard only at the construction
|
[push] is what adopts the context — so a guard only at the construction
|
||||||
would have a hole exactly the width of ZII.
|
would have a hole exactly the width of ZII.
|
||||||
@ -2117,7 +2119,7 @@ let view_not_permanent loc (container : Types.t) =
|
|||||||
"%s does not cross into dyn as a view here — its storage is not known \
|
"%s does not cross into dyn as a view here — its storage is not known \
|
||||||
to outlive the view, and a view is exactly as stale-safe as the thing \
|
to outlive the view, and a view is exactly as stale-safe as the thing \
|
||||||
it is a view of, no more and no less. A global's storage does outlive \
|
it is a view of, no more and no less. A global's storage does outlive \
|
||||||
it: (defvar g %s ...) viewed from anywhere reads storage fixed for the \
|
it: (defonce g %s ...) viewed from anywhere reads storage fixed for the \
|
||||||
process, and so does a field or an array element of one. A local, a \
|
process, and so does a field or an array element of one. A local, a \
|
||||||
parameter, a temporary, anything reached through a slice at any index \
|
parameter, a temporary, anything reached through a slice at any index \
|
||||||
level — even a global one, which holds only ptr+len and can point at a \
|
level — even a global one, which holds only ptr+len and can point at a \
|
||||||
@ -2163,7 +2165,7 @@ let box loc (e : Tast.expr) : Tast.expr =
|
|||||||
both, and they share [flan_dyn_view_flat]. *)
|
both, and they share [flan_dyn_view_flat]. *)
|
||||||
(* The element check runs before the lifetime one in all three arms, and
|
(* The element check runs before the lifetime one in all three arms, and
|
||||||
the order is load-bearing rather than incidental: the lifetime message
|
the order is load-bearing rather than incidental: the lifetime message
|
||||||
points at [(defvar g ...)] as the spelling that works, and for an
|
points at [(defonce g ...)] as the spelling that works, and for an
|
||||||
element type no view can carry — a string, an i32 — the global spelling
|
element type no view can carry — a string, an i32 — the global spelling
|
||||||
is refused too, so the wrong order hands the programmer advice that
|
is refused too, so the wrong order hands the programmer advice that
|
||||||
fails when they take it. Whichever refusal is unconditional wins. *)
|
fails when they take it. Whichever refusal is unconditional wins. *)
|
||||||
@ -2442,7 +2444,7 @@ let expect ctx loc ~want (got : Tast.expr) =
|
|||||||
| _, Types.Dyn -> unbox loc w got
|
| _, Types.Dyn -> unbox loc w got
|
||||||
(* Implicit widening, and this single arm is the whole of its surface.
|
(* Implicit widening, and this single arm is the whole of its surface.
|
||||||
[expect] is called by every site that annotates and by nothing else,
|
[expect] is called by every site that annotates and by nothing else,
|
||||||
so an argument, a return, a let or defvar with a type, a struct field
|
so an argument, a return, a let or defonce with a type, a struct field
|
||||||
initialiser, a push into a Vec and a C import's parameter all get it
|
initialiser, a push into a Vec and a C import's parameter all get it
|
||||||
here at once and none of them had to learn about it.
|
here at once and none of them had to learn about it.
|
||||||
|
|
||||||
@ -3282,7 +3284,7 @@ and int_literal loc ~want ?(preds = []) ?(default = Types.I32) n =
|
|||||||
mk loc (Types.Var v) (Tast.Int (n, Types.I64))
|
mk loc (Types.Var v) (Tast.Int (n, Types.I64))
|
||||||
(* A literal in dyn position takes i64 and not the i32 an unconstrained one
|
(* A literal in dyn position takes i64 and not the i32 an unconstrained one
|
||||||
defaults to. This is where "dyn integers are i64" stops being a statement
|
defaults to. This is where "dyn integers are i64" stops being a statement
|
||||||
about the ABI and becomes one about the language: [(defvar x dyn 5)] holds
|
about the ABI and becomes one about the language: [(defonce x dyn 5)] holds
|
||||||
an i64 five, and the defaulting question a wider set of boxes would raise
|
an i64 five, and the defaulting question a wider set of boxes would raise
|
||||||
never arises because there is only the one box. Handled here rather than
|
never arises because there is only the one box. Handled here rather than
|
||||||
left to [expect] so the literal is *built* at the right width — the range
|
left to [expect] so the literal is *built* at the right width — the range
|
||||||
@ -3430,7 +3432,7 @@ and var ctx ?(qualified = false) loc ~want name =
|
|||||||
write %s.%s" name dname dname c.Tast.vname
|
write %s.%s" name dname dname c.Tast.vname
|
||||||
| None ->
|
| None ->
|
||||||
(* A bare function name *is* the function. This is a Lisp-1 — one
|
(* A bare function name *is* the function. This is a Lisp-1 — one
|
||||||
top-level namespace, enforced, so a defn and a defvar cannot share
|
top-level namespace, enforced, so a defn and a defonce cannot share
|
||||||
a name — and that is exactly what makes (map double xs) safe to
|
a name — and that is exactly what makes (map double xs) safe to
|
||||||
read: there is no second binding of [double] for it to have meant
|
read: there is no second binding of [double] for it to have meant
|
||||||
instead, so Common Lisp's #'double would be punctuation answering
|
instead, so Common Lisp's #'double would be punctuation answering
|
||||||
@ -4807,7 +4809,7 @@ and check_arr ctx ~want loc items =
|
|||||||
|
|
||||||
DISCUSS.org's "need a value-producing array constructor". [(array 4 T)] is
|
DISCUSS.org's "need a value-producing array constructor". [(array 4 T)] is
|
||||||
the zeroed array and [dotimes] is Unit, so between them there was no way to
|
the zeroed array and [dotimes] is Unit, so between them there was no way to
|
||||||
write "an array of these" as an *expression* — which is what a defvar
|
write "an array of these" as an *expression* — which is what a defonce
|
||||||
initialiser has to be. These are that expression, at any rank.
|
initialiser has to be. These are that expression, at any rank.
|
||||||
|
|
||||||
**The lowering, and why it is not an aggregate value.** [Tast.Arr] is the
|
**The lowering, and why it is not an aggregate value.** [Tast.Arr] is the
|
||||||
@ -4823,7 +4825,7 @@ and check_arr ctx ~want loc items =
|
|||||||
|
|
||||||
The value stays value-like for all that: the slot is the form's own, nothing
|
The value stays value-like for all that: the slot is the form's own, nothing
|
||||||
else can name it, and the [Local] at the end is copied out exactly as any
|
else can name it, and the [Local] at the end is copied out exactly as any
|
||||||
other array-typed expression is. In a [defvar] initialiser the copy is the
|
other array-typed expression is. In a [defonce] initialiser the copy is the
|
||||||
store into the global that the startup function does; in a [let] it is the
|
store into the global that the startup function does; in a [let] it is the
|
||||||
binding's own store. An in-place fill of the *destination*, skipping the
|
binding's own store. An in-place fill of the *destination*, skipping the
|
||||||
temporary, would be the faster lowering and is deliberately not what this
|
temporary, would be the faster lowering and is deliberately not what this
|
||||||
@ -5392,7 +5394,7 @@ and check_place ctx loc (p : Ast.place) : Tast.place * Types.t =
|
|||||||
Loc.failk "check/set-constant" loc ~notes
|
Loc.failk "check/set-constant" loc ~notes
|
||||||
"%s is a constant, and a constant is not assignable — it is written \
|
"%s is a constant, and a constant is not assignable — it is written \
|
||||||
into the image and there is nothing to assign to. Declare it with \
|
into the image and there is nothing to assign to. Declare it with \
|
||||||
defvar if it has to change" name
|
defonce if it has to change" name
|
||||||
| Some (ty, false) -> Tast.Pglobal name, ty
|
| Some (ty, false) -> Tast.Pglobal name, ty
|
||||||
| None -> captured ctx loc name; unknown_name ~setting:true ctx loc name)
|
| None -> captured ctx loc name; unknown_name ~setting:true ctx loc name)
|
||||||
| Ast.Pfield (target, name) ->
|
| Ast.Pfield (target, name) ->
|
||||||
@ -6489,7 +6491,7 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
|||||||
it holds, and the element type is written at the call instead. This is not
|
it holds, and the element type is written at the call instead. This is not
|
||||||
the explicit instantiation syntax the generics section rules out: nothing
|
the explicit instantiation syntax the generics section rules out: nothing
|
||||||
here is generic, and the name is resolved as an ordinary type, not bound
|
here is generic, and the name is resolved as an ordinary type, not bound
|
||||||
to a type variable. Where the context does say — a defvar's type, a
|
to a type variable. Where the context does say — a defonce's type, a
|
||||||
function's return type, an argument — it is not needed and may be left
|
function's return type, an argument — it is not needed and may be left
|
||||||
out. *)
|
out. *)
|
||||||
| "vec-new" ->
|
| "vec-new" ->
|
||||||
@ -6797,7 +6799,7 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
|||||||
(* (map-new), (map-new K V), (map-new a), (map-new K V a). The same shape
|
(* (map-new), (map-new K V), (map-new a), (map-new K V a). The same shape
|
||||||
[vec-new] has and for the same reason: a [let] has no type annotation, so
|
[vec-new] has and for the same reason: a [let] has no type annotation, so
|
||||||
a local map has nowhere else to say what it holds. Where the context does
|
a local map has nowhere else to say what it holds. Where the context does
|
||||||
say — a defvar's type, a parameter, a return type — the pair may be left
|
say — a defonce's type, a parameter, a return type — the pair may be left
|
||||||
out. *)
|
out. *)
|
||||||
| "map-new" ->
|
| "map-new" ->
|
||||||
let k, v, args = map_new_types ctx ~want loc args in
|
let k, v, args = map_new_types ctx ~want loc args in
|
||||||
@ -8039,10 +8041,10 @@ and ordinary_call ctx ~want loc name args =
|
|||||||
"unknown function %s — did you mean %s?" name m
|
"unknown function %s — did you mean %s?" name m
|
||||||
| None ->
|
| None ->
|
||||||
if args <> [] && capitalised then
|
if args <> [] && capitalised then
|
||||||
(* [(defvar p (Pair i32))]. A capitalised head with arguments and
|
(* [(defonce p (Pair i32))]. A capitalised head with arguments and
|
||||||
no near miss anywhere is somebody reaching for a parameterised
|
no near miss anywhere is somebody reaching for a parameterised
|
||||||
type, which is what the type resolver says about [(Pair i32)]
|
type, which is what the type resolver says about [(Pair i32)]
|
||||||
when the same text lands in a type position. Before defvar took
|
when the same text lands in a type position. Before defonce took
|
||||||
either reading, that is the message this text got; it says the
|
either reading, that is the message this text got; it says the
|
||||||
same thing here so the answer does not depend on which side of
|
same thing here so the answer does not depend on which side of
|
||||||
the fork the form fell down. *)
|
the fork the form fell down. *)
|
||||||
@ -8081,7 +8083,7 @@ and ordinary_call ctx ~want loc name args =
|
|||||||
carries a slash answers correctly everywhere a call sits inside a
|
carries a slash answers correctly everywhere a call sits inside a
|
||||||
function — and wrongly in the one place a call does not: a package's
|
function — and wrongly in the one place a call does not: a package's
|
||||||
global initialiser, which is checked with no owner at all. An importer
|
global initialiser, which is checked with no owner at all. An importer
|
||||||
defining [len] reached inside an imported [(defvar sz i32 (len "abcd"))]
|
defining [len] reached inside an imported [(defonce sz i32 (len "abcd"))]
|
||||||
and changed what it computed. The files were never wrong about it.
|
and changed what it computed. The files were never wrong about it.
|
||||||
|
|
||||||
What it costs is the REPL: an expression evaluated with no file behind it
|
What it costs is the REPL: an expression evaluated with no file behind it
|
||||||
@ -9121,7 +9123,7 @@ let rec const_int env (e : Ast.expr) : int64 option =
|
|||||||
A type name inside an array literal is unambiguous evidence, because a type
|
A type name inside an array literal is unambiguous evidence, because a type
|
||||||
and a value cannot share a name: [collect]'s claimed table is over every
|
and a value cannot share a name: [collect]'s claimed table is over every
|
||||||
declaration kind there is. So finding one means the whole form was meant as
|
declaration kind there is. So finding one means the whole form was meant as
|
||||||
a type, and the form that takes one is [defvar]. *)
|
a type, and the form that takes one is [defonce]. *)
|
||||||
let rec defconst_type_shaped env gname (v : Ast.expr) =
|
let rec defconst_type_shaped env gname (v : Ast.expr) =
|
||||||
match v.Ast.e with
|
match v.Ast.e with
|
||||||
| Ast.Arr items ->
|
| Ast.Arr items ->
|
||||||
@ -9133,7 +9135,7 @@ let rec defconst_type_shaped env gname (v : Ast.expr) =
|
|||||||
"%s is a type, and this is a value: a two-element defconst has no \
|
"%s is a type, and this is a value: a two-element defconst has no \
|
||||||
type slot, so the brackets around it were read as an array \
|
type slot, so the brackets around it were read as an array \
|
||||||
literal and %s as a name in it. A global declared by its type is \
|
literal and %s as a name in it. A global declared by its type is \
|
||||||
a defvar — write (defvar %s ...) with the same brackets"
|
a defonce — write (defonce %s ...) with the same brackets"
|
||||||
n n gname
|
n n gname
|
||||||
| _ -> defconst_type_shaped env gname i)
|
| _ -> defconst_type_shaped env gname i)
|
||||||
items
|
items
|
||||||
@ -9144,7 +9146,7 @@ let collect env (decls : Ast.decl list) =
|
|||||||
the tables below are per-kind — structs, data types, aliases, enums, functions
|
the tables below are per-kind — structs, data types, aliases, enums, functions
|
||||||
and globals each have their own — and a collision between two of them
|
and globals each have their own — and a collision between two of them
|
||||||
would otherwise be found by LLVM, as [redefinition of function
|
would otherwise be found by LLVM, as [redefinition of function
|
||||||
'@flan.item'], or not at all. A [defn item] and a [defvar item] are two
|
'@flan.item'], or not at all. A [defn item] and a [defonce item] are two
|
||||||
declarations of one name and are rejected here. *)
|
declarations of one name and are rejected here. *)
|
||||||
(* The qualifier is reserved on this side too. [(defn builtin/len ...)]
|
(* The qualifier is reserved on this side too. [(defn builtin/len ...)]
|
||||||
reads — the reader treats [/] as an ordinary symbol character — and would
|
reads — the reader treats [/] as an ordinary symbol character — and would
|
||||||
@ -9279,7 +9281,7 @@ let collect env (decls : Ast.decl list) =
|
|||||||
signature may name a type declared further down and pairing must not depend
|
signature may name a type declared further down and pairing must not depend
|
||||||
on the order the file was written in. *)
|
on the order the file was written in. *)
|
||||||
let decls = pair_decls env decls in
|
let decls = pair_decls env decls in
|
||||||
(* And for the same reason, at the same point: a three-element defvar is a
|
(* And for the same reason, at the same point: a three-element defonce is a
|
||||||
type or a value by name, and every type name is registered by here. *)
|
type or a value by name, and every type name is registered by here. *)
|
||||||
let decls = settle_defvars env decls in
|
let decls = settle_defvars env decls in
|
||||||
List.iter
|
List.iter
|
||||||
@ -9487,10 +9489,12 @@ let collect env (decls : Ast.decl list) =
|
|||||||
Hashtbl.replace env.generics fn.Ast.name fn;
|
Hashtbl.replace env.generics fn.Ast.name fn;
|
||||||
Hashtbl.replace env.gsigs fn.Ast.name (vars, params, ret)
|
Hashtbl.replace env.gsigs fn.Ast.name (vars, params, ret)
|
||||||
end
|
end
|
||||||
| Ast.Defvar (n, t, _) ->
|
| Ast.Defvar (n, t, _, k) ->
|
||||||
let ty = match t with
|
let ty = match t with
|
||||||
| Some t -> resolve env t
|
| Some t -> resolve env t
|
||||||
| None -> fail loc "defvar %s needs a type" n
|
| None ->
|
||||||
|
fail loc "%s %s needs a type"
|
||||||
|
(match k with Ast.Once -> "defonce" | Ast.Every -> "def") n
|
||||||
in
|
in
|
||||||
Hashtbl.replace env.globals n (ty, false);
|
Hashtbl.replace env.globals n (ty, false);
|
||||||
Hashtbl.replace env.global_locs n loc
|
Hashtbl.replace env.global_locs n loc
|
||||||
@ -9794,7 +9798,7 @@ let () = check_fn_ref := check_fn
|
|||||||
backends: a computed initialiser is lifted into a function of its own and
|
backends: a computed initialiser is lifted into a function of its own and
|
||||||
the program calls it from [main], after the runtime is up and before any of
|
the program calls it from [main], after the runtime is up and before any of
|
||||||
the program's own code runs. So the premise is gone and the refusal goes
|
the program's own code runs. So the premise is gone and the refusal goes
|
||||||
with it. (defvar g (Vec u8) (slurp "level.edn")) is an ordinary program now,
|
with it. (defonce g (Vec u8) (slurp "level.edn")) is an ordinary program now,
|
||||||
and it is the shape the author kept writing.
|
and it is the shape the author kept writing.
|
||||||
|
|
||||||
What is still refused is [uninit] on one, and that is a different rule with
|
What is still refused is [uninit] on one, and that is a different rule with
|
||||||
@ -9824,12 +9828,12 @@ let container_global_init loc n (ty : Types.t) (init : Ast.init) =
|
|||||||
fail loc
|
fail loc
|
||||||
"the global %s is %s, and uninit on one is refused: its block pointer \
|
"the global %s is %s, and uninit on one is refused: its block pointer \
|
||||||
steers every read of it, and garbage there is not a garbage number \
|
steers every read of it, and garbage there is not a garbage number \
|
||||||
the way it is for an f64. Write (defvar %s %s) with no initialiser — \
|
the way it is for an f64. Write (defonce %s %s) with no initialiser — \
|
||||||
a zeroed %s is an empty one, and that is a value, not a placeholder"
|
a zeroed %s is an empty one, and that is a value, not a placeholder"
|
||||||
n (Types.to_string ty) n (Types.to_string ty) (Types.to_string ty)
|
n (Types.to_string ty) n (Types.to_string ty) (Types.to_string ty)
|
||||||
| _ -> ()
|
| _ -> ()
|
||||||
|
|
||||||
(* A container global has to be a [defvar]. A [defconst] is not an assignable
|
(* A container global has to be a [defonce]. A [defconst] is not an assignable
|
||||||
place — [check_place] refuses one by name — and a container's only constant
|
place — [check_place] refuses one by name — and a container's only constant
|
||||||
is the zeroed one, so a constant Vec could only ever hold the empty value
|
is the zeroed one, so a constant Vec could only ever hold the empty value
|
||||||
it was declared with: nothing could ever put the file's bytes in it.
|
it was declared with: nothing could ever put the file's bytes in it.
|
||||||
@ -9838,10 +9842,10 @@ let container_global_init loc n (ty : Types.t) (init : Ast.init) =
|
|||||||
let no_container_defconst loc n (ty : Types.t) =
|
let no_container_defconst loc n (ty : Types.t) =
|
||||||
if zero_only ty then
|
if zero_only ty then
|
||||||
fail loc
|
fail loc
|
||||||
"the global %s is %s, and a %s global is a defvar and not a defconst: \
|
"the global %s is %s, and a %s global is a defonce and not a defconst: \
|
||||||
a constant is not an assignable place, so nothing could ever load \
|
a constant is not an assignable place, so nothing could ever load \
|
||||||
this one — it would stay the empty %s it was declared as. Write \
|
this one — it would stay the empty %s it was declared as. Write \
|
||||||
(defvar %s %s) and fill it in a function"
|
(defonce %s %s) and fill it in a function"
|
||||||
n (Types.to_string ty) (Types.to_string ty) (Types.to_string ty)
|
n (Types.to_string ty) (Types.to_string ty) (Types.to_string ty)
|
||||||
n (Types.to_string ty)
|
n (Types.to_string ty)
|
||||||
|
|
||||||
@ -9851,7 +9855,7 @@ let no_container_defconst loc n (ty : Types.t) =
|
|||||||
store. Refused here, where the message can name the way through, rather than
|
store. Refused here, where the message can name the way through, rather than
|
||||||
at the emitter as "this one is computed", which is true and says nothing.
|
at the emitter as "this one is computed", which is true and says nothing.
|
||||||
|
|
||||||
A defvar is no longer any of this and no longer asks. Its computed
|
A defonce is no longer any of this and no longer asks. Its computed
|
||||||
initialiser is lifted into a function that runs at startup, so the member is
|
initialiser is lifted into a function that runs at startup, so the member is
|
||||||
written by exactly the store that writes one anywhere else — the encoder was
|
written by exactly the store that writes one anywhere else — the encoder was
|
||||||
only ever needed because there was nothing to run.
|
only ever needed because there was nothing to run.
|
||||||
@ -9867,7 +9871,7 @@ let no_union_const env loc n (v : Tast.expr) =
|
|||||||
fail loc
|
fail loc
|
||||||
"the constant %s is the union %s, and a union member cannot be written \
|
"the constant %s is the union %s, and a union member cannot be written \
|
||||||
into a constant: a constant is what the linker writes into the image \
|
into a constant: a constant is what the linker writes into the image \
|
||||||
and storing a member is a store. Leave it zeroed, or make it a defvar \
|
and storing a member is a store. Leave it zeroed, or make it a defonce \
|
||||||
and let its initialiser run at startup"
|
and let its initialiser run at startup"
|
||||||
n un
|
n un
|
||||||
| _ -> ()
|
| _ -> ()
|
||||||
@ -9885,7 +9889,7 @@ let no_union_const env loc n (v : Tast.expr) =
|
|||||||
because a backend can only refuse the program it is asked to emit, and the
|
because a backend can only refuse the program it is asked to emit, and the
|
||||||
two were not asking the same question — [Emit.const] refused a computed
|
two were not asking the same question — [Emit.const] refused a computed
|
||||||
defconst by name while the x86 backend ran it through the startup function
|
defconst by name while the x86 backend ran it through the startup function
|
||||||
behind an [.init~once.] flag, like a defvar. One refusal in the checker is
|
behind an [.init~once.] flag, like a defonce. One refusal in the checker is
|
||||||
the same program refused the same way on both, and it is the only place
|
the same program refused the same way on both, and it is the only place
|
||||||
that can say what to do instead.
|
that can say what to do instead.
|
||||||
|
|
||||||
@ -9923,7 +9927,7 @@ let const_defconst_init env loc n (v : Tast.expr) =
|
|||||||
"a constant cannot be %s.%s — a data type's payload is a blob, and \
|
"a constant cannot be %s.%s — a data type's payload is a blob, and \
|
||||||
writing a case into one at link time needs a byte-level encoder that \
|
writing a case into one at link time needs a byte-level encoder that \
|
||||||
does not exist (a string field could not be encoded at all). Make it a \
|
does not exist (a string field could not be encoded at all). Make it a \
|
||||||
defvar, whose initialiser runs at startup and stores the case, or \
|
defonce, whose initialiser runs at startup and stores the case, or \
|
||||||
declare it zeroed, which is %s.%s"
|
declare it zeroed, which is %s.%s"
|
||||||
dname case dname
|
dname case dname
|
||||||
(match Hashtbl.find_opt env.datas dname with
|
(match Hashtbl.find_opt env.datas dname with
|
||||||
@ -9932,9 +9936,9 @@ let const_defconst_init env loc n (v : Tast.expr) =
|
|||||||
| Some _ ->
|
| Some _ ->
|
||||||
fail loc
|
fail loc
|
||||||
"a constant's value must be a compile-time constant — the constant %s \
|
"a constant's value must be a compile-time constant — the constant %s \
|
||||||
is computed. A defvar may have a computed initialiser, because it runs \
|
is computed. A defonce may have a computed initialiser, because it runs \
|
||||||
at startup and stores the result; a defconst is what the linker writes \
|
at startup and stores the result; a defconst is what the linker writes \
|
||||||
into the image and has nowhere to run. Write (defvar %s ...), or give \
|
into the image and has nowhere to run. Write (defonce %s ...), or give \
|
||||||
the constant a literal — integer constants may also be written as \
|
the constant a literal — integer constants may also be written as \
|
||||||
arithmetic over literals and other constants, which is folded here"
|
arithmetic over literals and other constants, which is folded here"
|
||||||
n n
|
n n
|
||||||
@ -9954,7 +9958,7 @@ let const_defconst_init env loc n (v : Tast.expr) =
|
|||||||
A [handler-bind] or a [restart-case] *inside* an initialiser is ordinary
|
A [handler-bind] or a [restart-case] *inside* an initialiser is ordinary
|
||||||
code: it pushes its frames, runs, and pops them, all before the initialiser
|
code: it pushes its frames, runs, and pops them, all before the initialiser
|
||||||
returns, and nothing it does is visible outside. Both backends run it
|
returns, and nothing it does is visible outside. Both backends run it
|
||||||
exactly as they run it in any other function — which is what makes (defvar
|
exactly as they run it in any other function — which is what makes (defonce
|
||||||
data (Vec u8) (slurp "level.edn")) an ordinary program, since [slurp] is a
|
data (Vec u8) (slurp "level.edn")) an ordinary program, since [slurp] is a
|
||||||
restart-case with its own signal inside it, and that is the shape the author
|
restart-case with its own signal inside it, and that is the shape the author
|
||||||
kept reaching for.
|
kept reaching for.
|
||||||
@ -10016,7 +10020,7 @@ let no_transfer_in_init n (v : Tast.expr) =
|
|||||||
A function rather than the expression left in place, for a reason that is
|
A function rather than the expression left in place, for a reason that is
|
||||||
not tidiness: an initialiser can contain a [let], and a [let] needs a frame.
|
not tidiness: an initialiser can contain a [let], and a [let] needs a frame.
|
||||||
The slots were allocated on a context this function discarded, so what the
|
The slots were allocated on a context this function discarded, so what the
|
||||||
backend got was a slot index into a frame of size zero — [(defvar c i64 (let
|
backend got was a slot index into a frame of size zero — [(defonce c i64 (let
|
||||||
[x (i64 5)] (+ x 1)))] crashed the x86 backend with an out-of-bounds index,
|
[x (i64 5)] (+ x 1)))] crashed the x86 backend with an out-of-bounds index,
|
||||||
and there was no frame to give it without inventing one. This is that frame,
|
and there was no frame to give it without inventing one. This is that frame,
|
||||||
and it is the one every other body already has.
|
and it is the one every other body already has.
|
||||||
@ -10055,12 +10059,25 @@ let lift_ginit ctx loc n ty (v : Tast.expr) =
|
|||||||
let check_global env (d : Ast.decl) : Tast.global option =
|
let check_global env (d : Ast.decl) : Tast.global option =
|
||||||
let ctx () = invented_ctx env Types.Unit in
|
let ctx () = invented_ctx env Types.Unit in
|
||||||
match d.Ast.d with
|
match d.Ast.d with
|
||||||
| Ast.Defvar (n, _, init) ->
|
| Ast.Defvar (n, _, init, kind) ->
|
||||||
let ty, _ = Hashtbl.find env.globals n in
|
let ty, _ = Hashtbl.find env.globals n in
|
||||||
no_zeroed_fn d.Ast.dloc (Printf.sprintf "the global %s" n) ty;
|
no_zeroed_fn d.Ast.dloc (Printf.sprintf "the global %s" n) ty;
|
||||||
container_global_init d.Ast.dloc n ty init;
|
container_global_init d.Ast.dloc n ty init;
|
||||||
|
(* A [def]'s initialiser is lifted into [global/<n>] whatever it is — a
|
||||||
|
zero, a literal, a computed expression — where a [defonce]'s is lifted
|
||||||
|
only when it is computed. The lifting is what makes the form's promise
|
||||||
|
reachable: the host's startup function calls the initialiser through
|
||||||
|
its function cell, so a re-evaluated [def] swaps the cell and the next
|
||||||
|
re-run stores the *edited* value. A constant left inline would be
|
||||||
|
baked into the host's startup body, and every re-run would paint the
|
||||||
|
stale value back. [uninit] is the one exception on both forms: there
|
||||||
|
is nothing to run, so there is nothing to lift. *)
|
||||||
|
let lift_always = (match kind with Ast.Once -> false | Ast.Every -> true) in
|
||||||
let ginit =
|
let ginit =
|
||||||
match init with
|
match init with
|
||||||
|
| Ast.Zeroed when lift_always ->
|
||||||
|
let c = ctx () in
|
||||||
|
lift_ginit c d.Ast.dloc n ty { Tast.e = Tast.Zero ty; ty; loc = d.Ast.dloc }
|
||||||
| Ast.Zeroed -> { Tast.e = Tast.Zero ty; ty; loc = d.Ast.dloc }
|
| Ast.Zeroed -> { Tast.e = Tast.Zero ty; ty; loc = d.Ast.dloc }
|
||||||
| Ast.Uninit ->
|
| Ast.Uninit ->
|
||||||
(* Everywhere else [uninit] is an opt-out from ZII and the bytes are
|
(* Everywhere else [uninit] is an opt-out from ZII and the bytes are
|
||||||
@ -10086,16 +10103,19 @@ let check_global env (d : Ast.decl) : Tast.global option =
|
|||||||
| Ast.Init v ->
|
| Ast.Init v ->
|
||||||
let c = ctx () in
|
let c = ctx () in
|
||||||
let v = check c ~want:ty v in
|
let v = check c ~want:ty v in
|
||||||
if Tast.const_init v then v else lift_ginit c d.Ast.dloc n ty v
|
if Tast.const_init v && not lift_always then v
|
||||||
|
else lift_ginit c d.Ast.dloc n ty v
|
||||||
(* [settle_defvars] turned every one of these into a [Zeroed] or an
|
(* [settle_defvars] turned every one of these into a [Zeroed] or an
|
||||||
[Init] during [collect], and this pass runs over the list that pass
|
[Init] during [collect], and this pass runs over the list that pass
|
||||||
handed back. One arriving here is a driver that checked a global
|
handed back. One arriving here is a driver that checked a global
|
||||||
without collecting first. *)
|
without collecting first. *)
|
||||||
| Ast.Ambiguous _ ->
|
| Ast.Ambiguous _ ->
|
||||||
fail d.Ast.dloc
|
fail d.Ast.dloc
|
||||||
"internal: the third element of (defvar %s ...) was never decided" n
|
"internal: the third element of (%s %s ...) was never decided"
|
||||||
|
(match kind with Ast.Once -> "defonce" | Ast.Every -> "def") n
|
||||||
in
|
in
|
||||||
Some { Tast.gname = n; gty = ty; ginit; gconst = false; gfolded = false }
|
Some { Tast.gname = n; gty = ty; ginit; gconst = false; gfolded = false;
|
||||||
|
grerun = (match kind with Ast.Once -> false | Ast.Every -> true) }
|
||||||
| Ast.Defconst (n, _, v) ->
|
| Ast.Defconst (n, _, v) ->
|
||||||
let ty, _ = Hashtbl.find env.globals n in
|
let ty, _ = Hashtbl.find env.globals n in
|
||||||
no_zeroed_fn d.Ast.dloc (Printf.sprintf "the global %s" n) ty;
|
no_zeroed_fn d.Ast.dloc (Printf.sprintf "the global %s" n) ty;
|
||||||
@ -10121,7 +10141,7 @@ let check_global env (d : Ast.decl) : Tast.global option =
|
|||||||
(* [env.consts] holds exactly the constants the folding pass consumed, so
|
(* [env.consts] holds exactly the constants the folding pass consumed, so
|
||||||
membership is the question "is this value in the program's shape?" *)
|
membership is the question "is this value in the program's shape?" *)
|
||||||
Some { Tast.gname = n; gty = ty; ginit; gconst = true;
|
Some { Tast.gname = n; gty = ty; ginit; gconst = true;
|
||||||
gfolded = Hashtbl.mem env.consts n }
|
gfolded = Hashtbl.mem env.consts n; grerun = false }
|
||||||
| _ -> None
|
| _ -> None
|
||||||
|
|
||||||
(* The entry point, plan.org: (defn main [args [string]] i32), with both the
|
(* The entry point, plan.org: (defn main [args [string]] i32), with both the
|
||||||
@ -10176,8 +10196,8 @@ let check_main env decls =
|
|||||||
(* ── The order the initialisers run in ─────────────────────────────── *)
|
(* ── The order the initialisers run in ─────────────────────────────── *)
|
||||||
|
|
||||||
(* Declaration order is the order a program's globals are started in, and it is
|
(* Declaration order is the order a program's globals are started in, and it is
|
||||||
the wrong one as soon as one of them is computed from another: [(defvar b
|
the wrong one as soon as one of them is computed from another: [(defonce b
|
||||||
i64 (+ a 10))] written above [(defvar a i64 (+ 1 2))] read a zero and
|
i64 (+ a 10))] written above [(defonce a i64 (+ 1 2))] read a zero and
|
||||||
answered 10 without saying anything. So the computed ones are sorted
|
answered 10 without saying anything. So the computed ones are sorted
|
||||||
by what they need, which is what Odin does (src/checker.cpp,
|
by what they need, which is what Odin does (src/checker.cpp,
|
||||||
[calculate_global_init_order]) and for the same reason — the alternative is
|
[calculate_global_init_order]) and for the same reason — the alternative is
|
||||||
@ -10193,7 +10213,7 @@ let check_main env decls =
|
|||||||
edge.
|
edge.
|
||||||
|
|
||||||
The dependency is transitive through calls, not just through what the
|
The dependency is transitive through calls, not just through what the
|
||||||
initialiser names: [(defvar a i64 (f))] where [f] reads [b] needs [b]
|
initialiser names: [(defonce a i64 (f))] where [f] reads [b] needs [b]
|
||||||
started first, and an analysis that only looked at the initialiser's own
|
started first, and an analysis that only looked at the initialiser's own
|
||||||
text would order that pair by luck. Odin's graph is transitive for the same
|
text would order that pair by luck. Odin's graph is transitive for the same
|
||||||
reason.
|
reason.
|
||||||
@ -10487,7 +10507,7 @@ let rec dyn_behind_pointer p seen (t : Types.t) =
|
|||||||
cap above is about. Only the by-value shapes contribute; the rest are
|
cap above is about. Only the by-value shapes contribute; the rest are
|
||||||
refused by [hidden_dyn] before this number matters. *)
|
refused by [hidden_dyn] before this number matters. *)
|
||||||
(* Saturated at one past the cap, because the number only ever has to be
|
(* Saturated at one past the cap, because the number only ever has to be
|
||||||
compared with it. That is not tidiness: [(defvar big [4611686018427387904
|
compared with it. That is not tidiness: [(defonce big [4611686018427387904
|
||||||
S])] is a length an [Int64.to_int] multiplication wraps *negative* on, so an
|
S])] is a length an [Int64.to_int] multiplication wraps *negative* on, so an
|
||||||
honest product made the test [n > desc_offsets_max] false, the declaration
|
honest product made the test [n > desc_offsets_max] false, the declaration
|
||||||
was accepted, and the emitter then sat building the offset list until
|
was accepted, and the emitter then sat building the offset list until
|
||||||
@ -11158,7 +11178,7 @@ let memory_sites ?file (p : Tast.program) : Loc.diag list =
|
|||||||
| _ -> ()
|
| _ -> ()
|
||||||
in
|
in
|
||||||
(* A global's initialiser runs at startup and allocates there as much as a
|
(* A global's initialiser runs at startup and allocates there as much as a
|
||||||
body does — [(defvar names (vec-new dyn))] is a heap object before main
|
body does — [(defonce names (vec-new dyn))] is a heap object before main
|
||||||
has a line of its own — so the globals are walked and not only the
|
has a line of its own — so the globals are walked and not only the
|
||||||
functions. *)
|
functions. *)
|
||||||
List.iter (fun (g : Tast.global) -> Tast.walk look g.Tast.ginit) p.Tast.globals;
|
List.iter (fun (g : Tast.global) -> Tast.walk look g.Tast.ginit) p.Tast.globals;
|
||||||
|
|||||||
11
lib/dev.ml
11
lib/dev.ml
@ -2,7 +2,7 @@
|
|||||||
beside it, and a socket an editor talks to.
|
beside it, and a socket an editor talks to.
|
||||||
|
|
||||||
This is the piece between an editor and everything else. What it adds over
|
This is the piece between an editor and everything else. What it adds over
|
||||||
[flan reload] is that the session *persists*: a [defvar] added by one
|
[flan reload] is that the session *persists*: a [defonce] added by one
|
||||||
evaluation is part of the program the next one is checked against, and the
|
evaluation is part of the program the next one is checked against, and the
|
||||||
set of names the running process was built with is the one from the build
|
set of names the running process was built with is the one from the build
|
||||||
this daemon actually made. A CLI that rebuilds its session from source each
|
this daemon actually made. A CLI that rebuilds its session from source each
|
||||||
@ -2621,7 +2621,7 @@ let reg_listing t ~verb ~note =
|
|||||||
|
|
||||||
Locals were the half the shadow stack was built for; these are arguably the
|
Locals were the half the shadow stack was built for; these are arguably the
|
||||||
more useful half in this language. A game keeps most of its state in
|
more useful half in this language. A game keeps most of its state in
|
||||||
top-level [defvar]s and sand.flan holds its entire grid that way, so "what
|
top-level [defonce]s and sand.flan holds its entire grid that way, so "what
|
||||||
is the program's state right now" is a question about globals and there was
|
is the program's state right now" is a question about globals and there was
|
||||||
nowhere to ask it.
|
nowhere to ask it.
|
||||||
|
|
||||||
@ -2979,9 +2979,10 @@ let abort t =
|
|||||||
Nothing here does the keeping, and that is deliberate: a re-run re-enters
|
Nothing here does the keeping, and that is deliberate: a re-run re-enters
|
||||||
[flan_program_main] from the top, the startup function that runs the
|
[flan_program_main] from the top, the startup function that runs the
|
||||||
computed initialisers included, and what each of those does the second time
|
computed initialisers included, and what each of those does the second time
|
||||||
is decided by the form that defined the global. A [defvar] is CL's, so its
|
is decided by the form that defined the global. A [defonce] is CL's defvar,
|
||||||
initialiser runs only if the variable is not already initialised —
|
so its initialiser runs only if the variable is not already initialised —
|
||||||
[Emit.startup_plan] emits the flag that makes that true, and FIX.org's entry
|
[Emit.startup_plan] emits the flag that makes that true — and a [def] is
|
||||||
|
CL's defparameter, whose initialiser runs on every re-run; FIX.org's entry
|
||||||
of 2026-09-20 is the rule. This op says what it sees rather than arranging
|
of 2026-09-20 is the rule. This op says what it sees rather than arranging
|
||||||
for it.
|
for it.
|
||||||
|
|
||||||
|
|||||||
93
lib/emit.ml
93
lib/emit.ml
@ -62,7 +62,7 @@ let sname n = "%" ^ quoted n
|
|||||||
have no cells and call the symbol directly. *)
|
have no cells and call the symbol directly. *)
|
||||||
let cellname n = "@" ^ quoted (Mangle.cell n)
|
let cellname n = "@" ^ quoted (Mangle.cell n)
|
||||||
|
|
||||||
(* A name the host was never built with — a defn or a defvar typed in after the
|
(* A name the host was never built with — a defn or a defonce typed in after the
|
||||||
process started — has no symbol to bind to, so it is keyed by string through
|
process started — has no symbol to bind to, so it is keyed by string through
|
||||||
[flan_dev_cell] / [flan_dev_global] and the answer is cached in one of these
|
[flan_dev_cell] / [flan_dev_global] and the answer is cached in one of these
|
||||||
module-local slots. One indirection more than a name the host has, which is
|
module-local slots. One indirection more than a name the host has, which is
|
||||||
@ -3478,7 +3478,7 @@ let emit_fn m ?(hidden = false) ?(pnames = []) (fn : Tast.fn) =
|
|||||||
no init-at-startup path, by design". There is one now, and it is a call:
|
no init-at-startup path, by design". There is one now, and it is a call:
|
||||||
[Check] lifts a computed initialiser into a function and [emit_startup]
|
[Check] lifts a computed initialiser into a function and [emit_startup]
|
||||||
below stores its result before [main] runs. So what reaches here is what
|
below stores its result before [main] runs. So what reaches here is what
|
||||||
needs no code — every [defvar] whose initialiser [Tast.const_init] accepts,
|
needs no code — every [defonce] whose initialiser [Tast.const_init] accepts,
|
||||||
and every [defconst], because a defconst's initialiser is one of those too:
|
and every [defconst], because a defconst's initialiser is one of those too:
|
||||||
the checker refuses a computed one outright (2026-09-20). A constant is what
|
the checker refuses a computed one outright (2026-09-20). A constant is what
|
||||||
the linker writes, and a value that has to be computed is not one. *)
|
the linker writes, and a value that has to be computed is not one. *)
|
||||||
@ -3500,7 +3500,7 @@ let rec const m (e : Tast.expr) =
|
|||||||
| _ -> "{ " ^ String.concat ", " inner ^ " }")
|
| _ -> "{ " ^ String.concat ", " inner ^ " }")
|
||||||
| Tast.Some_ v ->
|
| Tast.Some_ v ->
|
||||||
Printf.sprintf "{ i8 1, %s %s }" (ll v.Tast.ty) (const m v)
|
Printf.sprintf "{ i8 1, %s %s }" (ll v.Tast.ty) (const m v)
|
||||||
(* No program reaches this. A [defvar] whose initialiser is computed never
|
(* No program reaches this. A [defonce] whose initialiser is computed never
|
||||||
asks — it was lifted into a function and this one is only called for the
|
asks — it was lifted into a function and this one is only called for the
|
||||||
constant ones — and a computed [defconst] is refused by
|
constant ones — and a computed [defconst] is refused by
|
||||||
[Check.const_defconst_init], which is where the two messages that used to
|
[Check.const_defconst_init], which is where the two messages that used to
|
||||||
@ -3535,7 +3535,7 @@ let emit_global m ?(hidden = false) (g : Tast.global) =
|
|||||||
(* The initialiser decides, and the form no longer has to be asked: a
|
(* The initialiser decides, and the form no longer has to be asked: a
|
||||||
[defconst]'s initialiser is always one [const] can write, because the
|
[defconst]'s initialiser is always one [const] can write, because the
|
||||||
checker refuses a computed one. So a zeroinitializer here is always a
|
checker refuses a computed one. So a zeroinitializer here is always a
|
||||||
[defvar] waiting for the startup function. *)
|
[defonce] waiting for the startup function. *)
|
||||||
(if Tast.const_init g.Tast.ginit then const m g.Tast.ginit
|
(if Tast.const_init g.Tast.ginit then const m g.Tast.ginit
|
||||||
else "zeroinitializer"))
|
else "zeroinitializer"))
|
||||||
|
|
||||||
@ -3564,13 +3564,17 @@ let startup_sym = fname ".init-globals"
|
|||||||
calls [flan_program_main] from the top, so every line of [main] above runs a
|
calls [flan_program_main] from the top, so every line of [main] above runs a
|
||||||
second time, this call included.
|
second time, this call included.
|
||||||
|
|
||||||
What that must mean is decided by the defining form, not by the daemon. A
|
What that must mean is decided by the defining form, not by the daemon —
|
||||||
[defvar] is Common Lisp's [defvar]: its initialiser runs only if the
|
def, defonce and defconst decide what a re-run does. A [defonce] is Common
|
||||||
|
Lisp's [defvar] under Clojure's name: its initialiser runs only if the
|
||||||
variable is not already initialised, so its value survives a re-run — which
|
variable is not already initialised, so its value survives a re-run — which
|
||||||
is what the daemon has always promised ("the globals are as it left them")
|
is what the daemon has always promised ("the globals are as it left them")
|
||||||
and what a plain zeroed [defvar] already got for free, since .bss is
|
and what a plain zeroed [defonce] already got for free, since .bss is
|
||||||
untouched by a second call. A computed one used to be the exception, wiped
|
untouched by a second call. A computed one used to be the exception, wiped
|
||||||
back to its initial value every re-run. A [defconst] whose initialiser is a
|
back to its initial value every re-run. A [def] is Common Lisp's
|
||||||
|
[defparameter]: its initialiser runs on every re-run — no flag — so an
|
||||||
|
edited initialiser repaints the same storage on the next re-run, which is
|
||||||
|
the reason the form exists. A [defconst] whose initialiser is a
|
||||||
compile-time constant is not reached from here at all: it is the linker's
|
compile-time constant is not reached from here at all: it is the linker's
|
||||||
image on one backend and [.init-data]'s stores on the other, and a re-run
|
image on one backend and [.init-data]'s stores on the other, and a re-run
|
||||||
reaches neither.
|
reaches neither.
|
||||||
@ -3582,19 +3586,18 @@ let startup_sym = fname ".init-globals"
|
|||||||
The two therefore coincide for a constant and no [defconst] reaches the
|
The two therefore coincide for a constant and no [defconst] reaches the
|
||||||
plan below. Until then they did not, and the backends disagreed about that
|
plan below. Until then they did not, and the backends disagreed about that
|
||||||
one program: [const] refused a computed [defconst] by name while the x86
|
one program: [const] refused a computed [defconst] by name while the x86
|
||||||
backend guarded it here like any [defvar]. One refusal in the checker is
|
backend guarded it here like any [defonce]. One refusal in the checker is
|
||||||
what ended it.
|
what ended it.
|
||||||
|
|
||||||
So each computed initialiser guards itself with a flag of its own. Per
|
So each [defonce]'s computed initialiser guards itself with a flag of its
|
||||||
global and not per startup function, because the rule belongs to the form:
|
own, and a [def]'s takes none. Per global and not per startup function,
|
||||||
a global whose initialiser is added by a later build, or a future form that
|
because the rule belongs to the form: a [def] beside a [defonce] decides
|
||||||
*does* recompute, decides its own case without the other globals' having to
|
its own case without the other globals' having to agree.
|
||||||
agree.
|
|
||||||
|
|
||||||
Dev builds only. A release build has no re-run to guard against and pays
|
Dev builds only. A release build has no re-run to guard against and pays
|
||||||
nothing — the body below is then the bare store it always was, byte for
|
nothing — the body below is then the bare store it always was, byte for
|
||||||
byte. The flag is a global of its own rather than a sentinel value in the
|
byte. The flag is a global of its own rather than a sentinel value in the
|
||||||
variable, because there is no value a [defvar] cannot hold.
|
variable, because there is no value a [defonce] cannot hold.
|
||||||
|
|
||||||
Writing the flag *after* the store is what makes a failed initialiser retry
|
Writing the flag *after* the store is what makes a failed initialiser retry
|
||||||
rather than be skipped. [Check.no_transfer_in_init] refuses a [signal] or an
|
rather than be skipped. [Check.no_transfer_in_init] refuses a [signal] or an
|
||||||
@ -3611,7 +3614,7 @@ let startup_sym = fname ".init-globals"
|
|||||||
The flag's name is mangled with a [~], which the reader treats as a
|
The flag's name is mangled with a [~], which the reader treats as a
|
||||||
terminator and so cannot appear in any symbol a program can write — the same
|
terminator and so cannot appear in any symbol a program can write — the same
|
||||||
trick [destructure~N] uses. A [.]-separated name would not do: [.] is an
|
trick [destructure~N] uses. A [.]-separated name would not do: [.] is an
|
||||||
ordinary symbol constituent, so [(defvar .init-once.x ...)] beside a
|
ordinary symbol constituent, so [(defonce .init-once.x ...)] beside a
|
||||||
computed [x] used to emit the same symbol twice and the dev build died at
|
computed [x] used to emit the same symbol twice and the dev build died at
|
||||||
the assembler. *)
|
the assembler. *)
|
||||||
let init_flag n = ".init~once." ^ n
|
let init_flag n = ".init~once." ^ n
|
||||||
@ -3620,23 +3623,34 @@ let init_flag n = ".init~once." ^ n
|
|||||||
startup function — built here so that the two backends cannot disagree
|
startup function — built here so that the two backends cannot disagree
|
||||||
about any of the three. [flags] is empty in a release build. *)
|
about any of the three. [flags] is empty in a release build. *)
|
||||||
let startup_plan m (globals : Tast.global list) =
|
let startup_plan m (globals : Tast.global list) =
|
||||||
|
(* Which globals the startup function stores at all: every initialiser
|
||||||
|
that is not a constant the image already holds. A [def]'s is *never*
|
||||||
|
such a constant — [Check.check_global] lifts every one of them,
|
||||||
|
zero and literal included, into a [global/<n>] call — so every [def]
|
||||||
|
except an [uninit] one is in here, which is what makes its store run
|
||||||
|
on each re-run and its initialiser reachable through the function
|
||||||
|
cell a re-evaluation swaps. *)
|
||||||
let computed =
|
let computed =
|
||||||
List.filter
|
List.filter
|
||||||
(fun (g : Tast.global) -> not (Tast.const_init g.Tast.ginit))
|
(fun (g : Tast.global) -> not (Tast.const_init g.Tast.ginit))
|
||||||
globals
|
globals
|
||||||
in
|
in
|
||||||
let flags =
|
(* One guard flag per [defonce] with something to run, and none for a
|
||||||
if not m.dev then []
|
[def]: the flag is exactly what makes an initialiser run once, and a
|
||||||
else
|
[def]'s runs every time — that unguarded store is the whole difference
|
||||||
List.map
|
between the two forms. *)
|
||||||
(fun (g : Tast.global) ->
|
let flag_for (g : Tast.global) =
|
||||||
{ Tast.gname = init_flag g.Tast.gname; gty = Types.Bool;
|
if m.dev && not g.Tast.grerun then
|
||||||
ginit =
|
Some
|
||||||
{ Tast.e = Tast.Bool false; ty = Types.Bool;
|
{ Tast.gname = init_flag g.Tast.gname; gty = Types.Bool;
|
||||||
loc = g.Tast.ginit.Tast.loc };
|
ginit =
|
||||||
gconst = false; gfolded = false })
|
{ Tast.e = Tast.Bool false; ty = Types.Bool;
|
||||||
computed
|
loc = g.Tast.ginit.Tast.loc };
|
||||||
|
gconst = false; gfolded = false; grerun = false }
|
||||||
|
else None
|
||||||
in
|
in
|
||||||
|
let flagged = List.map (fun g -> (g, flag_for g)) computed in
|
||||||
|
let flags = List.filter_map snd flagged in
|
||||||
(* Registered so that [place] and the x86 backend's [lower] can find a
|
(* Registered so that [place] and the x86 backend's [lower] can find a
|
||||||
flag's type the same way they find any other global's. Not added to the
|
flag's type the same way they find any other global's. Not added to the
|
||||||
program's own [globals] list: nothing the programmer wrote names one, and
|
program's own [globals] list: nothing the programmer wrote names one, and
|
||||||
@ -3645,8 +3659,8 @@ let startup_plan m (globals : Tast.global list) =
|
|||||||
(fun (g : Tast.global) -> Hashtbl.replace m.globals g.Tast.gname g.Tast.gty)
|
(fun (g : Tast.global) -> Hashtbl.replace m.globals g.Tast.gname g.Tast.gty)
|
||||||
flags;
|
flags;
|
||||||
let body =
|
let body =
|
||||||
List.map2
|
List.map
|
||||||
(fun (g : Tast.global) (flag : Tast.global option) ->
|
(fun ((g : Tast.global), (flag : Tast.global option)) ->
|
||||||
let loc = g.Tast.ginit.Tast.loc in
|
let loc = g.Tast.ginit.Tast.loc in
|
||||||
let store =
|
let store =
|
||||||
{ Tast.e = Tast.Set (Tast.Pglobal g.Tast.gname, g.Tast.ginit);
|
{ Tast.e = Tast.Set (Tast.Pglobal g.Tast.gname, g.Tast.ginit);
|
||||||
@ -3668,9 +3682,7 @@ let startup_plan m (globals : Tast.global list) =
|
|||||||
{ Tast.e = Tast.Unit; ty = Types.Unit; loc },
|
{ Tast.e = Tast.Unit; ty = Types.Unit; loc },
|
||||||
{ Tast.e = Tast.Do [ store; mark ]; ty = Types.Unit; loc });
|
{ Tast.e = Tast.Do [ store; mark ]; ty = Types.Unit; loc });
|
||||||
ty = Types.Unit; loc })
|
ty = Types.Unit; loc })
|
||||||
computed
|
flagged
|
||||||
(if flags = [] then List.map (fun _ -> None) computed
|
|
||||||
else List.map (fun f -> Some f) flags)
|
|
||||||
in
|
in
|
||||||
(computed, flags, body)
|
(computed, flags, body)
|
||||||
|
|
||||||
@ -4339,7 +4351,7 @@ let program ?(checks = true) ?(dev = false) ?(debug = false) ?(pnames = [])
|
|||||||
it a dev-build feature at run time. A constructor rather than a line in
|
it a dev-build feature at run time. A constructor rather than a line in
|
||||||
[main]: the notes are emitted into every function, and a note that
|
[main]: the notes are emitted into every function, and a note that
|
||||||
arrived before the flag was set would be a block the table never heard
|
arrived before the flag was set would be a block the table never heard
|
||||||
of. That used to be an argument about a [defvar] initialiser allocating
|
of. That used to be an argument about a [defonce] initialiser allocating
|
||||||
before [main] ran, which it no longer does — [emit_startup] is called
|
before [main] ran, which it no longer does — [emit_startup] is called
|
||||||
from [main] now. What survives is the weaker and sufficient version:
|
from [main] now. What survives is the weaker and sufficient version:
|
||||||
arming has to precede the first allocation, a constructor is the only
|
arming has to precede the first allocation, a constructor is the only
|
||||||
@ -4502,6 +4514,19 @@ let redefinition ?(checks = true) ?(dev = false) ?(debug = false)
|
|||||||
Printf.sprintf "%s = internal global ptr null\n"
|
Printf.sprintf "%s = internal global ptr null\n"
|
||||||
(cellptr f.Tast.name)))
|
(cellptr f.Tast.name)))
|
||||||
siblings;
|
siblings;
|
||||||
|
(* A lifted initialiser handed in as a target — [global/<n>] when a [def]
|
||||||
|
is re-evaluated — is not a sibling: its [fparent] is the global it
|
||||||
|
initialises, not a function in [fns]. Its cell is the host's like any
|
||||||
|
other (the host declares one per function, lifted ones included), so
|
||||||
|
the publish store below needs the declaration the sibling loop above
|
||||||
|
could not write. *)
|
||||||
|
List.iter
|
||||||
|
(fun (f : Tast.fn) ->
|
||||||
|
if f.Tast.fparent <> None && known f.Tast.name
|
||||||
|
&& not (transient f.Tast.name) then
|
||||||
|
Buffer.add_string m.out
|
||||||
|
(Printf.sprintf "%s = external global ptr\n" (cellname f.Tast.name)))
|
||||||
|
targets;
|
||||||
if new_fns <> [] || new_globals <> [] then
|
if new_fns <> [] || new_globals <> [] then
|
||||||
Buffer.add_string m.out
|
Buffer.add_string m.out
|
||||||
"\ndeclare ptr @flan_dev_cell(ptr)\n\
|
"\ndeclare ptr @flan_dev_cell(ptr)\n\
|
||||||
@ -4550,7 +4575,7 @@ let redefinition ?(checks = true) ?(dev = false) ?(debug = false)
|
|||||||
would have to agree with LLVM's on every target. *)
|
would have to agree with LLVM's on every target. *)
|
||||||
(* Its declared initial value travels with it, as a constant the
|
(* Its declared initial value travels with it, as a constant the
|
||||||
runtime copies on the allocation and ignores afterwards. Without
|
runtime copies on the allocation and ignores afterwards. Without
|
||||||
this a new (defvar n i64 42) or a new defconst would silently be
|
this a new (defonce n i64 42) or a new defconst would silently be
|
||||||
zero — calloc is only the right answer for ZII.
|
zero — calloc is only the right answer for ZII.
|
||||||
|
|
||||||
A *computed* initialiser sends a null instead, and the allocation
|
A *computed* initialiser sends a null instead, and the allocation
|
||||||
|
|||||||
11
lib/load.ml
11
lib/load.ml
@ -448,11 +448,11 @@ let qualify_decl owned alias (d : Ast.decl) : Ast.decl =
|
|||||||
able to name the type. *)
|
able to name the type. *)
|
||||||
| Ast.Defunion (n, ms) ->
|
| Ast.Defunion (n, ms) ->
|
||||||
Ast.Defunion (qualify alias n, List.map (rename_field owned alias) ms)
|
Ast.Defunion (qualify alias n, List.map (rename_field owned alias) ms)
|
||||||
| Ast.Defvar (n, t, init) ->
|
| Ast.Defvar (n, t, init, k) ->
|
||||||
Ast.Defvar (qualify alias n, Option.map (rename_texpr owned alias) t,
|
Ast.Defvar (qualify alias n, Option.map (rename_texpr owned alias) t,
|
||||||
(match init with
|
(match init with
|
||||||
| Ast.Init v -> Ast.Init (rename_expr owned alias [] v)
|
| Ast.Init v -> Ast.Init (rename_expr owned alias [] v)
|
||||||
(* An undecided three-element defvar carries both readings
|
(* An undecided three-element defonce carries both readings
|
||||||
and neither has been picked yet, so both are renamed —
|
and neither has been picked yet, so both are renamed —
|
||||||
the type half by [rename_texpr] above, the value half
|
the type half by [rename_texpr] above, the value half
|
||||||
here. Renaming only one would make the import decide
|
here. Renaming only one would make the import decide
|
||||||
@ -460,7 +460,8 @@ let qualify_decl owned alias (d : Ast.decl) : Ast.decl =
|
|||||||
pass's. *)
|
pass's. *)
|
||||||
| Ast.Ambiguous v ->
|
| Ast.Ambiguous v ->
|
||||||
Ast.Ambiguous (rename_expr owned alias [] v)
|
Ast.Ambiguous (rename_expr owned alias [] v)
|
||||||
| other -> other))
|
| other -> other),
|
||||||
|
k)
|
||||||
| Ast.Defn fn ->
|
| Ast.Defn fn ->
|
||||||
let params = List.map (rename_field owned alias) fn.Ast.params in
|
let params = List.map (rename_field owned alias) fn.Ast.params in
|
||||||
let praw = Option.map (List.map (rename_pitem owned alias)) fn.Ast.praw in
|
let praw = Option.map (List.map (rename_pitem owned alias)) fn.Ast.praw in
|
||||||
@ -848,9 +849,9 @@ let decl_uses acc (d : Ast.decl) =
|
|||||||
| Ast.Declare (f, _) | Ast.DeclareC (f, _) ->
|
| Ast.Declare (f, _) | Ast.DeclareC (f, _) ->
|
||||||
List.iter field f.Ast.params;
|
List.iter field f.Ast.params;
|
||||||
Option.iter (texpr_uses acc) f.Ast.ret
|
Option.iter (texpr_uses acc) f.Ast.ret
|
||||||
| Ast.Defvar (_, t, init) ->
|
| Ast.Defvar (_, t, init, _) ->
|
||||||
Option.iter (texpr_uses acc) t;
|
Option.iter (texpr_uses acc) t;
|
||||||
(* Both readings again: an undecided defvar may turn out to be the one
|
(* Both readings again: an undecided defonce may turn out to be the one
|
||||||
whose initialiser calls the function, and a dependency this pass misses
|
whose initialiser calls the function, and a dependency this pass misses
|
||||||
is a declaration dropped from the module. *)
|
is a declaration dropped from the module. *)
|
||||||
(match init with
|
(match init with
|
||||||
|
|||||||
@ -140,7 +140,7 @@ let building = ref false
|
|||||||
calling a dropped one is as unbuildable as the dropped one itself.
|
calling a dropped one is as unbuildable as the dropped one itself.
|
||||||
|
|
||||||
Only [defn]s are dropped. A [defstruct], [defdata], [defalias], [defenum]
|
Only [defn]s are dropped. A [defstruct], [defdata], [defalias], [defenum]
|
||||||
or [defvar] stays whatever it names: the functions that survive still
|
or [defonce] stays whatever it names: the functions that survive still
|
||||||
mention those types, and a reduced prelude missing them would not check.
|
mention those types, and a reduced prelude missing them would not check.
|
||||||
There used to be a sharper reason — [Parse.prelude_types] memoised the
|
There used to be a sharper reason — [Parse.prelude_types] memoised the
|
||||||
prelude's type names for the parser's return-type guess, and a reduced
|
prelude's type names for the parser's return-type guess, and a reduced
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
let prefix = "flan."
|
let prefix = "flan."
|
||||||
|
|
||||||
(* A function or a global. One namespace, because the language has one: a
|
(* A function or a global. One namespace, because the language has one: a
|
||||||
[defn] and a [defvar] cannot share a name, so nothing here has to keep
|
[defn] and a [defonce] cannot share a name, so nothing here has to keep
|
||||||
them apart. The compiler's own names go through this too — [.init-globals]
|
them apart. The compiler's own names go through this too — [.init-globals]
|
||||||
and [.init-data] start with a dot no reader token can produce. *)
|
and [.init-data] start with a dot no reader token can produce. *)
|
||||||
let sym n = prefix ^ n
|
let sym n = prefix ^ n
|
||||||
|
|||||||
53
lib/parse.ml
53
lib/parse.ml
@ -669,9 +669,9 @@ and form f mk (head : Form.t) (args : Form.t list) : Ast.expr =
|
|||||||
a head, which is the same property that makes a quasiquoted macro call
|
a head, which is the same property that makes a quasiquoted macro call
|
||||||
output rather than a dependency. Building a declaration as a value is what
|
output rather than a dependency. Building a declaration as a value is what
|
||||||
a macro is for. *)
|
a macro is for. *)
|
||||||
| Sym ("defmacro" | "defn" | "defvar" | "defconst" | "defstruct" | "defdata"
|
| Sym ("defmacro" | "defn" | "def" | "defonce" | "defconst" | "defstruct"
|
||||||
| "defunion" | "defclass" | "defgeneric" | "defmulti" | "defmethod"
|
| "defdata" | "defunion" | "defclass" | "defgeneric" | "defmulti"
|
||||||
| "defenum" | "defalias" | "import" as name) ->
|
| "defmethod" | "defenum" | "defalias" | "import" as name) ->
|
||||||
fail f
|
fail f
|
||||||
"%s is a top-level declaration, not an expression. A quasiquoted one is \
|
"%s is a top-level declaration, not an expression. A quasiquoted one is \
|
||||||
a value and a macro may answer with it; an evaluated one is not a thing \
|
a value and a macro may answer with it; an evaluated one is not a thing \
|
||||||
@ -1180,18 +1180,20 @@ and pattern (f : Form.t) : Ast.pattern =
|
|||||||
Ast.Pctor (ctor, List.map sym binds)
|
Ast.Pctor (ctor, List.map sym binds)
|
||||||
| _ -> fail f "expected a pattern, found %s" (Form.to_string f)
|
| _ -> fail f "expected a pattern, found %s" (Form.to_string f)
|
||||||
|
|
||||||
(* ── The third element of a defvar ─────────────────────────────────────
|
(* ── The third element of a defonce or a def ───────────────────────────
|
||||||
[(defvar x i32)] declares a zeroed static and [(defvar score 0)] declares a
|
[(defonce x i32)] declares a zeroed static and [(defonce score 0)] declares
|
||||||
dyn global holding 0, and which one a form is is decided by whether the
|
a dyn global holding 0, and which one a form is is decided by whether the
|
||||||
third element is a type. The author's rule, 2026-09-20: "if it's 3 atoms
|
third element is a type. [def] takes exactly the same spellings — the two
|
||||||
then it's dyn", and "dispatch the if it's a type do the right thing".
|
forms differ only in what a re-run does. The author's rule, 2026-09-20:
|
||||||
|
"if it's 3 atoms then it's dyn", and "dispatch the if it's a type do the
|
||||||
|
right thing".
|
||||||
|
|
||||||
Most forms are settled by their shape alone and are settled here: [0], a
|
Most forms are settled by their shape alone and are settled here: [0], a
|
||||||
string, a map, [[1 2 3]] and [(f "x")] are not types by any reading, so the
|
string, a map, [[1 2 3]] and [(f "x")] are not types by any reading, so the
|
||||||
global is dyn and its initialiser is the expression; [[4 u32]], [()] and
|
global is dyn and its initialiser is the expression; [[4 u32]], [()] and
|
||||||
[(Fn [i32] i32)] are types by any reading and keep exactly the meaning they
|
[(Fn [i32] i32)] are types by any reading and keep exactly the meaning they
|
||||||
have today. Note which side the bracket falls on: [[n T]] stays a fixed
|
have today. Note which side the bracket falls on: [[n T]] stays a fixed
|
||||||
array, so [(defvar rows [4 u32])] is the zeroed grid it always was, and a
|
array, so [(defonce rows [4 u32])] is the zeroed grid it always was, and a
|
||||||
*vector literal* of two names is not reachable in this position.
|
*vector literal* of two names is not reachable in this position.
|
||||||
|
|
||||||
Two shapes are left over, and they are the ones a name decides rather than
|
Two shapes are left over, and they are the ones a name decides rather than
|
||||||
@ -1633,18 +1635,39 @@ let rec decl (f : Form.t) : Ast.decl =
|
|||||||
"defenum is (defenum Name [member value? ...]). A member with no \
|
"defenum is (defenum Name [member value? ...]). A member with no \
|
||||||
value takes the previous member's plus one, and the first takes 0")
|
value takes the previous member's plus one, and the first takes 0")
|
||||||
|
|
||||||
| List ({ v = Sym "defvar"; _ } :: args) ->
|
(* The two forms share one arm because they share everything but what a
|
||||||
|
daemon re-run does: [defonce] initialises once and keeps its value, [def]
|
||||||
|
runs its initialiser on every re-run. The author's rule (FIX.org,
|
||||||
|
2026-09-20): def, defonce and defconst decide what a re-run does, not the
|
||||||
|
daemon. *)
|
||||||
|
| List ({ v = Sym ("defonce" | "def" as form); _ } :: args) ->
|
||||||
|
let kind = if String.equal form "def" then Ast.Every else Ast.Once in
|
||||||
(match args with
|
(match args with
|
||||||
| [ n; t ] ->
|
| [ n; t ] ->
|
||||||
let ty, init = defvar3 t in
|
let ty, init = defvar3 t in
|
||||||
mk (Ast.Defvar (sym n, Some ty, init))
|
mk (Ast.Defvar (sym n, Some ty, init, kind))
|
||||||
| [ n; t; { v = Sym "uninit"; _ } ] ->
|
| [ n; t; { v = Sym "uninit"; _ } ] ->
|
||||||
mk (Ast.Defvar (sym n, Some (texpr t), Ast.Uninit))
|
mk (Ast.Defvar (sym n, Some (texpr t), Ast.Uninit, kind))
|
||||||
| [ n; t; v ] -> mk (Ast.Defvar (sym n, Some (texpr t), Ast.Init (expr v)))
|
| [ n; t; v ] ->
|
||||||
|
mk (Ast.Defvar (sym n, Some (texpr t), Ast.Init (expr v), kind))
|
||||||
| _ ->
|
| _ ->
|
||||||
fail f
|
fail f
|
||||||
"defvar is (defvar name Type value?) or (defvar name value) — a \
|
"%s is (%s name Type value?) or (%s name value) — a third element \
|
||||||
third element that is not a type is the value of a dyn global")
|
that is not a type is the value of a dyn global"
|
||||||
|
form form form)
|
||||||
|
|
||||||
|
(* The old name of [defonce], refused by name rather than left to fall
|
||||||
|
through to "unknown function": every program written before the rename
|
||||||
|
spells it, and the message is the migration. *)
|
||||||
|
| List ({ v = Sym "defvar"; _ } :: args) ->
|
||||||
|
let rest =
|
||||||
|
String.concat " " (List.map Form.to_string args)
|
||||||
|
in
|
||||||
|
Loc.failk "parse/defvar-renamed" f.loc
|
||||||
|
"defvar is now called defonce — the name says what it does: it \
|
||||||
|
initialises once and keeps its value across re-runs. Write (defonce \
|
||||||
|
%s), or (def %s) if the value should follow the source on every re-run"
|
||||||
|
rest rest
|
||||||
|
|
||||||
| List ({ v = Sym "defconst"; _ } :: args) ->
|
| List ({ v = Sym "defconst"; _ } :: args) ->
|
||||||
(match args with
|
(match args with
|
||||||
|
|||||||
@ -200,7 +200,7 @@ let source = {flan|
|
|||||||
;; regression test if the sequence is byte-identical on native and wasm32
|
;; regression test if the sequence is byte-identical on native and wasm32
|
||||||
;; (plan.org, RNG is ours). PCG-XSH-RR 32: one u64 LCG step per draw, folded
|
;; (plan.org, RNG is ours). PCG-XSH-RR 32: one u64 LCG step per draw, folded
|
||||||
;; down to 32 bits by an xorshift and rotated by the state's top five bits.
|
;; down to 32 bits by an xorshift and rotated by the state's top five bits.
|
||||||
(defvar rand-state u64 6364136223846793005)
|
(defonce rand-state u64 6364136223846793005)
|
||||||
|
|
||||||
(defn rand-seed [seed u64] ()
|
(defn rand-seed [seed u64] ()
|
||||||
(set rand-state (+ (* seed 6364136223846793005) 1442695040888963407)))
|
(set rand-state (+ (* seed 6364136223846793005) 1442695040888963407)))
|
||||||
@ -2073,7 +2073,7 @@ let source = {flan|
|
|||||||
;; per compiler process and every macro in a program shares it, so the counter
|
;; per compiler process and every macro in a program shares it, so the counter
|
||||||
;; is process-wide in practice; a second module would restart it, and the day
|
;; is process-wide in practice; a second module would restart it, and the day
|
||||||
;; there is one, the fix is to seed this from the module's index.
|
;; there is one, the fix is to seed this from the module's index.
|
||||||
(defvar gensym-n i64 0)
|
(defonce gensym-n i64 0)
|
||||||
|
|
||||||
(defn gensym [] Form
|
(defn gensym [] Form
|
||||||
(set gensym-n (+ gensym-n 1))
|
(set gensym-n (+ gensym-n 1))
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
Only [fns] and [externs] are pruned. Globals, structs and data types stay:
|
Only [fns] and [externs] are pruned. Globals, structs and data types stay:
|
||||||
a dropped function is a loud link error, a dropped global would be a
|
a dropped function is a loud link error, a dropped global would be a
|
||||||
silently different program, and an unreferenced global is bytes in BSS that
|
silently different program, and an unreferenced global is bytes in BSS that
|
||||||
cost nothing. A [defvar brush rl/Texture2D] in a headless build is exactly
|
cost nothing. A [defonce brush rl/Texture2D] in a headless build is exactly
|
||||||
that.
|
that.
|
||||||
|
|
||||||
Dev builds are not pruned at all. A REPL redefines a function that the
|
Dev builds are not pruned at all. A REPL redefines a function that the
|
||||||
|
|||||||
@ -290,7 +290,7 @@ let compatible ?(origin = fun _ -> None) ?(relaxed = []) ~loc
|
|||||||
with
|
with
|
||||||
(* A [defconst] is folded into its call sites — into an array length, at
|
(* A [defconst] is folded into its call sites — into an array length, at
|
||||||
worst, which is decided before any type resolves — so its value is in
|
worst, which is decided before any type resolves — so its value is in
|
||||||
the running program's code and not only in its storage. A [defvar]'s
|
the running program's code and not only in its storage. A [defonce]'s
|
||||||
initial value is the opposite case and must *not* be refused: the
|
initial value is the opposite case and must *not* be refused: the
|
||||||
storage holds live state the program has long since moved past, which
|
storage holds live state the program has long since moved past, which
|
||||||
is the whole of "edit the code, keep the sand". Same record, opposite
|
is the whole of "edit the code, keep the sand". Same record, opposite
|
||||||
@ -779,6 +779,30 @@ let eval ?(origin = "<eval>") ?pause t src : change =
|
|||||||
program.Tast.fns)
|
program.Tast.fns)
|
||||||
names
|
names
|
||||||
in
|
in
|
||||||
|
(* A re-evaluated [def] is a promise about the *next re-run*: its
|
||||||
|
initialiser runs every time, so the edited one has to be the one that
|
||||||
|
runs. The initialiser is the lifted [global/<n>] — [Check.check_global]
|
||||||
|
lifts every [def] initialiser, constants included, for exactly this —
|
||||||
|
and the host's startup function calls it through its cell, so
|
||||||
|
republishing that one function is the whole delivery. Only when the
|
||||||
|
host already has the cell: a [def] the process has never seen gets its
|
||||||
|
storage from [flan_dev_global] like any new global, and there is no
|
||||||
|
startup call to swap. *)
|
||||||
|
let def_inits =
|
||||||
|
List.concat_map
|
||||||
|
(fun (d : Ast.decl) ->
|
||||||
|
match d.Ast.d with
|
||||||
|
| Ast.Defvar (n, _, _, Ast.Every) ->
|
||||||
|
let g = "global/" ^ n in
|
||||||
|
if known t g
|
||||||
|
&& List.exists
|
||||||
|
(fun (f : Tast.fn) -> String.equal f.Tast.name g)
|
||||||
|
program.Tast.fns
|
||||||
|
then [ g ]
|
||||||
|
else []
|
||||||
|
| _ -> [])
|
||||||
|
incoming
|
||||||
|
in
|
||||||
let from_generics =
|
let from_generics =
|
||||||
List.concat_map
|
List.concat_map
|
||||||
(fun n ->
|
(fun n ->
|
||||||
@ -797,7 +821,7 @@ let eval ?(origin = "<eval>") ?pause t src : change =
|
|||||||
in
|
in
|
||||||
let fns =
|
let fns =
|
||||||
List.sort_uniq String.compare
|
List.sort_uniq String.compare
|
||||||
(declared_fns @ from_generics @ new_instances)
|
(declared_fns @ def_inits @ from_generics @ new_instances)
|
||||||
in
|
in
|
||||||
(* A constant that changed and can be published: known to the host, not
|
(* A constant that changed and can be published: known to the host, not
|
||||||
consumed by the checker. The module stores its new value at the frame
|
consumed by the checker. The module stores its new value at the frame
|
||||||
@ -1831,7 +1855,7 @@ let write_slot ?(origin = "<set>") t ~frame ~(fn : Tast.fn) ~slot ~path
|
|||||||
(* ── The globals a stopped stack reaches ───────────────────────────── *)
|
(* ── The globals a stopped stack reaches ───────────────────────────── *)
|
||||||
|
|
||||||
(* The other half of what a break loop can show, and in this language arguably
|
(* The other half of what a break loop can show, and in this language arguably
|
||||||
the more useful one: a game keeps most of its state in top-level [defvar]s,
|
the more useful one: a game keeps most of its state in top-level [defonce]s,
|
||||||
and sand.flan holds its entire grid that way.
|
and sand.flan holds its entire grid that way.
|
||||||
|
|
||||||
Almost the same thunk as [render_locals] with a different root, and the
|
Almost the same thunk as [render_locals] with a different root, and the
|
||||||
|
|||||||
@ -321,6 +321,10 @@ type global = {
|
|||||||
ginit : expr;
|
ginit : expr;
|
||||||
gconst : bool;
|
gconst : bool;
|
||||||
gfolded : bool;
|
gfolded : bool;
|
||||||
|
(* [def] rather than [defonce]: the initialiser runs on every daemon
|
||||||
|
re-run instead of once behind a flag, so an edited initialiser takes
|
||||||
|
effect on the next re-run. [Emit.startup_plan] is the consumer. *)
|
||||||
|
grerun : bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
(* A foreign function: no body, and [esym] is the symbol the linker sees. The
|
(* A foreign function: no body, and [esym] is the symbol the linker sees. The
|
||||||
|
|||||||
@ -51,7 +51,7 @@ type t =
|
|||||||
| Fn of t list * t (* (Fn [T ...] R) *)
|
| Fn of t list * t (* (Fn [T ...] R) *)
|
||||||
| Var of string (* a type variable — milestone 5 *)
|
| Var of string (* a type variable — milestone 5 *)
|
||||||
(* [dyn]: one machine word whose contents the runtime knows and this module
|
(* [dyn]: one machine word whose contents the runtime knows and this module
|
||||||
does not. It is a written type — [(defvar x dyn 5)] boxes the 5 — and it
|
does not. It is a written type — [(defonce x dyn 5)] boxes the 5 — and it
|
||||||
is also what an unannotated [defn] parameter means, which is why it is a
|
is also what an unannotated [defn] parameter means, which is why it is a
|
||||||
case here and not a Named type the prelude declares: the checker has to
|
case here and not a Named type the prelude declares: the checker has to
|
||||||
recognise it to choose the boxing and the dyn op lowering, and a name in a
|
recognise it to choose the boxing and the dyn op lowering, and a name in a
|
||||||
@ -103,7 +103,7 @@ let fkind_of_name = function
|
|||||||
is spelled [()] in source, and [Parse.texpr] refuses the word.
|
is spelled [()] in source, and [Parse.texpr] refuses the word.
|
||||||
|
|
||||||
[int] and [float] are on it for the same reason they are in the two
|
[int] and [float] are on it for the same reason they are in the two
|
||||||
functions above: the places that ask this list — whether a [defvar]'s third
|
functions above: the places that ask this list — whether a [defonce]'s third
|
||||||
element is a type, whether [(vec-new int)] names an element type, whether a
|
element is a type, whether [(vec-new int)] names an element type, whether a
|
||||||
[let] binding vector has an annotation wedged into it — must answer the
|
[let] binding vector has an annotation wedged into it — must answer the
|
||||||
same for [int] as for [i32], or the alias is a type-position-only spelling
|
same for [int] as for [i32], or the alias is a type-position-only spelling
|
||||||
|
|||||||
@ -24,7 +24,7 @@ facility (see plan.org, "Managed classes").
|
|||||||
|
|
||||||
- `[n T]` is a value. It lives wherever it is declared, copies on assignment and
|
- `[n T]` is a value. It lives wherever it is declared, copies on assignment and
|
||||||
on pass-by-value, and is what `defconst colors [4 u32] ...` and
|
on pass-by-value, and is what `defconst colors [4 u32] ...` and
|
||||||
`(defvar grid [rows [cols u32]] ...)` are.
|
`(defonce grid [rows [cols u32]] ...)` are.
|
||||||
- `[T]` is a **non-owning slice**: a borrowed window into a `[n T]`, a `(Vec T)`,
|
- `[T]` is a **non-owning slice**: a borrowed window into a `[n T]`, a `(Vec T)`,
|
||||||
or a literal in read-only memory. Copying a slice copies ptr+len, never the
|
or a literal in read-only memory. Copying a slice copies ptr+len, never the
|
||||||
elements. A slice may be `const`-qualified; freeing through one is not possible
|
elements. A slice may be `const`-qualified; freeing through one is not possible
|
||||||
@ -136,7 +136,7 @@ A container global **starts zeroed** — a zeroed `Vec` is an empty `Vec` — an
|
|||||||
loaded by whichever function loads it, with an ordinary assignment:
|
loaded by whichever function loads it, with an ordinary assignment:
|
||||||
|
|
||||||
```
|
```
|
||||||
(defvar the-data (Vec u8))
|
(defonce the-data (Vec u8))
|
||||||
(defn load [] () (set the-data (slurp "game-data.edn")))
|
(defn load [] () (set the-data (slurp "game-data.edn")))
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -217,7 +217,7 @@ provenance pass above may enforce it.
|
|||||||
A fixed set of assignable forms, not a `setf`-style extensible place mechanism:
|
A fixed set of assignable forms, not a `setf`-style extensible place mechanism:
|
||||||
|
|
||||||
```
|
```
|
||||||
(set x v) ; a local or a defvar
|
(set x v) ; a local or a defonce
|
||||||
(set (.field x) v) ; struct field; x may be a struct, (Ptr S) or (Handle S)
|
(set (.field x) v) ; struct field; x may be a struct, (Ptr S) or (Handle S)
|
||||||
(set (at a i ...) v) ; fixed array, slice, or Vec element
|
(set (at a i ...) v) ; fixed array, slice, or Vec element
|
||||||
(set (deref p) v) ; whole-object store through a pointer
|
(set (deref p) v) ; whole-object store through a pointer
|
||||||
|
|||||||
@ -45,7 +45,7 @@ let generic_src n =
|
|||||||
\ (set j (- j 1))))\n\
|
\ (set j (- j 1))))\n\
|
||||||
\ (set i (+ i 1)))))\n\n";
|
\ (set i (+ i 1)))))\n\n";
|
||||||
for i = 0 to n - 1 do
|
for i = 0 to n - 1 do
|
||||||
Buffer.add_string b (Printf.sprintf "(defvar xs-%s [8 %s])\n" tys.(i) tys.(i))
|
Buffer.add_string b (Printf.sprintf "(defonce xs-%s [8 %s])\n" tys.(i) tys.(i))
|
||||||
done;
|
done;
|
||||||
Buffer.add_string b "\n(defn main [] ()\n";
|
Buffer.add_string b "\n(defn main [] ()\n";
|
||||||
for i = 0 to n - 1 do
|
for i = 0 to n - 1 do
|
||||||
@ -76,7 +76,7 @@ let mono_src n =
|
|||||||
\ (set j (- j 1))))\n\
|
\ (set j (- j 1))))\n\
|
||||||
\ (set i (+ i 1)))))\n\n"
|
\ (set i (+ i 1)))))\n\n"
|
||||||
t t t t t t t);
|
t t t t t t t);
|
||||||
Buffer.add_string b (Printf.sprintf "(defvar xs-%s [8 %s])\n\n" t t)
|
Buffer.add_string b (Printf.sprintf "(defonce xs-%s [8 %s])\n\n" t t)
|
||||||
done;
|
done;
|
||||||
Buffer.add_string b "(defn main [] ()\n";
|
Buffer.add_string b "(defn main [] ()\n";
|
||||||
for i = 0 to n - 1 do
|
for i = 0 to n - 1 do
|
||||||
|
|||||||
@ -29,8 +29,8 @@
|
|||||||
(set i (+ i 1))
|
(set i (+ i 1))
|
||||||
(set j (- j 1)))))
|
(set j (- j 1)))))
|
||||||
|
|
||||||
(defvar ns [5 i32])
|
(defonce ns [5 i32])
|
||||||
(defvar fs [5 f32])
|
(defonce fs [5 f32])
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
(let [xs (slice ns 0 5)
|
(let [xs (slice ns 0 5)
|
||||||
|
|||||||
@ -10,34 +10,34 @@
|
|||||||
;;;; arith.flan gives: a literal operand is exactly what a constant folder
|
;;;; arith.flan gives: a literal operand is exactly what a constant folder
|
||||||
;;;; removes, and a folded program does not contain the code being tested.
|
;;;; removes, and a folded program does not contain the code being tested.
|
||||||
|
|
||||||
(defvar i8hi i8 127)
|
(defonce i8hi i8 127)
|
||||||
(defvar i16hi i16 32767)
|
(defonce i16hi i16 32767)
|
||||||
(defvar i32hi i32 2147483647)
|
(defonce i32hi i32 2147483647)
|
||||||
(defvar u8hi u8 255)
|
(defonce u8hi u8 255)
|
||||||
(defvar u16hi u16 65535)
|
(defonce u16hi u16 65535)
|
||||||
(defvar u32hi u32 4294967295)
|
(defonce u32hi u32 4294967295)
|
||||||
(defvar i64hi i64 9223372036854775807)
|
(defonce i64hi i64 9223372036854775807)
|
||||||
(defvar u64zero u64 0)
|
(defonce u64zero u64 0)
|
||||||
|
|
||||||
(defvar one8 i8 1)
|
(defonce one8 i8 1)
|
||||||
(defvar one16 i16 1)
|
(defonce one16 i16 1)
|
||||||
(defvar one32 i32 1)
|
(defonce one32 i32 1)
|
||||||
(defvar oneu8 u8 1)
|
(defonce oneu8 u8 1)
|
||||||
(defvar oneu16 u16 1)
|
(defonce oneu16 u16 1)
|
||||||
(defvar oneu32 u32 1)
|
(defonce oneu32 u32 1)
|
||||||
(defvar one64 i64 1)
|
(defonce one64 i64 1)
|
||||||
(defvar oneu64 u64 1)
|
(defonce oneu64 u64 1)
|
||||||
|
|
||||||
(defvar big32 i32 123456789)
|
(defonce big32 i32 123456789)
|
||||||
(defvar big64 i64 1234567890123)
|
(defonce big64 i64 1234567890123)
|
||||||
(defvar three i32 3)
|
(defonce three i32 3)
|
||||||
(defvar three64 i64 3)
|
(defonce three64 i64 3)
|
||||||
(defvar seven i32 7)
|
(defonce seven i32 7)
|
||||||
(defvar negsev i32 -7)
|
(defonce negsev i32 -7)
|
||||||
(defvar shift i32 33)
|
(defonce shift i32 33)
|
||||||
(defvar shift8 i8 9)
|
(defonce shift8 i8 9)
|
||||||
(defvar f32one f32 1.0)
|
(defonce f32one f32 1.0)
|
||||||
(defvar f32big f32 16777217.0)
|
(defonce f32big f32 16777217.0)
|
||||||
|
|
||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
;; ── Wrapping at every width ──────────────────────────────────────
|
;; ── Wrapping at every width ──────────────────────────────────────
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
(defstruct Point [x i32 y i32])
|
(defstruct Point [x i32 y i32])
|
||||||
(defstruct Box [lo Point hi Point n i32])
|
(defstruct Box [lo Point hi Point n i32])
|
||||||
|
|
||||||
(defvar gp Point)
|
(defonce gp Point)
|
||||||
|
|
||||||
;; A parameter is a copy: writing to it must not reach the caller's value.
|
;; A parameter is a copy: writing to it must not reach the caller's value.
|
||||||
(defn bump [p Point] Point
|
(defn bump [p Point] Point
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
;;;; the LLVM side says what the check costs when a compiler is allowed to
|
;;;; the LLVM side says what the check costs when a compiler is allowed to
|
||||||
;;;; hoist it out of the loop.
|
;;;; hoist it out of the loop.
|
||||||
|
|
||||||
(defvar xs [1024 i32])
|
(defonce xs [1024 i32])
|
||||||
|
|
||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
(dotimes [i 1024]
|
(dotimes [i 1024]
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
(defstruct Blip [n i32])
|
(defstruct Blip [n i32])
|
||||||
|
|
||||||
(defvar log i64)
|
(defonce log i64)
|
||||||
|
|
||||||
(defn deep [n i32] i32
|
(defn deep [n i32] i32
|
||||||
(signal (Blip {.n n}))
|
(signal (Blip {.n n}))
|
||||||
|
|||||||
@ -14,32 +14,32 @@
|
|||||||
;;;; shift that can reach either backend out of range is a computed one, and a
|
;;;; shift that can reach either backend out of range is a computed one, and a
|
||||||
;;;; literal would be folded away before the code under test ran anyway.
|
;;;; literal would be folded away before the code under test ran anyway.
|
||||||
|
|
||||||
(defvar i8min i8 -128)
|
(defonce i8min i8 -128)
|
||||||
(defvar i16min i16 -32768)
|
(defonce i16min i16 -32768)
|
||||||
(defvar i32min i32 -2147483648)
|
(defonce i32min i32 -2147483648)
|
||||||
(defvar i64min i64 -9223372036854775808)
|
(defonce i64min i64 -9223372036854775808)
|
||||||
(defvar u8hi u8 255)
|
(defonce u8hi u8 255)
|
||||||
(defvar u16hi u16 65535)
|
(defonce u16hi u16 65535)
|
||||||
(defvar u32hi u32 4294967295)
|
(defonce u32hi u32 4294967295)
|
||||||
(defvar one8 i8 1)
|
(defonce one8 i8 1)
|
||||||
(defvar one16 i16 1)
|
(defonce one16 i16 1)
|
||||||
(defvar one32 i32 1)
|
(defonce one32 i32 1)
|
||||||
(defvar oneu8 u8 1)
|
(defonce oneu8 u8 1)
|
||||||
(defvar oneu16 u16 1)
|
(defonce oneu16 u16 1)
|
||||||
(defvar oneu32 u32 1)
|
(defonce oneu32 u32 1)
|
||||||
(defvar one64 i64 1)
|
(defonce one64 i64 1)
|
||||||
|
|
||||||
(defvar c8 i8 8)
|
(defonce c8 i8 8)
|
||||||
(defvar c9 i8 9)
|
(defonce c9 i8 9)
|
||||||
(defvar cu8 u8 8)
|
(defonce cu8 u8 8)
|
||||||
(defvar c16 i16 16)
|
(defonce c16 i16 16)
|
||||||
(defvar cu16 u16 17)
|
(defonce cu16 u16 17)
|
||||||
(defvar c32 i32 32)
|
(defonce c32 i32 32)
|
||||||
(defvar c33 i32 33)
|
(defonce c33 i32 33)
|
||||||
(defvar cu32 u32 32)
|
(defonce cu32 u32 32)
|
||||||
(defvar cu33 u32 33)
|
(defonce cu33 u32 33)
|
||||||
(defvar c64 i64 64)
|
(defonce c64 i64 64)
|
||||||
(defvar c65 i64 65)
|
(defonce c65 i64 65)
|
||||||
|
|
||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
;; ── The count exactly at the width: masks to 0, so nothing moves ──
|
;; ── The count exactly at the width: masks to 0, so nothing moves ──
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
;;;; condition corpus is written in.
|
;;;; condition corpus is written in.
|
||||||
(defstruct Oops [id i32])
|
(defstruct Oops [id i32])
|
||||||
|
|
||||||
(defvar trace i64)
|
(defonce trace i64)
|
||||||
|
|
||||||
;;; The shape that had no answer. The handler-bind is this function's last
|
;;; The shape that had no answer. The handler-bind is this function's last
|
||||||
;;; form, so what it yields is what the function returns, two frames above the
|
;;; form, so what it yields is what the function returns, two frames above the
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
;; continue, the bitwise family, unsigned arithmetic and shifts, and the
|
;; continue, the bitwise family, unsigned arithmetic and shifts, and the
|
||||||
;; conversions in both directions.
|
;; conversions in both directions.
|
||||||
|
|
||||||
(defvar counter i64 0)
|
(defonce counter i64 0)
|
||||||
|
|
||||||
(defconst limit i32 6)
|
(defconst limit i32 6)
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
(defstruct Blip [n i32])
|
(defstruct Blip [n i32])
|
||||||
|
|
||||||
(defvar log i64)
|
(defonce log i64)
|
||||||
|
|
||||||
;;; Two frames down, with a defer between, so the transfer crosses a function
|
;;; Two frames down, with a defer between, so the transfer crosses a function
|
||||||
;;; boundary and a transfer exit that has work to do.
|
;;; boundary and a transfer exit that has work to do.
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
;;;; Both cases go through a restart-case, so what is compared is the message
|
;;;; Both cases go through a restart-case, so what is compared is the message
|
||||||
;;;; on stderr as well as the fact that something was signalled.
|
;;;; on stderr as well as the fact that something was signalled.
|
||||||
|
|
||||||
(defvar a [4 i32])
|
(defonce a [4 i32])
|
||||||
|
|
||||||
(defn promised [n i32] i32
|
(defn promised [n i32] i32
|
||||||
;; n is a parameter, so the checker has no literal to look at.
|
;; n is a parameter, so the checker has no literal to look at.
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
(defvar log i64)
|
(defonce log i64)
|
||||||
|
|
||||||
(defn quiet [n i32] i32
|
(defn quiet [n i32] i32
|
||||||
(defer (set log (+ log 1)))
|
(defer (set log (+ log 1)))
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
;;;; case that has to keep working.
|
;;;; case that has to keep working.
|
||||||
(import agent "vendor:agent")
|
(import agent "vendor:agent")
|
||||||
|
|
||||||
(defvar ticks i64)
|
(defonce ticks i64)
|
||||||
|
|
||||||
(defn tick [] i64
|
(defn tick [] i64
|
||||||
(set ticks (+ ticks 1))
|
(set ticks (+ ticks 1))
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
;;;; constructor either. See flan_agent.c's [auto_start].
|
;;;; constructor either. See flan_agent.c's [auto_start].
|
||||||
(import agent "vendor:agent")
|
(import agent "vendor:agent")
|
||||||
|
|
||||||
(defvar ticks i64)
|
(defonce ticks i64)
|
||||||
|
|
||||||
(defn tick [] i64
|
(defn tick [] i64
|
||||||
(set ticks (+ ticks 1))
|
(set ticks (+ ticks 1))
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
;;; nothing for a shim to do.
|
;;; nothing for a shim to do.
|
||||||
(declare stdin-byte [] i32 "getchar")
|
(declare stdin-byte [] i32 "getchar")
|
||||||
|
|
||||||
(defvar ticks i64)
|
(defonce ticks i64)
|
||||||
|
|
||||||
(defn tick [] i64
|
(defn tick [] i64
|
||||||
(set ticks (+ ticks 1))
|
(set ticks (+ ticks 1))
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
;;;; vendor/agent/agent.flan.
|
;;;; vendor/agent/agent.flan.
|
||||||
(import agent "vendor:agent")
|
(import agent "vendor:agent")
|
||||||
|
|
||||||
(defvar ticks i64)
|
(defonce ticks i64)
|
||||||
|
|
||||||
(defn tick [] i64
|
(defn tick [] i64
|
||||||
(set ticks (+ ticks 1))
|
(set ticks (+ ticks 1))
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
;; A zeroed Allocator. A global rather than a local because the arena has to
|
;; A zeroed Allocator. A global rather than a local because the arena has to
|
||||||
;; outlive the frame that makes it, and because a handler cannot see a local
|
;; outlive the frame that makes it, and because a handler cannot see a local
|
||||||
;; (check.ml's `captured` says so by name).
|
;; (check.ml's `captured` says so by name).
|
||||||
(defvar frame Allocator)
|
(defonce frame Allocator)
|
||||||
|
|
||||||
;;; The context is a dynamic variable, so a function called from inside a
|
;;; The context is a dynamic variable, so a function called from inside a
|
||||||
;;; with-allocator body sees the rebinding without anything being passed.
|
;;; with-allocator body sees the rebinding without anything being passed.
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
;;;; two ways this dies. Each death is the whole test of its case, so they are
|
;;;; two ways this dies. Each death is the whole test of its case, so they are
|
||||||
;;;; separate runs rather than one program that could pass by dying early.
|
;;;; separate runs rather than one program that could pass by dying early.
|
||||||
|
|
||||||
(defvar frame Allocator)
|
(defonce frame Allocator)
|
||||||
|
|
||||||
(defalias Row (Vec i32))
|
(defalias Row (Vec i32))
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
;;;; There is no drop, no destructor, no finalizer and no per-element teardown
|
;;;; 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.
|
;;;; anywhere below. The release at the bottom of main is one call.
|
||||||
|
|
||||||
(defvar frame Allocator)
|
(defonce frame Allocator)
|
||||||
|
|
||||||
(defdata Value
|
(defdata Value
|
||||||
[(Nil [])
|
[(Nil [])
|
||||||
|
|||||||
@ -44,22 +44,22 @@
|
|||||||
;;;; is allowed to elide — a literal divisor that is neither 0 nor -1, and
|
;;;; is allowed to elide — a literal divisor that is neither 0 nor -1, and
|
||||||
;;;; unsigned division, which has no overflow case at all.
|
;;;; unsigned division, which has no overflow case at all.
|
||||||
|
|
||||||
(defvar frames i64)
|
(defonce frames i64)
|
||||||
(defvar skipped i64)
|
(defonce skipped i64)
|
||||||
(defvar cleaned i64)
|
(defonce cleaned i64)
|
||||||
(defvar op i32)
|
(defonce op i32)
|
||||||
(defvar lhs i64)
|
(defonce lhs i64)
|
||||||
(defvar rhs i64)
|
(defonce rhs i64)
|
||||||
|
|
||||||
;;; Globals rather than locals because a handler cannot see the locals of the
|
;;; Globals rather than locals because a handler cannot see the locals of the
|
||||||
;;; function that established it — check.ml refuses a capture by name and says
|
;;; function that established it — check.ml refuses a capture by name and says
|
||||||
;;; to use a global.
|
;;; to use a global.
|
||||||
(defvar zero i64)
|
(defonce zero i64)
|
||||||
(defvar neg1 i64 -1)
|
(defonce neg1 i64 -1)
|
||||||
(defvar big i64 9223372036854775807)
|
(defonce big i64 9223372036854775807)
|
||||||
(defvar huge f64 1e300)
|
(defonce huge f64 1e300)
|
||||||
(defvar small f64 -1e300)
|
(defonce small f64 -1e300)
|
||||||
(defvar uz u32)
|
(defonce uz u32)
|
||||||
|
|
||||||
(defn show [name string n i64] ()
|
(defn show [name string n i64] ()
|
||||||
(print name) (print " ") (print n) (println ""))
|
(print name) (print " ") (print n) (println ""))
|
||||||
|
|||||||
@ -14,21 +14,21 @@
|
|||||||
;;;; allowed to elide, and folding these away would leave the test asserting on
|
;;;; allowed to elide, and folding these away would leave the test asserting on
|
||||||
;;;; a program that does not contain the check.
|
;;;; a program that does not contain the check.
|
||||||
|
|
||||||
(defvar zero i64)
|
(defonce zero i64)
|
||||||
(defvar neg1 i64 -1)
|
(defonce neg1 i64 -1)
|
||||||
(defvar big i64 9223372036854775807)
|
(defonce big i64 9223372036854775807)
|
||||||
(defvar ten i64 10)
|
(defonce ten i64 10)
|
||||||
(defvar uz u32)
|
(defonce uz u32)
|
||||||
(defvar huge f64 1e300)
|
(defonce huge f64 1e300)
|
||||||
;; The narrow versions of the same two failures. They are here because they are
|
;; The narrow versions of the same two failures. They are here because they are
|
||||||
;; the ones the two backends reach by different routes: the overflow test
|
;; the ones the two backends reach by different routes: the overflow test
|
||||||
;; compares against the *narrow* type's most negative value inside a 64-bit
|
;; compares against the *narrow* type's most negative value inside a 64-bit
|
||||||
;; register, and the f32 range test is compared in f32 on one backend and in a
|
;; register, and the f32 range test is compared in f32 on one backend and in a
|
||||||
;; double on the other. Both routes are supposed to give the same answer and
|
;; double on the other. Both routes are supposed to give the same answer and
|
||||||
;; the survey is what says so.
|
;; the survey is what says so.
|
||||||
(defvar i32big i32 2147483647)
|
(defonce i32big i32 2147483647)
|
||||||
(defvar m1-32 i32 -1)
|
(defonce m1-32 i32 -1)
|
||||||
(defvar wide f32 1e30)
|
(defonce wide f32 1e30)
|
||||||
|
|
||||||
(defn main [args [string]] i32
|
(defn main [args [string]] i32
|
||||||
(let [n (i32 (bytes->i64 (bytes-view (at args 1))))
|
(let [n (i32 (bytes->i64 (bytes-view (at args 1))))
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
(defstruct V2 [x f32 y f32])
|
(defstruct V2 [x f32 y f32])
|
||||||
|
|
||||||
(defconst n 3)
|
(defconst n 3)
|
||||||
(defvar points [3 V2])
|
(defonce points [3 V2])
|
||||||
|
|
||||||
(defn sumx [ps [3 V2]] i32
|
(defn sumx [ps [3 V2]] i32
|
||||||
(let [t (f32 0.0)]
|
(let [t (f32 0.0)]
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
;;;; DISCUSS.org's "need a value-producing array constructor": (array n T) is
|
;;;; DISCUSS.org's "need a value-producing array constructor": (array n T) is
|
||||||
;;;; the zeroed array and dotimes is Unit, so neither could be the initialiser
|
;;;; the zeroed array and dotimes is Unit, so neither could be the initialiser
|
||||||
;;;; expression of a declaration. These are expressions, so they compose where
|
;;;; expression of a declaration. These are expressions, so they compose where
|
||||||
;;;; a bracket literal does — including as a defvar's initialiser, which is the
|
;;;; a bracket literal does — including as a defonce's initialiser, which is the
|
||||||
;;;; line the note was written about.
|
;;;; line the note was written about.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; The dimensions are in brackets and are the same compile-time lengths the
|
;;;; The dimensions are in brackets and are the same compile-time lengths the
|
||||||
@ -13,8 +13,8 @@
|
|||||||
(defconst cols 4)
|
(defconst cols 4)
|
||||||
|
|
||||||
;; The line from the note. A typed declaration with a computed initialiser,
|
;; The line from the note. A typed declaration with a computed initialiser,
|
||||||
;; which is the startup-lifted path a defvar already had.
|
;; which is the startup-lifted path a defonce already had.
|
||||||
(defvar grid [rows [cols u8]] (array-fill [rows cols] 255))
|
(defonce grid [rows [cols u8]] (array-fill [rows cols] 255))
|
||||||
|
|
||||||
;; One index per dimension, i32 each, and the return type is the element type.
|
;; One index per dimension, i32 each, and the return type is the element type.
|
||||||
(defn cell [r i32 c i32] i32 (+ (* r 100) c))
|
(defn cell [r i32 c i32] i32 (+ (* r 100) c))
|
||||||
@ -24,7 +24,7 @@
|
|||||||
;; Row-major order is pinned, so a generator that counts observes it: this one
|
;; Row-major order is pinned, so a generator that counts observes it: this one
|
||||||
;; is called once per element and answers the call number, so the array it
|
;; is called once per element and answers the call number, so the array it
|
||||||
;; fills is 0 1 2 ... in the order the elements are written.
|
;; fills is 0 1 2 ... in the order the elements are written.
|
||||||
(defvar ticks i32)
|
(defonce ticks i32)
|
||||||
|
|
||||||
(defn tick [r i32 c i32] i32
|
(defn tick [r i32 c i32] i32
|
||||||
(set ticks (+ ticks 1))
|
(set ticks (+ ticks 1))
|
||||||
@ -34,7 +34,7 @@
|
|||||||
(defstruct Cell [row i32 col i32])
|
(defstruct Cell [row i32 col i32])
|
||||||
|
|
||||||
;; Counts its own calls, for the evaluated-once line below.
|
;; Counts its own calls, for the evaluated-once line below.
|
||||||
(defvar calls i32)
|
(defonce calls i32)
|
||||||
|
|
||||||
(defn bump [] i32
|
(defn bump [] i32
|
||||||
(set calls (+ calls 1))
|
(set calls (+ calls 1))
|
||||||
@ -75,7 +75,7 @@
|
|||||||
(print (at t 0 0)) (print " ") (print (at t 0 1)) (print " ")
|
(print (at t 0 0)) (print " ") (print (at t 0 1)) (print " ")
|
||||||
(print (at t 1 0)) (print " ") (print (at t 2 3)) (println "")) ; 0 1 4 11
|
(print (at t 1 0)) (print " ") (print (at t 2 3)) (println "")) ; 0 1 4 11
|
||||||
|
|
||||||
;; The defvar from the top: 255 everywhere, read back as an i32 so the
|
;; The defonce from the top: 255 everywhere, read back as an i32 so the
|
||||||
;; printed value is the number and not a byte.
|
;; printed value is the number and not a byte.
|
||||||
(print (i32 (at grid 0 0))) (print " ")
|
(print (i32 (at grid 0 0))) (print " ")
|
||||||
(print (i32 (at grid 2 3))) (println "") ; 255 255
|
(print (i32 (at grid 2 3))) (println "") ; 255 255
|
||||||
|
|||||||
@ -44,17 +44,17 @@
|
|||||||
;;;; runtime rather than in emitted IR, so those two are plumbed
|
;;;; runtime rather than in emitted IR, so those two are plumbed
|
||||||
;;;; separately and are the ones most likely to be left behind.
|
;;;; separately and are the ones most likely to be left behind.
|
||||||
|
|
||||||
(defvar grid [4 i32])
|
(defonce grid [4 i32])
|
||||||
|
|
||||||
;;; Handlers cannot see the locals of the function that established them —
|
;;; Handlers cannot see the locals of the function that established them —
|
||||||
;;; check.ml refuses a capture by name and says to use a global — so
|
;;; check.ml refuses a capture by name and says to use a global — so
|
||||||
;;; everything this program counts lives up here.
|
;;; everything this program counts lives up here.
|
||||||
(defvar frames i64)
|
(defonce frames i64)
|
||||||
(defvar skipped i64)
|
(defonce skipped i64)
|
||||||
(defvar cleaned i64)
|
(defonce cleaned i64)
|
||||||
(defvar low i64)
|
(defonce low i64)
|
||||||
(defvar high i64)
|
(defonce high i64)
|
||||||
(defvar length i64)
|
(defonce length i64)
|
||||||
|
|
||||||
;;; Two frames deep, with a defer on the way, so the transfer has something to
|
;;; Two frames deep, with a defer on the way, so the transfer has something to
|
||||||
;;; cross and something to run on its way out.
|
;;; cross and something to run on its way out.
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
;;;; The selector is also the index wherever it can be, which is what keeps the
|
;;;; The selector is also the index wherever it can be, which is what keeps the
|
||||||
;;;; index dynamic — a literal would let the checker reject it outright one day
|
;;;; index dynamic — a literal would let the checker reject it outright one day
|
||||||
;;;; (that is a separate job) and lets LLVM fold the branch away here.
|
;;;; (that is a separate job) and lets LLVM fold the branch away here.
|
||||||
(defvar arr [3 i32])
|
(defonce arr [3 i32])
|
||||||
|
|
||||||
(defn main [args [string]] i32
|
(defn main [args [string]] i32
|
||||||
(let [n (i32 (bytes->i64 (bytes-view (at args 1))))
|
(let [n (i32 (bytes->i64 (bytes-view (at args 1))))
|
||||||
|
|||||||
@ -7,9 +7,9 @@
|
|||||||
(defstruct Missing [id i32])
|
(defstruct Missing [id i32])
|
||||||
(defstruct Other [id i32])
|
(defstruct Other [id i32])
|
||||||
|
|
||||||
(defvar log i64)
|
(defonce log i64)
|
||||||
(defvar order i64)
|
(defonce order i64)
|
||||||
(defvar seen i64)
|
(defonce seen i64)
|
||||||
|
|
||||||
(defn note [n i64] () (set order (+ (* order 10) n)))
|
(defn note [n i64] () (set order (+ (* order 10) n)))
|
||||||
|
|
||||||
|
|||||||
@ -8,8 +8,8 @@
|
|||||||
(defstruct AssetMissing [id i32])
|
(defstruct AssetMissing [id i32])
|
||||||
(defstruct Corrupt [id i32])
|
(defstruct Corrupt [id i32])
|
||||||
|
|
||||||
(defvar seen i64)
|
(defonce seen i64)
|
||||||
(defvar other i64)
|
(defonce other i64)
|
||||||
|
|
||||||
;;; Signals twice and keeps going both times — that is the whole of §1.
|
;;; Signals twice and keeps going both times — that is the whole of §1.
|
||||||
(defn load-all [] ()
|
(defn load-all [] ()
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
;;;;
|
;;;;
|
||||||
;;;; The numbers differ per failure, so a wrong answer names its own cause.
|
;;;; The numbers differ per failure, so a wrong answer names its own cause.
|
||||||
|
|
||||||
(defvar order i64)
|
(defonce order i64)
|
||||||
|
|
||||||
(defn note [n i32] () (set order (+ (* order 10) (i64 n))))
|
(defn note [n i32] () (set order (+ (* order 10) (i64 n))))
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
;;;; The third element of a defvar, both ways.
|
;;;; The third element of a defonce, both ways.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; The rule, 2026-09-20: a type there is the zeroed static global it has
|
;;;; The rule, 2026-09-20: a type there is the zeroed static global it has
|
||||||
;;;; always been, and anything else is a dyn global initialised from that
|
;;;; always been, and anything else is a dyn global initialised from that
|
||||||
@ -7,7 +7,7 @@
|
|||||||
;;;; one of its declarations got.
|
;;;; one of its declarations got.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; There is no new lowering under any of it. The dyn half is exactly what
|
;;;; There is no new lowering under any of it. The dyn half is exactly what
|
||||||
;;;; [(defvar x dyn <expr>)] already compiled to — the initialiser lifted into
|
;;;; [(defonce x dyn <expr>)] already compiled to — the initialiser lifted into
|
||||||
;;;; the startup function that main calls after the runtime is up, and a
|
;;;; the startup function that main calls after the runtime is up, and a
|
||||||
;;;; collector root pushed for the global before it runs — which is why this
|
;;;; collector root pushed for the global before it runs — which is why this
|
||||||
;;;; program runs identically on both backends and why neither of them
|
;;;; program runs identically on both backends and why neither of them
|
||||||
@ -18,29 +18,29 @@
|
|||||||
;; ── The type reading, which is every declaration that worked before ──
|
;; ── The type reading, which is every declaration that worked before ──
|
||||||
;; A primitive, a fixed array, a struct and a container: all four are types in
|
;; A primitive, a fixed array, a struct and a container: all four are types in
|
||||||
;; the third position, so all four are the zeroed statics they were.
|
;; the third position, so all four are the zeroed statics they were.
|
||||||
(defvar current-color i32)
|
(defonce current-color i32)
|
||||||
(defvar grid [2 [3 u32]])
|
(defonce grid [2 [3 u32]])
|
||||||
(defvar origin Point)
|
(defonce origin Point)
|
||||||
(defvar bytes (Vec u8))
|
(defonce bytes (Vec u8))
|
||||||
|
|
||||||
;; ── The value reading, which is the new spelling ─────────────────────
|
;; ── The value reading, which is the new spelling ─────────────────────
|
||||||
;; None of these names a type, so each is a dyn global holding the value its
|
;; None of these names a type, so each is a dyn global holding the value its
|
||||||
;; expression produced before the program's own code ran.
|
;; expression produced before the program's own code ran.
|
||||||
(defvar score 0)
|
(defonce score 0)
|
||||||
(defvar label "start")
|
(defonce label "start")
|
||||||
(defvar config {:level 1 :name "one"})
|
(defonce config {:level 1 :name "one"})
|
||||||
(defvar tally seeded)
|
(defonce tally seeded)
|
||||||
|
|
||||||
;; A call, which is the shape the author kept writing: the file is read once,
|
;; A call, which is the shape the author kept writing: the file is read once,
|
||||||
;; at startup, into a global that outlives main.
|
;; at startup, into a global that outlives main.
|
||||||
(defn load [] dyn {:rows 3 :cols 4})
|
(defn load [] dyn {:rows 3 :cols 4})
|
||||||
|
|
||||||
(defvar game-data (load))
|
(defonce game-data (load))
|
||||||
|
|
||||||
;; And the value the bare symbol above was initialised from, declared *below*
|
;; And the value the bare symbol above was initialised from, declared *below*
|
||||||
;; it on purpose: which reading a defvar gets is decided with every name in
|
;; it on purpose: which reading a defonce gets is decided with every name in
|
||||||
;; hand, not in the order the file was written.
|
;; hand, not in the order the file was written.
|
||||||
(defvar seeded i64 7)
|
(defonce seeded i64 7)
|
||||||
|
|
||||||
(defn main [] ()
|
(defn main [] ()
|
||||||
;; The statics, untouched: zero, zero, zero, and an empty Vec that is a real
|
;; The statics, untouched: zero, zero, zero, and an empty Vec that is a real
|
||||||
@ -16,7 +16,7 @@
|
|||||||
(defstruct Point [x i32 y i32])
|
(defstruct Point [x i32 y i32])
|
||||||
(defstruct Line [a Point b Point])
|
(defstruct Line [a Point b Point])
|
||||||
|
|
||||||
(defvar calls i32)
|
(defonce calls i32)
|
||||||
|
|
||||||
(defn make-point [] Point
|
(defn make-point [] Point
|
||||||
(set calls (+ calls 1))
|
(set calls (+ calls 1))
|
||||||
|
|||||||
@ -20,8 +20,8 @@
|
|||||||
;;;; bounds failure now lands somewhere a session can be recovered from.
|
;;;; bounds failure now lands somewhere a session can be recovered from.
|
||||||
(import agent "vendor:agent")
|
(import agent "vendor:agent")
|
||||||
|
|
||||||
(defvar grid [4 i32])
|
(defonce grid [4 i32])
|
||||||
(defvar skipped i64)
|
(defonce skipped i64)
|
||||||
|
|
||||||
;;; One frame deep under the restart-case, so the transfer has something to
|
;;; One frame deep under the restart-case, so the transfer has something to
|
||||||
;;; cross and the backtrace has something to show.
|
;;; cross and the backtrace has something to show.
|
||||||
@ -33,7 +33,7 @@
|
|||||||
(do (touch i) (println "frame done"))
|
(do (touch i) (println "frame done"))
|
||||||
(continue [] (set skipped (+ skipped 1)))))
|
(continue [] (set skipped (+ skipped 1)))))
|
||||||
|
|
||||||
(defvar ticks i64)
|
(defonce ticks i64)
|
||||||
|
|
||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
(agent/start "/tmp/flan-dev-break-bounds-fallback.sock")
|
(agent/start "/tmp/flan-dev-break-bounds-fallback.sock")
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
(use-placeholder [] -1)
|
(use-placeholder [] -1)
|
||||||
(retry [] 7)))
|
(retry [] 7)))
|
||||||
|
|
||||||
(defvar ticks i64)
|
(defonce ticks i64)
|
||||||
|
|
||||||
(defn step [] i64
|
(defn step [] i64
|
||||||
(set ticks (+ ticks 1))
|
(set ticks (+ ticks 1))
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
;;; Counted so that an evaluation has something of the program's own to read,
|
;;; Counted so that an evaluation has something of the program's own to read,
|
||||||
;;; and so a transcript can be checked for progress rather than only for text.
|
;;; and so a transcript can be checked for progress rather than only for text.
|
||||||
(defvar frames i64)
|
(defonce frames i64)
|
||||||
|
|
||||||
(defn chatter [] i64
|
(defn chatter [] i64
|
||||||
;; Sixty-four lines of sixty-three characters and a newline: 4096 bytes a
|
;; Sixty-four lines of sixty-three characters and a newline: 4096 bytes a
|
||||||
|
|||||||
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
(defmethod area point [p] (* (get p :x) (get p :y)))
|
(defmethod area point [p] (* (get p :x) (get p :y)))
|
||||||
|
|
||||||
(defvar instances dyn)
|
(defonce instances dyn)
|
||||||
|
|
||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
(agent/start "/tmp/flan-dev-classes-fallback.sock")
|
(agent/start "/tmp/flan-dev-classes-fallback.sock")
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
;;;; and everything this fixture is for happens after it.
|
;;;; and everything this fixture is for happens after it.
|
||||||
(import agent "vendor:agent")
|
(import agent "vendor:agent")
|
||||||
|
|
||||||
(defvar config dyn)
|
(defonce config dyn)
|
||||||
|
|
||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
(agent/start "/tmp/flan-dev-dyn-global-fallback.sock")
|
(agent/start "/tmp/flan-dev-dyn-global-fallback.sock")
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
;;;;
|
;;;;
|
||||||
;;;; dev-locals.flan is about what one frame holds. This is about what the
|
;;;; dev-locals.flan is about what one frame holds. This is about what the
|
||||||
;;;; whole stopped stack is reading, which in this language is most of the
|
;;;; whole stopped stack is reading, which in this language is most of the
|
||||||
;;;; program: a game keeps its state in top-level defvars, and sand.flan holds
|
;;;; program: a game keeps its state in top-level defonces, and sand.flan holds
|
||||||
;;;; its entire grid that way.
|
;;;; its entire grid that way.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; The globals are declared in an order the answer must *not* come back in.
|
;;;; The globals are declared in an order the answer must *not* come back in.
|
||||||
@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
(defstruct Boom [why i32])
|
(defstruct Boom [why i32])
|
||||||
|
|
||||||
(defvar label string)
|
(defonce label string)
|
||||||
(defvar grid [4 i32])
|
(defonce grid [4 i32])
|
||||||
(defvar pressure i64)
|
(defonce pressure i64)
|
||||||
(defvar untouched i64 99)
|
(defonce untouched i64 99)
|
||||||
|
|
||||||
;; The inner frame. It writes two globals and then errors with nothing
|
;; The inner frame. It writes two globals and then errors with nothing
|
||||||
;; handling the condition, so the program stops here with [main] under it.
|
;; handling the condition, so the program stops here with [main] under it.
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
;; The discriminator. `outer' binds a local of this name to something else, so
|
;; The discriminator. `outer' binds a local of this name to something else, so
|
||||||
;; every claim about which frame answered is visible in the value itself.
|
;; every claim about which frame answered is visible in the value itself.
|
||||||
(defvar mark i64)
|
(defonce mark i64)
|
||||||
|
|
||||||
;; The innermost frame, and it is deliberately dull: it holds nothing worth
|
;; The innermost frame, and it is deliberately dull: it holds nothing worth
|
||||||
;; inspecting, so that the frame worth inspecting is not the one an expression
|
;; inspecting, so that the frame worth inspecting is not the one an expression
|
||||||
@ -41,7 +41,7 @@
|
|||||||
s (Shape.Rect {.w 3 .h 6})]
|
s (Shape.Rect {.w 3 .h 6})]
|
||||||
(deeper)))
|
(deeper)))
|
||||||
|
|
||||||
(defvar ticks i64)
|
(defonce ticks i64)
|
||||||
|
|
||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
(set mark 99)
|
(set mark 99)
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
;;;; a real daemon as well.
|
;;;; a real daemon as well.
|
||||||
(import agent "vendor:agent")
|
(import agent "vendor:agent")
|
||||||
|
|
||||||
(defvar frames i64)
|
(defonce frames i64)
|
||||||
|
|
||||||
(defn step [] i64 7)
|
(defn step [] i64 7)
|
||||||
|
|
||||||
|
|||||||
@ -39,7 +39,7 @@
|
|||||||
(let [after (i64 99)] after))
|
(let [after (i64 99)] after))
|
||||||
(carry-on [] 5)))))
|
(carry-on [] 5)))))
|
||||||
|
|
||||||
(defvar ticks i64)
|
(defonce ticks i64)
|
||||||
|
|
||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
(agent/start "/tmp/flan-dev-locals-fallback.sock")
|
(agent/start "/tmp/flan-dev-locals-fallback.sock")
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
;;;; landing there is the zero-argument form working end to end.
|
;;;; landing there is the zero-argument form working end to end.
|
||||||
(import agent "vendor:agent")
|
(import agent "vendor:agent")
|
||||||
|
|
||||||
(defvar ticks i64)
|
(defonce ticks i64)
|
||||||
|
|
||||||
;;; A condition and something that signals it, so that a body typed in later
|
;;; A condition and something that signals it, so that a body typed in later
|
||||||
;;; can establish a handler and transfer past this frame — spec-conditions.md
|
;;; can establish a handler and transfer past this frame — spec-conditions.md
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
;;;; running beside it.
|
;;;; running beside it.
|
||||||
(import agent "vendor:agent")
|
(import agent "vendor:agent")
|
||||||
|
|
||||||
(defvar ticks i64)
|
(defonce ticks i64)
|
||||||
|
|
||||||
(defn parity [n i64] string
|
(defn parity [n i64] string
|
||||||
(let [out "even"]
|
(let [out "even"]
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
;;;; back to a fresh park in one op.
|
;;;; back to a fresh park in one op.
|
||||||
(import agent "vendor:agent")
|
(import agent "vendor:agent")
|
||||||
|
|
||||||
(defvar runs i64)
|
(defonce runs i64)
|
||||||
|
|
||||||
(defn step [] i64 7)
|
(defn step [] i64 7)
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
;;;; tail with nothing in front of it.
|
;;;; tail with nothing in front of it.
|
||||||
(import agent "vendor:agent")
|
(import agent "vendor:agent")
|
||||||
|
|
||||||
(defvar ticks i64)
|
(defonce ticks i64)
|
||||||
|
|
||||||
;;; Something to stop on that is *not* a breakpoint, so that a marked
|
;;; Something to stop on that is *not* a breakpoint, so that a marked
|
||||||
;;; expression sent to an already-stopped program can be told from the break it
|
;;; expression sent to an already-stopped program can be told from the break it
|
||||||
|
|||||||
@ -34,8 +34,8 @@
|
|||||||
;;; a printed line because a global is reachable by name from `C-x C-e' while
|
;;; a printed line because a global is reachable by name from `C-x C-e' while
|
||||||
;;; the program is stopped, and a local is not: the test asks the session for
|
;;; the program is stopped, and a local is not: the test asks the session for
|
||||||
;;; them the way a person at the break loop would.
|
;;; them the way a person at the break loop would.
|
||||||
(defvar live-addr i64)
|
(defonce live-addr i64)
|
||||||
(defvar dead-addr i64)
|
(defonce dead-addr i64)
|
||||||
|
|
||||||
(defn deeper [] i64
|
(defn deeper [] i64
|
||||||
(restart-case
|
(restart-case
|
||||||
@ -54,7 +54,7 @@
|
|||||||
(set dead-addr (ptr-num dead))
|
(set dead-addr (ptr-num dead))
|
||||||
(deeper))))
|
(deeper))))
|
||||||
|
|
||||||
(defvar ticks i64)
|
(defonce ticks i64)
|
||||||
|
|
||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
(agent/start "/tmp/flan-dev-ptr-fallback.sock")
|
(agent/start "/tmp/flan-dev-ptr-fallback.sock")
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
;;; because the break loop matches on a class the *host* was compiled with.
|
;;; because the break loop matches on a class the *host* was compiled with.
|
||||||
(defstruct Missing [id i32])
|
(defstruct Missing [id i32])
|
||||||
|
|
||||||
(defvar ticks i64)
|
(defonce ticks i64)
|
||||||
(defn step [] i64
|
(defn step [] i64
|
||||||
(set ticks (+ ticks 1))
|
(set ticks (+ ticks 1))
|
||||||
ticks)
|
ticks)
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
;;;; What a re-run does to a global, which is decided by the form that
|
;;;; What a re-run does to a global, which is decided by the form that
|
||||||
;;;; defined it and not by the daemon.
|
;;;; defined it and not by the daemon.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; A [defvar] is Common Lisp's [defvar]: its initialiser runs only if the
|
;;;; A [defonce] is Common Lisp's [defvar] under Clojure's name: its
|
||||||
|
;;;; initialiser runs only if the
|
||||||
;;;; variable is not already initialised, so its value survives a re-run. A
|
;;;; variable is not already initialised, so its value survives a re-run. A
|
||||||
;;;; plain zeroed one always did — .bss is untouched by a second entry into
|
;;;; plain zeroed one always did — .bss is untouched by a second entry into
|
||||||
;;;; main — and a computed one did not, because the startup function [main]
|
;;;; main — and a computed one did not, because the startup function [main]
|
||||||
@ -12,7 +13,7 @@
|
|||||||
;;;; 2026-09-20 there is no other kind: a defconst's initialiser has to be a
|
;;;; 2026-09-20 there is no other kind: a defconst's initialiser has to be a
|
||||||
;;;; compile-time constant, refused in the checker so that both backends
|
;;;; compile-time constant, refused in the checker so that both backends
|
||||||
;;;; refuse the same program. Before that the LLVM backend refused a computed
|
;;;; refuse the same program. Before that the LLVM backend refused a computed
|
||||||
;;;; one while x86 guarded it at startup like a defvar.
|
;;;; one while x86 guarded it at startup like a defonce.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; So each line printed below is a claim about one of those cases, and the
|
;;;; So each line printed below is a claim about one of those cases, and the
|
||||||
;;;; run number is the first of them: [runs] is computed, so before the fix it
|
;;;; run number is the first of them: [runs] is computed, so before the fix it
|
||||||
@ -25,10 +26,10 @@
|
|||||||
;; lifted into the startup function rather than written into the image.
|
;; lifted into the startup function rather than written into the image.
|
||||||
(defn start [] i64 base)
|
(defn start [] i64 base)
|
||||||
|
|
||||||
(defvar counter i64 (start))
|
(defonce counter i64 (start))
|
||||||
|
|
||||||
;; Zero-valued, which needs no startup at all and must keep needing none.
|
;; Zero-valued, which needs no startup at all and must keep needing none.
|
||||||
(defvar zeroed i64)
|
(defonce zeroed i64)
|
||||||
|
|
||||||
;; A computed dyn global: the map is built by a function, rooted before the
|
;; A computed dyn global: the map is built by a function, rooted before the
|
||||||
;; startup function runs, and mutated by every run. Its contents have to
|
;; startup function runs, and mutated by every run. Its contents have to
|
||||||
@ -36,7 +37,7 @@
|
|||||||
;; has to survive collection either way.
|
;; has to survive collection either way.
|
||||||
(defn table [] dyn {:runs 0})
|
(defn table [] dyn {:runs 0})
|
||||||
|
|
||||||
(defvar state dyn (table))
|
(defonce state dyn (table))
|
||||||
|
|
||||||
;; The same thing written the short way: the third element is not a type, so
|
;; The same thing written the short way: the third element is not a type, so
|
||||||
;; this is a dyn global initialised at startup — the same declaration [state]
|
;; this is a dyn global initialised at startup — the same declaration [state]
|
||||||
@ -44,7 +45,7 @@
|
|||||||
;; a second one. Its value has to survive a re-run for exactly [counter]'s
|
;; a second one. Its value has to survive a re-run for exactly [counter]'s
|
||||||
;; reason, and if the new spelling had grown a startup path of its own this is
|
;; reason, and if the new spelling had grown a startup path of its own this is
|
||||||
;; the line that would count 1, 1, 1, 1.
|
;; the line that would count 1, 1, 1, 1.
|
||||||
(defvar tally 0)
|
(defonce tally 0)
|
||||||
|
|
||||||
;; A typed array with a computed initialiser: (array-fill ...) is an
|
;; A typed array with a computed initialiser: (array-fill ...) is an
|
||||||
;; expression, so it is lifted into the startup function and guarded there
|
;; expression, so it is lifted into the startup function and guarded there
|
||||||
@ -52,7 +53,7 @@
|
|||||||
;; entry into main — this would count 251, 251, 251, 251 instead of climbing,
|
;; entry into main — this would count 251, 251, 251, 251 instead of climbing,
|
||||||
;; which is [counter]'s own failure in the one shape that only an array can
|
;; which is [counter]'s own failure in the one shape that only an array can
|
||||||
;; have.
|
;; have.
|
||||||
(defvar grid [2 [3 u8]] (array-fill [2 3] 250))
|
(defonce grid [2 [3 u8]] (array-fill [2 3] 250))
|
||||||
|
|
||||||
;; The guard flags the fix adds are the compiler's own globals, and they used
|
;; The guard flags the fix adds are the compiler's own globals, and they used
|
||||||
;; to be spelled [.init-once.<name>] — a name a program can write, since [.]
|
;; to be spelled [.init-once.<name>] — a name a program can write, since [.]
|
||||||
@ -61,7 +62,20 @@
|
|||||||
;; now; before that the dev build died at the assembler with the symbol
|
;; now; before that the dev build died at the assembler with the symbol
|
||||||
;; defined twice, and the flag's Bool retyped this i64 on the way. It stays
|
;; defined twice, and the flag's Bool retyped this i64 on the way. It stays
|
||||||
;; unprinted on purpose — the expected output is what it was.
|
;; unprinted on purpose — the expected output is what it was.
|
||||||
(defvar .init-once.counter i64 7)
|
(defonce .init-once.counter i64 7)
|
||||||
|
|
||||||
|
;; [def], Common Lisp's defparameter: its initialiser runs on every re-run,
|
||||||
|
;; with no guard flag, so the increment each run makes is painted over before
|
||||||
|
;; main reads it back. Where [tally] climbs 1, 2, 3, 4, this prints 4, 4, 4, 4
|
||||||
|
;; — and after the daemon evaluates an edited (def c 9), the next re-run
|
||||||
|
;; prints 10, which is the whole reason the form exists: an edited
|
||||||
|
;; initialiser takes effect on C-c C-c plus re-run.
|
||||||
|
(def c 3)
|
||||||
|
|
||||||
|
;; The typed-array spelling of the same form. The re-run repaints the same
|
||||||
|
;; storage — no reallocation — so the element main increments is 7 again by
|
||||||
|
;; the time it is read: 8 on every run, never 9.
|
||||||
|
(def hues [4 u32] (array-fill [4] 7))
|
||||||
|
|
||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
(agent/start "/tmp/flan-dev-rerun-fallback.sock")
|
(agent/start "/tmp/flan-dev-rerun-fallback.sock")
|
||||||
@ -71,6 +85,8 @@
|
|||||||
(put state :runs (+ (get state :runs) 1))
|
(put state :runs (+ (get state :runs) 1))
|
||||||
(set tally (+ tally 1))
|
(set tally (+ tally 1))
|
||||||
(set (at grid 0 0) (u8 (+ (i32 (at grid 0 0)) 1)))
|
(set (at grid 0 0) (u8 (+ (i32 (at grid 0 0)) 1)))
|
||||||
|
(set c (+ c 1))
|
||||||
|
(set (at hues 0) (u32 (+ (i32 (at hues 0)) 1)))
|
||||||
(print "counter ") (print counter) (println "")
|
(print "counter ") (print counter) (println "")
|
||||||
(print "zeroed ") (print zeroed) (println "")
|
(print "zeroed ") (print zeroed) (println "")
|
||||||
(print "runs ") (print (get state :runs)) (println "")
|
(print "runs ") (print (get state :runs)) (println "")
|
||||||
@ -80,6 +96,8 @@
|
|||||||
;; on every run, and 0 if the initialiser had been skipped outright.
|
;; on every run, and 0 if the initialiser had been skipped outright.
|
||||||
(print "grid-far ") (print (i32 (at grid 1 2))) (println "")
|
(print "grid-far ") (print (i32 (at grid 1 2))) (println "")
|
||||||
(print "base ") (print base) (println "")
|
(print "base ") (print base) (println "")
|
||||||
|
(print "c ") (print c) (println "")
|
||||||
|
(print "hue ") (print (i32 (at hues 0))) (println "")
|
||||||
;; Long enough for a client to be served, short enough to park well inside
|
;; Long enough for a client to be served, short enough to park well inside
|
||||||
;; any watchdog — dev-macro.flan's clock, for its reason.
|
;; any watchdog — dev-macro.flan's clock, for its reason.
|
||||||
(dotimes [i 100]
|
(dotimes [i 100]
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
;;;; leave a process of this behind for the rest of the afternoon.
|
;;;; leave a process of this behind for the rest of the afternoon.
|
||||||
(import agent "vendor:agent")
|
(import agent "vendor:agent")
|
||||||
|
|
||||||
(defvar ticks i64)
|
(defonce ticks i64)
|
||||||
|
|
||||||
(defn step [] i64
|
(defn step [] i64
|
||||||
(set ticks (+ ticks 1))
|
(set ticks (+ ticks 1))
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
;;;; The same claim as dev-trap-free-all.flan over the other allocator trap,
|
;;;; The same claim as dev-trap-free-all.flan over the other allocator trap,
|
||||||
;;;; and with nothing on the restart stack.
|
;;;; and with nothing on the restart stack.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; `nowhere` is a zeroed `Allocator` — a `defvar` nobody assigned — and
|
;;;; `nowhere` is a zeroed `Allocator` — a `defonce` nobody assigned — and
|
||||||
;;;; `free-all` on it is the "I released the region" / "I never made one"
|
;;;; `free-all` on it is the "I released the region" / "I never made one"
|
||||||
;;;; collapse the runtime refuses to let a program spell the same way. The
|
;;;; collapse the runtime refuses to let a program spell the same way. The
|
||||||
;;;; refusal is a trap with no transfer channel, so it parks rather than
|
;;;; refusal is a trap with no transfer channel, so it parks rather than
|
||||||
@ -10,7 +10,7 @@
|
|||||||
;;;; can fix the program in and a daemon that is gone.
|
;;;; can fix the program in and a daemon that is gone.
|
||||||
(import agent "vendor:agent")
|
(import agent "vendor:agent")
|
||||||
|
|
||||||
(defvar nowhere Allocator)
|
(defonce nowhere Allocator)
|
||||||
|
|
||||||
(defn main [] i32
|
(defn main [] i32
|
||||||
(agent/start "/tmp/flan-dev-trap-null-alloc-fallback.sock")
|
(agent/start "/tmp/flan-dev-trap-null-alloc-fallback.sock")
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
;; exists for; see flan_dev.c, "A number sampled thousands of times a frame".
|
;; exists for; see flan_dev.c, "A number sampled thousands of times a frame".
|
||||||
(declare-c watch-num-i64 [name string x i64] i32 "flan_dev_watch_num_i64")
|
(declare-c watch-num-i64 [name string x i64] i32 "flan_dev_watch_num_i64")
|
||||||
|
|
||||||
(defvar ticks i64)
|
(defonce ticks i64)
|
||||||
|
|
||||||
(defn loop-cells [] i32
|
(defn loop-cells [] i32
|
||||||
(let [i 0]
|
(let [i 0]
|
||||||
|
|||||||
@ -10,10 +10,10 @@
|
|||||||
;;;; be unboxed at are the ones that do: a parameter, a return type, and a
|
;;;; be unboxed at are the ones that do: a parameter, a return type, and a
|
||||||
;;;; global's declared type. All three are here.
|
;;;; global's declared type. All three are here.
|
||||||
|
|
||||||
(defvar seven i64 7)
|
(defonce seven i64 7)
|
||||||
(defvar boxed dyn 21)
|
(defonce boxed dyn 21)
|
||||||
;; The other direction at a global: a dyn initialiser meeting a written type.
|
;; The other direction at a global: a dyn initialiser meeting a written type.
|
||||||
(defvar unboxed i64 boxed)
|
(defonce unboxed i64 boxed)
|
||||||
|
|
||||||
(defn take-dyn [d dyn] dyn
|
(defn take-dyn [d dyn] dyn
|
||||||
(+ d 100))
|
(+ d 100))
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
;;;; the computed globals already use, which is the point: a dyn global is a
|
;;;; the computed globals already use, which is the point: a dyn global is a
|
||||||
;;;; computed global and needed no new mechanism.
|
;;;; computed global and needed no new mechanism.
|
||||||
|
|
||||||
(defvar counter dyn 0)
|
(defonce counter dyn 0)
|
||||||
(defvar label dyn "start")
|
(defonce label dyn "start")
|
||||||
|
|
||||||
(defn bump [] ()
|
(defn bump [] ()
|
||||||
(set counter (+ counter 1)))
|
(set counter (+ counter 1)))
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user