An array is a value you can write, not a place you have to fill first
DISCUSS.org's "need a value-producing array constructor": the author wanted grid filled with 255 as part of its declaration and could not write it. (array n T) produces the zeroed array only, and dotimes is Unit, so it can mutate a place that already exists but cannot be the initialiser expression -- which has to produce the whole value in one go. The grid was declared zeroed and filled in main instead. Two forms, both expressions, both any rank: (array-fill [rows cols] 255) every element that value (array-gen [rows cols] cell) every element (cell i j) Spelled apart rather than one form dispatching on the third element's type, because an array *of* function values is a thing to want and one form would have to decide whether (array-fill [4] f) meant four copies of f or four calls of it. The dimensions are read in Parse, and that is the whole reason they are recognised there: handed through as an ordinary call, [rows cols] is an array literal of two names, and where those names are defconsts it is a perfectly good two-element array of integers -- the wrong reading, and a silent one. Read in Parse they are the same len the [n T] type spelling takes, resolved by the same array_len, with one extra condition of their own: the fill counts in i32 like every index in the language, so a dimension no i32 can reach has no loop that could end. The lowering is a loop over a slot, not an aggregate. Tast.Arr is the node the backends have and both build it element by element from a list as long as the array; a fill of [600 [800 u8]] is half a million elements and there is no list to be had. So these bind the array to a slot, zero it, run one While per dimension writing through Set of a Pindex, and answer with the slot -- While, Set and Pindex, which is the argument check_loop already makes for recur. Nothing new reaches a backend and all three get the form with no edit. The value stays value-like: the slot is the form's own, and the Local at the end copies out the way any array-typed expression does. Row-major is pinned, not incidental: the first dimension is the outermost loop, and a generator that counts observes it. The fill value and the generator value are each bound once before any loop starts, so (array-fill [n] (next-id)) is one call and n copies of its answer. What falls out for the defvar the note was written about, and neither half is a carve-out: (defvar grid [rows [cols u8]] (array-fill [rows cols] 255)) is the spelling that works -- a typed global with a computed initialiser, which is the startup-lifted path with the init-once guard that defvar already had, so the fill runs once and the value survives a re-run like any other computed one. The three-element spelling means what the 2026-09-20 rule says it means: not a type, so a dyn global, and a typed fixed array crosses into dyn only as a view of storage that outlives the view. A freshly built array is a temporary, so it is refused -- by the element rule where the elements are themselves an array, by the lifetime rule where they are one of the three scalars a view carries. Both refusals are the ones any other temporary gets. The type an array-fill builds never goes through resolve, so resolve's own guard is asked again where it is built: a fixed array of function values would be zeroed, and a zeroed function value is a null pointer.
This commit is contained in:
parent
e807986622
commit
d4def945a9
26
lib/ast.ml
26
lib/ast.ml
@ -92,6 +92,28 @@ and expr_kind =
|
|||||||
fails on an unknown name. This is that position's answer, and it says what
|
fails on an unknown name. This is that position's answer, and it says what
|
||||||
it does rather than looking like a vector of two things. *)
|
it does rather than looking like a vector of two things. *)
|
||||||
| ArrayOf of texpr (* the whole array type, built by Parse *)
|
| ArrayOf of texpr (* the whole array type, built by Parse *)
|
||||||
|
(* (array-fill [r c] v) and (array-gen [r c] f) — a fixed array of any rank
|
||||||
|
as an *expression*, which is what [ArrayOf] and [dotimes] between them
|
||||||
|
could not be: [ArrayOf] produces the zeroed value only, and [dotimes] is
|
||||||
|
Unit and can only mutate a place that already exists. These produce the
|
||||||
|
whole value, so they compose where a bracket literal does.
|
||||||
|
|
||||||
|
The dimensions are in brackets and are [len]s, not expressions, for the
|
||||||
|
reason the brackets are read at all: in expression position [[rows cols]]
|
||||||
|
is an array *literal* of two names, and where those names are defconsts
|
||||||
|
it would quietly type-check as one. So the form is recognised in [Parse]
|
||||||
|
and the brackets are read with the same [len] the [n T] type spelling
|
||||||
|
uses — an integer or a compile-time constant's name, and nothing else.
|
||||||
|
|
||||||
|
Two forms rather than one with a dispatch on the third element's type: an
|
||||||
|
array *of function values* is a thing one may want, and a single form
|
||||||
|
would have to decide whether [(array-fill [4] f)] meant four copies of
|
||||||
|
[f] or four calls of it. Spelled apart, neither reading is ever in doubt.
|
||||||
|
|
||||||
|
[ArrayGen]'s expression is a function value taking one index per
|
||||||
|
dimension; [ArrayFill]'s is the element value itself, evaluated once. *)
|
||||||
|
| ArrayFill of len list * expr
|
||||||
|
| ArrayGen of len list * expr
|
||||||
(* These bind names or alter control flow, so none of them can be a call. *)
|
(* These bind names or alter control flow, so none of them can be a call. *)
|
||||||
| Fn of string list * expr list (* (fn [x y] ...) — non-escaping *)
|
| Fn of string list * expr list (* (fn [x y] ...) — non-escaping *)
|
||||||
| Dotimes of string option * string * expr * expr list (* (dotimes :o [i n] ...) *)
|
| Dotimes of string option * string * expr * expr list (* (dotimes :o [i n] ...) *)
|
||||||
@ -368,6 +390,10 @@ let map_children f (e : expr) : expr =
|
|||||||
| Bare fs -> Bare (List.map (fun (n, v) -> (n, ex v)) fs)
|
| Bare fs -> Bare (List.map (fun (n, v) -> (n, ex v)) fs)
|
||||||
| MapLit (tag, kvs) -> MapLit (tag, List.map (fun (k, v) -> (ex k, ex v)) kvs)
|
| MapLit (tag, kvs) -> MapLit (tag, List.map (fun (k, v) -> (ex k, ex v)) kvs)
|
||||||
| Arr es -> Arr (List.map ex es)
|
| Arr es -> Arr (List.map ex es)
|
||||||
|
(* Not leaves: the fill value and the generator are ordinary
|
||||||
|
subexpressions. The dimensions are [len]s and hold none. *)
|
||||||
|
| ArrayFill (ds, v) -> ArrayFill (ds, ex v)
|
||||||
|
| ArrayGen (ds, f) -> ArrayGen (ds, ex f)
|
||||||
| Fn (ps, es) -> Fn (ps, List.map ex es)
|
| Fn (ps, es) -> Fn (ps, List.map ex es)
|
||||||
| Dotimes (l, n, c, es) -> Dotimes (l, n, ex c, List.map ex es)
|
| Dotimes (l, n, c, es) -> Dotimes (l, n, ex c, List.map ex es)
|
||||||
| Defer es -> Defer (List.map ex es)
|
| Defer es -> Defer (List.map ex es)
|
||||||
|
|||||||
175
lib/check.ml
175
lib/check.ml
@ -2573,6 +2573,8 @@ let rec check ctx ?want (e : Ast.expr) : Tast.expr =
|
|||||||
| Ast.ArrayOf t ->
|
| Ast.ArrayOf t ->
|
||||||
let ty = resolve ctx.env t in
|
let ty = resolve ctx.env t in
|
||||||
expect ctx loc ~want (mk loc ty (Tast.Zero ty))
|
expect ctx loc ~want (mk loc ty (Tast.Zero ty))
|
||||||
|
| Ast.ArrayFill (dims, v) -> check_array_fill ctx ~want loc dims v
|
||||||
|
| Ast.ArrayGen (dims, f) -> check_array_gen ctx ~want loc dims f
|
||||||
| Ast.Match (scrutinee, arms) -> check_match ctx ~tail ?want loc scrutinee arms
|
| Ast.Match (scrutinee, arms) -> check_match ctx ~tail ?want loc scrutinee arms
|
||||||
| Ast.Call (head, args) -> check_call ctx ~want loc head args
|
| Ast.Call (head, args) -> check_call ctx ~want loc head args
|
||||||
| Ast.Unwrap (Ast.Usome, v) ->
|
| Ast.Unwrap (Ast.Usome, v) ->
|
||||||
@ -4107,6 +4109,179 @@ and check_arr ctx ~want loc items =
|
|||||||
an array literal does not satisfy a slice expectation. *)
|
an array literal does not satisfy a slice expectation. *)
|
||||||
expect ctx loc ~want (mk loc (Types.Array (n, elem)) (Tast.Arr items))
|
expect ctx loc ~want (mk loc (Types.Array (n, elem)) (Tast.Arr items))
|
||||||
|
|
||||||
|
(* ── (array-fill [r c] v) and (array-gen [r c] f) ──────────────────────
|
||||||
|
|
||||||
|
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
|
||||||
|
write "an array of these" as an *expression* — which is what a defvar
|
||||||
|
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
|
||||||
|
one the backends already have, and both build it element by element from a
|
||||||
|
list that is as long as the array: an [insertvalue] chain on LLVM, a store
|
||||||
|
per element on x86. A fill of [[600 800 u8]] is half a million elements and
|
||||||
|
there is no list to be had. So these lower to a *loop over a slot*: bind the
|
||||||
|
array to a slot, zero it, run one loop per dimension writing each element
|
||||||
|
through [Tast.Set] of a [Pindex], and answer with the slot. Nothing new
|
||||||
|
reaches a backend — it is [While], [Set] and [Pindex], which is the same
|
||||||
|
argument [check_loop] makes for [recur] — and both backends get the form
|
||||||
|
with no edit, the js one included.
|
||||||
|
|
||||||
|
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
|
||||||
|
other array-typed expression is. In a [defvar] initialiser the copy 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
|
||||||
|
temporary, would be the faster lowering and is deliberately not what this
|
||||||
|
does — the destination is not a thing an expression may know about, and
|
||||||
|
[mem2reg] plus the store-to-load forwarding both backends already get is
|
||||||
|
where that cost goes.
|
||||||
|
|
||||||
|
The slot is zeroed before the loops rather than left [Uninit]. An element
|
||||||
|
type of [dyn] is the reason it has to be: between the binding and the
|
||||||
|
store that overwrites it the collector may run, and it would read whatever
|
||||||
|
the frame happened to hold as a dyn word. The double write is the price and
|
||||||
|
it is one memset.
|
||||||
|
|
||||||
|
**Row-major, pinned.** The first dimension is the outermost loop, so
|
||||||
|
[[i][j]] runs with [j] fastest. A generator that prints, or counts, or
|
||||||
|
appends, observes that order, so it is a promise: this is the order, not
|
||||||
|
the order the nesting happened to come out in.
|
||||||
|
|
||||||
|
**Evaluated once.** The fill value and the generator *value* are each bound
|
||||||
|
to a slot before any loop starts, so [(array-fill [n] (next-id))] is one
|
||||||
|
call and n copies of its answer — not n calls. A generator's *body*, of
|
||||||
|
course, runs once per element; that is what it is for. *)
|
||||||
|
|
||||||
|
(* The dimensions, resolved by the same rule the [n T] type spelling uses —
|
||||||
|
[array_len] is literally that rule — with the one extra condition this form
|
||||||
|
has and the type spelling does not: the fill counts in i32, because every
|
||||||
|
index in the language is an i32, so a dimension that does not fit one has no
|
||||||
|
loop that could reach its end. *)
|
||||||
|
and array_dims ctx loc (dims : Ast.len list) =
|
||||||
|
List.map
|
||||||
|
(fun d ->
|
||||||
|
let n = array_len ctx.env loc d in
|
||||||
|
if n < 0L || Int64.compare n 2147483647L > 0 then
|
||||||
|
fail loc
|
||||||
|
"%Ld is not a dimension a fill can count to: an index in this \
|
||||||
|
language is an i32, and so is the loop that writes the elements"
|
||||||
|
n;
|
||||||
|
n)
|
||||||
|
dims
|
||||||
|
|
||||||
|
(* [r c] and an element type make [r [c T]], outermost first. *)
|
||||||
|
and array_of_dims ns elem =
|
||||||
|
List.fold_right (fun n t -> Types.Array (n, t)) ns elem
|
||||||
|
|
||||||
|
(* The element type an annotation asks for, peeled one [Array] per dimension.
|
||||||
|
[None] where the annotation is not an array of at least this rank: the
|
||||||
|
mismatch is then [expect]'s to report against the whole type, which is the
|
||||||
|
message that names both shapes rather than one of their leaves. *)
|
||||||
|
and array_elem_want rank want =
|
||||||
|
if rank = 0 then want
|
||||||
|
else
|
||||||
|
match want with
|
||||||
|
| Some (Types.Array (_, t)) -> array_elem_want (rank - 1) (Some t)
|
||||||
|
| _ -> None
|
||||||
|
|
||||||
|
(* The shared lowering. [pre] is bound before any loop runs — that is what
|
||||||
|
"evaluated once" means — and [element] is handed the index locals, in
|
||||||
|
dimension order, to build the value one element takes. *)
|
||||||
|
and array_build ctx loc ns elem ~pre ~element =
|
||||||
|
let aty = array_of_dims ns elem in
|
||||||
|
let arr = fresh_slot ctx aty in
|
||||||
|
let arrv = mk loc aty (Tast.Local arr) in
|
||||||
|
let islots = List.map (fun _ -> fresh_slot ctx index_ty) ns in
|
||||||
|
let ivals = List.map (fun s -> mk loc index_ty (Tast.Local s)) islots in
|
||||||
|
let zero = mk loc index_ty (Tast.Int (0L, Types.I32)) in
|
||||||
|
let one = mk loc index_ty (Tast.Int (1L, Types.I32)) in
|
||||||
|
let store =
|
||||||
|
mk loc Types.Unit (Tast.Set (Tast.Pindex (arrv, ivals), element ivals))
|
||||||
|
in
|
||||||
|
(* One [Let] and one [While] per dimension, the first dimension outermost.
|
||||||
|
The counter is bound *inside* the enclosing loop's body so that it is
|
||||||
|
re-zeroed on every pass of it, and the increment is the latch for the
|
||||||
|
reason [check_dotimes] gives. These loops carry no [break] and no
|
||||||
|
[continue], which is the condition [tast.ml] puts on a [While] the
|
||||||
|
checker invents. *)
|
||||||
|
let rec nest ns islots =
|
||||||
|
match ns, islots with
|
||||||
|
| [], [] -> store
|
||||||
|
| n :: ns, i :: islots ->
|
||||||
|
let iv = mk loc index_ty (Tast.Local i) in
|
||||||
|
let limit = mk loc index_ty (Tast.Int (n, Types.I32)) in
|
||||||
|
let cond = mk loc Types.Bool (Tast.Prim (Tast.Lt, [ iv; limit ])) in
|
||||||
|
let step =
|
||||||
|
mk loc Types.Unit
|
||||||
|
(Tast.Set (Tast.Plocal i,
|
||||||
|
mk loc index_ty (Tast.Prim (Tast.Add, [ iv; one ]))))
|
||||||
|
in
|
||||||
|
let loop =
|
||||||
|
mk loc Types.Unit (Tast.While (cond, [ nest ns islots ], [ step ]))
|
||||||
|
in
|
||||||
|
mk loc Types.Unit (Tast.Let ([ (i, zero) ], [ loop ]))
|
||||||
|
| _, _ -> fail loc "array fill: one counter per dimension"
|
||||||
|
in
|
||||||
|
mk loc aty
|
||||||
|
(Tast.Let (pre @ [ (arr, mk loc aty (Tast.Zero aty)) ],
|
||||||
|
[ nest ns islots; arrv ]))
|
||||||
|
|
||||||
|
and check_array_fill ctx ~want loc dims v =
|
||||||
|
let ns = array_dims ctx loc dims in
|
||||||
|
let elem_want = array_elem_want (List.length ns) want in
|
||||||
|
(* The annotation's element type is the [want] the value is checked against,
|
||||||
|
so a disagreement is reported at the value, in the ordinary
|
||||||
|
expected/found words, rather than as a whole-array mismatch a line up. *)
|
||||||
|
let v = check ctx ?want:elem_want v in
|
||||||
|
let elem = match elem_want with Some t -> t | None -> v.Tast.ty in
|
||||||
|
(* [resolve] refuses a fixed array of function values, because the elements
|
||||||
|
this form does not write would be zeroed and a zeroed function value is a
|
||||||
|
null pointer. The type is built here without going through [resolve], so
|
||||||
|
the same guard has to be asked here. *)
|
||||||
|
no_zeroed_fn loc "a fixed array's element" elem;
|
||||||
|
let vs = fresh_slot ctx elem in
|
||||||
|
let vv = mk loc elem (Tast.Local vs) in
|
||||||
|
expect ctx loc ~want
|
||||||
|
(array_build ctx loc ns elem ~pre:[ (vs, v) ] ~element:(fun _ -> vv))
|
||||||
|
|
||||||
|
and check_array_gen ctx ~want loc dims f =
|
||||||
|
let ns = array_dims ctx loc dims in
|
||||||
|
let rank = List.length ns in
|
||||||
|
let f = check ctx f in
|
||||||
|
let plural n = if n = 1 then "" else "s" in
|
||||||
|
let elem =
|
||||||
|
match f.Tast.ty with
|
||||||
|
| Types.Fn (ps, r) ->
|
||||||
|
let got = List.length ps in
|
||||||
|
if got <> rank then
|
||||||
|
fail f.Tast.loc
|
||||||
|
"this array-gen has %d dimension%s, so its generator is called with \
|
||||||
|
%d index%s — and this one takes %d argument%s"
|
||||||
|
rank (plural rank) rank
|
||||||
|
(if rank = 1 then "" else "es") got (plural got);
|
||||||
|
List.iteri
|
||||||
|
(fun k p ->
|
||||||
|
if not (Types.equal p index_ty) then
|
||||||
|
fail f.Tast.loc
|
||||||
|
"an index is an i32, and this generator's argument %d is %s"
|
||||||
|
(k + 1) (Types.to_string p))
|
||||||
|
ps;
|
||||||
|
r
|
||||||
|
| other ->
|
||||||
|
fail f.Tast.loc
|
||||||
|
"array-gen's second element is a function value, called once per \
|
||||||
|
element with one i32 index per dimension, and this is %s — for one \
|
||||||
|
value repeated, write array-fill"
|
||||||
|
(Types.to_string other)
|
||||||
|
in
|
||||||
|
no_zeroed_fn loc "a fixed array's element" elem;
|
||||||
|
let fs = fresh_slot ctx f.Tast.ty in
|
||||||
|
let fv = mk loc f.Tast.ty (Tast.Local fs) in
|
||||||
|
expect ctx loc ~want
|
||||||
|
(array_build ctx loc ns elem ~pre:[ (fs, f) ]
|
||||||
|
~element:(fun idxs -> mk loc elem (Tast.CallPtr (fv, idxs))))
|
||||||
|
|
||||||
and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
||||||
let s = check ctx scrutinee in
|
let s = check ctx scrutinee in
|
||||||
(* What the arms are alternatives over. An [Option] is a two-case data type
|
(* What the arms are alternatives over. An [Option] is a two-case data type
|
||||||
|
|||||||
32
lib/load.ml
32
lib/load.ml
@ -160,6 +160,15 @@ let qualify_name owned alias bound n =
|
|||||||
|
|
||||||
(* The type names the package itself declares. Only these are rewritten: a
|
(* The type names the package itself declares. Only these are rewritten: a
|
||||||
reference to [i32] or to [Ptr] must survive untouched. *)
|
reference to [i32] or to [Ptr] must survive untouched. *)
|
||||||
|
(* An array length written as a name is an ordinary compile-time constant of
|
||||||
|
the package, so it is qualified like any other reference to one. Shared by
|
||||||
|
the [Tarray] below and by [array-fill]/[array-gen], whose dimensions are the
|
||||||
|
same [len] in expression position. *)
|
||||||
|
let rename_len owned alias (l : Ast.len) : Ast.len =
|
||||||
|
match l with
|
||||||
|
| Ast.Lname n when List.mem n owned -> Ast.Lname (qualify alias n)
|
||||||
|
| l -> l
|
||||||
|
|
||||||
let rec rename_texpr owned alias (t : Ast.texpr) : Ast.texpr =
|
let rec rename_texpr owned alias (t : Ast.texpr) : Ast.texpr =
|
||||||
let k =
|
let k =
|
||||||
match t.Ast.t with
|
match t.Ast.t with
|
||||||
@ -169,12 +178,7 @@ let rec rename_texpr owned alias (t : Ast.texpr) : Ast.texpr =
|
|||||||
(* The length too: [rows] in [[rows [cols u32]]] is an ordinary
|
(* The length too: [rows] in [[rows [cols u32]]] is an ordinary
|
||||||
compile-time constant of the package, not part of the type syntax. *)
|
compile-time constant of the package, not part of the type syntax. *)
|
||||||
| Ast.Tarray (l, e) ->
|
| Ast.Tarray (l, e) ->
|
||||||
let l =
|
Ast.Tarray (rename_len owned alias l, rename_texpr owned alias e)
|
||||||
match l with
|
|
||||||
| Ast.Lname n when List.mem n owned -> Ast.Lname (qualify alias n)
|
|
||||||
| l -> l
|
|
||||||
in
|
|
||||||
Ast.Tarray (l, rename_texpr owned alias e)
|
|
||||||
| Ast.Tmap (k, v) ->
|
| Ast.Tmap (k, v) ->
|
||||||
Ast.Tmap (rename_texpr owned alias k, rename_texpr owned alias v)
|
Ast.Tmap (rename_texpr owned alias k, rename_texpr owned alias v)
|
||||||
| Ast.Tapp (n, args) ->
|
| Ast.Tapp (n, args) ->
|
||||||
@ -279,6 +283,12 @@ let rec rename_expr owned alias bound (e : Ast.expr) : Ast.expr =
|
|||||||
Ast.MapLit (tag, List.map (fun (k, v) -> (go k, go v)) kvs)
|
Ast.MapLit (tag, List.map (fun (k, v) -> (go k, go v)) kvs)
|
||||||
| Ast.Arr items -> Ast.Arr (gos items)
|
| Ast.Arr items -> Ast.Arr (gos items)
|
||||||
| Ast.ArrayOf t -> Ast.ArrayOf (rename_texpr owned alias t)
|
| Ast.ArrayOf t -> Ast.ArrayOf (rename_texpr owned alias t)
|
||||||
|
(* The dimensions too, for the reason [rename_texpr] gives about the one
|
||||||
|
inside [Tarray]: a dimension written as a name is an ordinary
|
||||||
|
compile-time constant of the package and has to be qualified like any
|
||||||
|
other reference to it. *)
|
||||||
|
| Ast.ArrayFill (ds, v) -> Ast.ArrayFill (List.map (rename_len owned alias) ds, go v)
|
||||||
|
| Ast.ArrayGen (ds, v) -> Ast.ArrayGen (List.map (rename_len owned alias) ds, go v)
|
||||||
| Ast.Fn (ps, body) ->
|
| Ast.Fn (ps, body) ->
|
||||||
Ast.Fn (ps, List.map (rename_expr owned alias (ps @ bound)) body)
|
Ast.Fn (ps, List.map (rename_expr owned alias (ps @ bound)) body)
|
||||||
| Ast.Dotimes (l, i, n, body) ->
|
| Ast.Dotimes (l, i, n, body) ->
|
||||||
@ -744,6 +754,16 @@ let rec expr_uses acc (e : Ast.expr) =
|
|||||||
| Ast.MapLit (_, kvs) -> List.iter (fun (k, v) -> go k; go v) kvs
|
| Ast.MapLit (_, kvs) -> List.iter (fun (k, v) -> go k; go v) kvs
|
||||||
| Ast.Arr items -> gos items
|
| Ast.Arr items -> gos items
|
||||||
| Ast.ArrayOf t -> texpr_uses acc t
|
| Ast.ArrayOf t -> texpr_uses acc t
|
||||||
|
(* A dimension written as a name is a use of that constant, exactly as it is
|
||||||
|
inside [Tarray]. *)
|
||||||
|
| Ast.ArrayFill (ds, v) | Ast.ArrayGen (ds, v) ->
|
||||||
|
List.iter
|
||||||
|
(fun (l : Ast.len) ->
|
||||||
|
match l with
|
||||||
|
| Ast.Lname n -> acc := (n, e.Ast.loc) :: !acc
|
||||||
|
| Ast.Lint _ -> ())
|
||||||
|
ds;
|
||||||
|
go v
|
||||||
| Ast.Fn (_, body) -> gos body
|
| Ast.Fn (_, body) -> gos body
|
||||||
| Ast.Dotimes (_, _, n, body) -> go n; gos body
|
| Ast.Dotimes (_, _, n, body) -> go n; gos body
|
||||||
| Ast.Defer body -> gos body
|
| Ast.Defer body -> gos body
|
||||||
|
|||||||
30
lib/parse.ml
30
lib/parse.ml
@ -437,6 +437,36 @@ and form f mk (head : Form.t) (args : Form.t list) : Ast.expr =
|
|||||||
"array is (array COUNT TYPE), as in (array 4 rl/Vector2) — a zeroed \
|
"array is (array COUNT TYPE), as in (array 4 rl/Vector2) — a zeroed \
|
||||||
fixed array of COUNT of them")
|
fixed array of COUNT of them")
|
||||||
|
|
||||||
|
(* ── (array-fill [r c] v) and (array-gen [r c] f) ──────────────────
|
||||||
|
The two value-producing array constructors, and the reason they are
|
||||||
|
recognised here rather than reaching Check as ordinary calls: the
|
||||||
|
dimensions are in brackets, and a bracket in expression position is an
|
||||||
|
array literal. [(array-fill [rows cols] 255)] handed through as a call
|
||||||
|
would arrive with an [Arr] of two [Var]s as its first argument, which
|
||||||
|
where [rows] and [cols] are defconsts is a perfectly good two-element
|
||||||
|
array of integers — the wrong reading, and a silent one. Read here, the
|
||||||
|
brackets are [len]s: the same integer-or-constant's-name the [n T] type
|
||||||
|
spelling takes, refused by [len] when they are anything else. *)
|
||||||
|
| Sym (("array-fill" | "array-gen") as which) ->
|
||||||
|
let usage () =
|
||||||
|
fail f
|
||||||
|
"%s is (%s [n ...] %s) — the dimensions in brackets, each an integer \
|
||||||
|
or a compile-time constant's name, and %s"
|
||||||
|
which which
|
||||||
|
(if which = "array-fill" then "value" else "f")
|
||||||
|
(if which = "array-fill" then
|
||||||
|
"the value every element takes"
|
||||||
|
else
|
||||||
|
"a function taking one i32 index per dimension")
|
||||||
|
in
|
||||||
|
(match args with
|
||||||
|
| [ { v = Vec (_ :: _ as ds); _ }; v ] ->
|
||||||
|
let ds = List.map len ds in
|
||||||
|
let v = expr v in
|
||||||
|
mk (if which = "array-fill" then Ast.ArrayFill (ds, v)
|
||||||
|
else Ast.ArrayGen (ds, v))
|
||||||
|
| _ -> usage ())
|
||||||
|
|
||||||
| Sym "match" ->
|
| Sym "match" ->
|
||||||
(match args with
|
(match args with
|
||||||
| scrutinee :: rest -> mk (Ast.Match (expr scrutinee, arms f rest))
|
| scrutinee :: rest -> mk (Ast.Match (expr scrutinee, arms f rest))
|
||||||
|
|||||||
86
test/programs/array-fill.flan
Normal file
86
test/programs/array-fill.flan
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
;;;; (array-fill [r c] v) and (array-gen [r c] f) — a fixed array as a value.
|
||||||
|
;;;;
|
||||||
|
;;;; 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
|
||||||
|
;;;; expression of a declaration. These are expressions, so they compose where
|
||||||
|
;;;; a bracket literal does — including as a defvar's initialiser, which is the
|
||||||
|
;;;; line the note was written about.
|
||||||
|
;;;;
|
||||||
|
;;;; The dimensions are in brackets and are the same compile-time lengths the
|
||||||
|
;;;; [n T] type spelling takes: an integer or a constant's name.
|
||||||
|
|
||||||
|
(defconst rows 3)
|
||||||
|
(defconst cols 4)
|
||||||
|
|
||||||
|
;; The line from the note. A typed declaration with a computed initialiser,
|
||||||
|
;; which is the startup-lifted path a defvar already had.
|
||||||
|
(defvar grid [rows [cols u8]] (array-fill [rows cols] 255))
|
||||||
|
|
||||||
|
;; 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 one [i i32] i32 (* i i))
|
||||||
|
|
||||||
|
;; 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
|
||||||
|
;; fills is 0 1 2 ... in the order the elements are written.
|
||||||
|
(defvar ticks i32)
|
||||||
|
|
||||||
|
(defn tick [r i32 c i32] i32
|
||||||
|
(set ticks (+ ticks 1))
|
||||||
|
(- ticks 1))
|
||||||
|
|
||||||
|
(defn main [] i32
|
||||||
|
;; Rank 1.
|
||||||
|
(let [a (array-fill [5] 7)]
|
||||||
|
(print (at a 0)) (print " ") (print (at a 4)) (println "")) ; 7 7
|
||||||
|
|
||||||
|
;; Rank 2, and the element type is the fill value's.
|
||||||
|
(let [b (array-fill [2 3] (f32 1.5))]
|
||||||
|
(print (at b 1 2)) (println "")) ; 1.5
|
||||||
|
|
||||||
|
;; Rank 3.
|
||||||
|
(let [c (array-fill [2 2 2] -1)]
|
||||||
|
(print (at c 0 0 0)) (print " ") (print (at c 1 1 1)) (println "")) ; -1 -1
|
||||||
|
|
||||||
|
;; A dimension may be a constant's name, exactly as in [rows [cols u8]].
|
||||||
|
(let [d (array-fill [rows cols] 1)]
|
||||||
|
(print (at d 2 3)) (println "")) ; 1
|
||||||
|
|
||||||
|
;; The generator, rank 1: element i is i*i.
|
||||||
|
(let [g (array-gen [5] one)]
|
||||||
|
(print (at g 0)) (print " ") (print (at g 3)) (print " ")
|
||||||
|
(print (at g 4)) (println "")) ; 0 9 16
|
||||||
|
|
||||||
|
;; The generator, rank 2. Element [i][j] is i*100+j, which pins the index
|
||||||
|
;; arguments: the first is the outer index and the second the inner one, and
|
||||||
|
;; a form that passed them the other way round would print 1 and 300 here.
|
||||||
|
(let [h (array-gen [rows cols] cell)]
|
||||||
|
(print (at h 0 0)) (print " ") (print (at h 0 1)) (print " ")
|
||||||
|
(print (at h 1 0)) (print " ") (print (at h 2 3)) (println "")) ; 0 1 100 203
|
||||||
|
|
||||||
|
;; Row-major, pinned. [tick] answers the call number, so the element that
|
||||||
|
;; was written first holds 0 — and with four columns, [1][0] is the fifth.
|
||||||
|
(let [t (array-gen [rows cols] tick)]
|
||||||
|
(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
|
||||||
|
|
||||||
|
;; The defvar from the top: 255 everywhere, read back as an i32 so the
|
||||||
|
;; printed value is the number and not a byte.
|
||||||
|
(print (i32 (at grid 0 0))) (print " ")
|
||||||
|
(print (i32 (at grid 2 3))) (println "") ; 255 255
|
||||||
|
|
||||||
|
;; An array value copies, which is what makes this a value and not a view:
|
||||||
|
;; writing through the copy leaves the global alone.
|
||||||
|
(let [copy grid]
|
||||||
|
(set (at copy 0 0) (u8 1))
|
||||||
|
(print (i32 (at copy 0 0))) (print " ")
|
||||||
|
(print (i32 (at grid 0 0))) (println "")) ; 1 255
|
||||||
|
|
||||||
|
;; A zero dimension is an array with no elements, and the loop that fills it
|
||||||
|
;; runs no passes. Nothing to read, so the claim is that it compiles and the
|
||||||
|
;; program carries on.
|
||||||
|
(let [e (array-fill [0] 9)]
|
||||||
|
(println "empty ok"))
|
||||||
|
|
||||||
|
0)
|
||||||
@ -46,6 +46,14 @@
|
|||||||
;; the line that would count 1, 1, 1, 1.
|
;; the line that would count 1, 1, 1, 1.
|
||||||
(defvar tally 0)
|
(defvar tally 0)
|
||||||
|
|
||||||
|
;; A typed array with a computed initialiser: (array-fill ...) is an
|
||||||
|
;; expression, so it is lifted into the startup function and guarded there
|
||||||
|
;; exactly as [counter]'s call is. If it were not — if a fill re-ran on every
|
||||||
|
;; 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
|
||||||
|
;; have.
|
||||||
|
(defvar 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 [.]
|
||||||
;; is an ordinary symbol constituent. This one is exactly the old spelling of
|
;; is an ordinary symbol constituent. This one is exactly the old spelling of
|
||||||
@ -62,10 +70,15 @@
|
|||||||
(set .init-once.counter (+ .init-once.counter 1))
|
(set .init-once.counter (+ .init-once.counter 1))
|
||||||
(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)))
|
||||||
(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 "")
|
||||||
(print "tally ") (print tally) (println "")
|
(print "tally ") (print tally) (println "")
|
||||||
|
(print "grid ") (print (i32 (at grid 0 0))) (println "")
|
||||||
|
;; The element the run never writes, which says the fill ran at all: 250
|
||||||
|
;; on every run, and 0 if the initialiser had been skipped outright.
|
||||||
|
(print "grid-far ") (print (i32 (at grid 1 2))) (println "")
|
||||||
(print "base ") (print base) (println "")
|
(print "base ") (print base) (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.
|
||||||
|
|||||||
@ -384,6 +384,29 @@ let () =
|
|||||||
(* (array COUNT TYPE). Every line of it is a [let] binding, which is the
|
(* (array COUNT TYPE). Every line of it is a [let] binding, which is the
|
||||||
one position with no type slot and the whole reason the form exists. *)
|
one position with no type slot and the whole reason the form exists. *)
|
||||||
outputs "array constructor" "programs/array-ctor.flan" "4\n0\n7\n9\n4\n";
|
outputs "array constructor" "programs/array-ctor.flan" "4\n0\n7\n9\n4\n";
|
||||||
|
(* (array-fill [r c] v) and (array-gen [r c] f), DISCUSS.org's
|
||||||
|
value-producing array constructor. Three of these lines are load-bearing
|
||||||
|
beyond "it prints something". "0 1 100 203" pins the index arguments:
|
||||||
|
element [i][j] is i*100+j, so a generator handed its indices the other
|
||||||
|
way round prints 1 and 300 there. "0 1 4 11" pins the *order*: the
|
||||||
|
generator answers the call number, so with four columns the element at
|
||||||
|
[1][0] being 4 is row-major, written as a promise rather than as
|
||||||
|
whatever the nesting happened to do. And "1 255" is the value semantics
|
||||||
|
— a copy written through leaves the global alone.
|
||||||
|
|
||||||
|
Three rows, because the fill is a loop over a slot rather than an
|
||||||
|
aggregate literal and each backend builds that loop itself: the -O0 row
|
||||||
|
is the one where nothing has been folded away, and the x86 row is the
|
||||||
|
dev backend that emits the stores by hand. *)
|
||||||
|
(let fill_out =
|
||||||
|
"7 7\n1.5\n-1 -1\n1\n0 9 16\n0 1 100 203\n0 1 4 11\n255 255\n1 255\n\
|
||||||
|
empty ok\n"
|
||||||
|
in
|
||||||
|
outputs "array-fill and array-gen" "programs/array-fill.flan" fill_out;
|
||||||
|
outputs ~opt:"-O0" "array-fill and array-gen, -O0"
|
||||||
|
"programs/array-fill.flan" fill_out;
|
||||||
|
outputs ~x86:true "array-fill and array-gen, --x86"
|
||||||
|
"programs/array-fill.flan" fill_out);
|
||||||
(* break and continue. The dotimes/continue case is the one that fails by
|
(* break and continue. The dotimes/continue case is the one that fails by
|
||||||
hanging rather than by printing the wrong thing — the step is the loop's
|
hanging rather than by printing the wrong thing — the step is the loop's
|
||||||
latch, and folded onto the body a continue would jump past it — so the
|
latch, and folded onto the body a continue would jump past it — so the
|
||||||
|
|||||||
@ -4742,10 +4742,11 @@ let () =
|
|||||||
initialiser every *other* time would pass a single re-run.
|
initialiser every *other* time would pass a single re-run.
|
||||||
|
|
||||||
[programs/dev-rerun.flan] prints one line per case per run, and the
|
[programs/dev-rerun.flan] prints one line per case per run, and the
|
||||||
whole assertion is the fourth run's five lines: [counter] computed and
|
whole assertion is the fourth run's lines: [counter] computed and
|
||||||
incremented four times, [zeroed] uncomputed and incremented four times,
|
incremented four times, [zeroed] uncomputed and incremented four times,
|
||||||
a computed dyn map whose contents were mutated four times, a dyn global
|
a computed dyn map whose contents were mutated four times, a dyn global
|
||||||
written the three-element way and incremented four times, and a
|
written the three-element way and incremented four times, a typed array
|
||||||
|
filled once by a computed (array-fill ...) and written four times, and a
|
||||||
[defconst] that no run can have changed. *)
|
[defconst] that no run can have changed. *)
|
||||||
let rsock = tmp "rerun.sock" and rout = tmp "rerun.out" in
|
let rsock = tmp "rerun.sock" and rout = tmp "rerun.out" in
|
||||||
(try Sys.remove rsock with Sys_error _ -> ());
|
(try Sys.remove rsock with Sys_error _ -> ());
|
||||||
@ -4802,6 +4803,14 @@ let () =
|
|||||||
guard because it *is* the same declaration by the time anything
|
guard because it *is* the same declaration by the time anything
|
||||||
downstream sees it. *)
|
downstream sees it. *)
|
||||||
"tally 4";
|
"tally 4";
|
||||||
|
(* A typed array with a computed initialiser — (array-fill ...),
|
||||||
|
which is an expression and so is lifted into the startup function
|
||||||
|
like any other computed one. 250 filled once and incremented four
|
||||||
|
times; a fill that re-ran would print 251 every run. *)
|
||||||
|
"grid 254";
|
||||||
|
(* And the element no run writes, which separates "the guard held"
|
||||||
|
from "the initialiser never ran": 250, not 0. *)
|
||||||
|
"grid-far 250";
|
||||||
(* And a [defconst], which no run can have changed. *)
|
(* And a [defconst], which no run can have changed. *)
|
||||||
"base 40" ]
|
"base 40" ]
|
||||||
in
|
in
|
||||||
|
|||||||
@ -697,6 +697,26 @@ let () =
|
|||||||
~needle:"expected a type";
|
~needle:"expected a type";
|
||||||
parse_rejects "array with a non-constant count" "(defn f [] () (array (+ 1 1) f32))"
|
parse_rejects "array with a non-constant count" "(defn f [] () (array (+ 1 1) f32))"
|
||||||
~needle:"an array length is an integer or a constant's name";
|
~needle:"an array length is an integer or a constant's name";
|
||||||
|
(* The dimensions are read in [Parse], and this is the whole reason: without
|
||||||
|
the bracket being read here it would arrive as an ordinary argument, and
|
||||||
|
an [Arr] of two names is a perfectly good array literal wherever those
|
||||||
|
names are constants. So the bracket is required and its contents are
|
||||||
|
[len]s, refused by the same message [4 f32] gets. *)
|
||||||
|
parse_rejects "array-fill wants its dimensions in brackets"
|
||||||
|
"(defn f [] () (array-fill 3 0))"
|
||||||
|
~needle:"array-fill is (array-fill [n ...] value)";
|
||||||
|
parse_rejects "array-fill wants a fill value"
|
||||||
|
"(defn f [] () (array-fill [3]))"
|
||||||
|
~needle:"array-fill is (array-fill [n ...] value)";
|
||||||
|
parse_rejects "array-fill has no rank zero"
|
||||||
|
"(defn f [] () (array-fill [] 0))"
|
||||||
|
~needle:"array-fill is (array-fill [n ...] value)";
|
||||||
|
parse_rejects "an array-fill dimension is a length, not an expression"
|
||||||
|
"(defn f [] () (array-fill [(+ 1 1)] 0))"
|
||||||
|
~needle:"an array length is an integer or a constant's name";
|
||||||
|
parse_rejects "array-gen says its own name in its usage"
|
||||||
|
"(defn f [] () (array-gen 3 g))"
|
||||||
|
~needle:"array-gen is (array-gen [n ...] f)";
|
||||||
|
|
||||||
(* ── The corpus parses ─────────────────────────────────────────── *)
|
(* ── The corpus parses ─────────────────────────────────────────── *)
|
||||||
List.iter
|
List.iter
|
||||||
@ -897,6 +917,16 @@ let () =
|
|||||||
infers "array constructor" "(array 4 f32)" "[4 f32]";
|
infers "array constructor" "(array 4 f32)" "[4 f32]";
|
||||||
infers "array of a struct" "(array 2 i32)" "[2 i32]";
|
infers "array of a struct" "(array 2 i32)" "[2 i32]";
|
||||||
infers "array of an array" "(array 2 [3 u8])" "[2 [3 u8]]";
|
infers "array of an array" "(array 2 [3 u8])" "[2 [3 u8]]";
|
||||||
|
(* (array-fill [r c] v): the same type at any rank, with the element type
|
||||||
|
taken from the fill value. Unlike [array] above this one is a value and
|
||||||
|
not a zero, which is what lets it be a defvar's initialiser — see
|
||||||
|
programs/array-fill.flan for what it puts in the elements. *)
|
||||||
|
infers "array-fill, rank 1" "(array-fill [5] 7)" "[5 i32]";
|
||||||
|
infers "array-fill, rank 2" "(array-fill [2 3] 0.5)" "[2 [3 f64]]";
|
||||||
|
infers "array-fill, rank 3" "(array-fill [2 3 4] true)" "[2 [3 [4 bool]]]";
|
||||||
|
(* A zero dimension is a legal array with no elements, and the fill loop
|
||||||
|
runs no passes over it. *)
|
||||||
|
infers "array-fill of nothing" "(array-fill [0] 1)" "[0 i32]";
|
||||||
infers "bytes of a string" "(bytes \"hi\")" "[u8]";
|
infers "bytes of a string" "(bytes \"hi\")" "[u8]";
|
||||||
infers "len is i32" "(len (bytes \"hi\"))" "i32";
|
infers "len is i32" "(len (bytes \"hi\"))" "i32";
|
||||||
infers "slice of a slice" "(slice (bytes \"hi\") 0 1)" "[u8]";
|
infers "slice of a slice" "(slice (bytes \"hi\") 0 1)" "[u8]";
|
||||||
@ -2157,6 +2187,88 @@ let () =
|
|||||||
"(defvar score i64 1) (defvar total scor) (defn f [] ())"
|
"(defvar score i64 1) (defvar total scor) (defn f [] ())"
|
||||||
~needle:"Nothing named scor is declared as either — did you mean score?";
|
~needle:"Nothing named scor is declared as either — did you mean score?";
|
||||||
|
|
||||||
|
(* ── (array-fill ...) and (array-gen ...) as initialisers ──────────
|
||||||
|
DISCUSS.org's "need a value-producing array constructor" wanted
|
||||||
|
[(defvar grid (array-fill [rows cols] 255))] — the grid filled as part of
|
||||||
|
its declaration rather than in a mutation step after it. What falls out of
|
||||||
|
the rules already settled, and it is not a carve-out either way:
|
||||||
|
|
||||||
|
The four-element spelling is the one that works. It is a typed global with
|
||||||
|
a computed initialiser, which is the startup-lifted path a defvar already
|
||||||
|
had, and the value it stores is an ordinary fixed array.
|
||||||
|
|
||||||
|
The three-element spelling does not mean this, and could not. A defvar
|
||||||
|
whose third element is not a type is a *dyn* global by the 2026-09-20
|
||||||
|
rule, and a typed fixed array crosses into dyn only as a view of storage
|
||||||
|
that outlives the view. A freshly built array is a temporary, so the view
|
||||||
|
lifetime guard refuses it — and where the elements are an array rather
|
||||||
|
than one of the three scalar widths a view carries, the element refusal
|
||||||
|
gets there first. Both refusals are the ones any other temporary gets;
|
||||||
|
neither was written for this form. *)
|
||||||
|
defvar_reading "a typed array-fill global is computed, not zeroed"
|
||||||
|
"(defconst rows 2) (defconst cols 3)\n\
|
||||||
|
(defvar grid [rows [cols u8]] (array-fill [rows cols] 255))\n\
|
||||||
|
(defn f [] u8 (at grid 0 0))"
|
||||||
|
"grid" ~ty:"[2 [3 u8]]" ~zeroed:false;
|
||||||
|
rejects_check "a three-element array-fill defvar is the dyn reading"
|
||||||
|
"(defvar xs (array-fill [3] (i64 1))) (defn f [] ())"
|
||||||
|
~needle:"does not cross into dyn as a view here";
|
||||||
|
rejects_check "and its element type is asked about first"
|
||||||
|
"(defvar grid (array-fill [2 3] 255)) (defn f [] ())"
|
||||||
|
~needle:"does not cross into dyn yet";
|
||||||
|
(* A defconst is not a second path to it: its value is what the linker
|
||||||
|
writes into the image, and a fill is a loop. *)
|
||||||
|
rejects_check "array-fill is not a constant's value"
|
||||||
|
"(defconst g [2 u8] (array-fill [2] (u8 1))) (defn f [] ())"
|
||||||
|
~needle:"a constant's value must be a compile-time constant";
|
||||||
|
|
||||||
|
(* The element type the annotation asks for is the one the fill value is
|
||||||
|
checked against, so the disagreement is reported at the value. *)
|
||||||
|
rejects_check "the annotation and the fill value must agree"
|
||||||
|
"(defvar g [2 [3 u8]] (array-fill [2 3] (f32 1.0))) (defn f [] ())"
|
||||||
|
~needle:"expected u8, found f32";
|
||||||
|
rejects_check "the annotation's shape has to be the fill's shape"
|
||||||
|
"(defvar g [2 u8] (array-fill [3] (u8 1))) (defn f [] ())"
|
||||||
|
~needle:"expected [2 u8], found [3 u8]";
|
||||||
|
(* A dimension is the same compile-time length [n T] takes, and a local is
|
||||||
|
not one. The refusal is [array_len]'s own, which is what "the same rule"
|
||||||
|
means here. *)
|
||||||
|
rejects_check "a dimension is a compile-time constant"
|
||||||
|
"(defn f [] i32 (let [n 3 a (array-fill [n] 0)] 0))"
|
||||||
|
~needle:"is not a compile-time integer constant";
|
||||||
|
(* The one condition this form has that the [n T] type spelling does not:
|
||||||
|
the fill counts in i32 like every other index, so a dimension no i32 can
|
||||||
|
reach has no loop that could end. Written as a literal, because a
|
||||||
|
[defconst] that big is refused as an i32 constant before it is ever a
|
||||||
|
dimension. *)
|
||||||
|
rejects_check "a dimension has to fit an i32 index"
|
||||||
|
"(defn f [] i32 (let [a (array-fill [3000000000] 0)] 0))"
|
||||||
|
~needle:"is not a dimension a fill can count to";
|
||||||
|
|
||||||
|
(* The generator. Its type decides the element type, its arity has to be the
|
||||||
|
rank, and its arguments are indices. *)
|
||||||
|
accepts "array-gen takes a named function"
|
||||||
|
"(defn cell [r i32 c i32] i32 (+ (* r 100) c))\n\
|
||||||
|
(defvar grid [2 [3 i32]] (array-gen [2 3] cell))\n\
|
||||||
|
(defn f [] i32 (at grid 1 2))";
|
||||||
|
rejects_check "array-gen's second element is a function"
|
||||||
|
"(defn f [] i32 (let [a (array-gen [3] 7)] 0))"
|
||||||
|
~needle:"array-gen's second element is a function value";
|
||||||
|
rejects_check "the generator takes one argument per dimension"
|
||||||
|
"(defn g [i i32 j i32] i32 0) (defn f [] i32 (let [a (array-gen [3] g)] 0))"
|
||||||
|
~needle:"this array-gen has 1 dimension, so its generator is called with \
|
||||||
|
1 index — and this one takes 2 arguments";
|
||||||
|
rejects_check "the generator's arguments are i32 indices"
|
||||||
|
"(defn g [i i64] i32 0) (defn f [] i32 (let [a (array-gen [3] g)] 0))"
|
||||||
|
~needle:"an index is an i32, and this generator's argument 1 is i64";
|
||||||
|
(* [resolve] refuses a fixed array of function values — a zeroed one would
|
||||||
|
be a null pointer — and the type these forms build never goes through
|
||||||
|
[resolve], so the guard is asked again where the type is built. *)
|
||||||
|
rejects_check "an array of function values is refused here too"
|
||||||
|
"(defn h [x i32] i32 x) (defn g [i i32] (Fn [i32] i32) h)\n\
|
||||||
|
(defn f [] i32 (let [a (array-gen [2] g)] 0))"
|
||||||
|
~needle:"a fixed array's element cannot be (Fn [i32] i32)";
|
||||||
|
|
||||||
(* ── Computed global initialisers ──────────────────────────────────
|
(* ── Computed global initialisers ──────────────────────────────────
|
||||||
The order they run in is the compiler's to choose, so a global written
|
The order they run in is the compiler's to choose, so a global written
|
||||||
above the one it reads is fine... *)
|
above the one it reads is fine... *)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user