A let binding has no type slot, so (array 4 T) is how you say it
[4 T] is the type syntax and is unchanged; it already works in a defvar, a parameter, a field and a return. A let binding is the one position with no type slot, and there the brackets are an array literal of two elements whose second is a type name — which came back as "unknown name rl/Vector2" and cost 32 hand-written Vector2s in one raylib example. (array COUNT TYPE) is a parser form rather than a builtin call, because the second argument is a type and the parser's callers have none. Parse assembles the Tarray itself, so the count takes a constant's name for free and a value in the type position is refused by the type reader's own message. The checker resolves it to Tast.Zero — no new backend node and no new type. (zeroed [4 T]) was proposed first and rejected: the parser can tell, a person cannot. zeroed keeps its job of being inferred; array is the one that is told.
This commit is contained in:
parent
49bb9b9c42
commit
9f4a59c56f
24
BUILT.md
24
BUILT.md
@ -2435,6 +2435,30 @@ reason `-linkall` is not optional. Say plainly what that coverage is not: nothin
|
||||
before this landed, so `macro-unless.flan` is a test written after the feature. The corpus written before it is
|
||||
`sand.flan` and `web/examples/control.flan`, and both compile unchanged.
|
||||
|
||||
## `(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
|
||||
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
|
||||
array *literal* of two elements, whose second element is a type name nothing declares. So `(let [pts [4 rl/Vector2]]
|
||||
...)` failed with *unknown name rl/Vector2*, which describes the symptom and not the mistake. It cost 32 hand-written
|
||||
`Vector2`s in one raylib example.
|
||||
|
||||
`(array COUNT TYPE)` is the answer: a zeroed fixed array, told its count and its element type as plain arguments.
|
||||
`(array 4 rl/Vector2)` and the type `[4 rl/Vector2]` denote the same type, so the constructor is assignable to a
|
||||
declaration written the other way and either spelling can be the parameter — `array-ctor.flan` asserts exactly that.
|
||||
|
||||
It is a parser form and not a builtin call, because the second argument is a *type* and there are no types in the
|
||||
parser's callers. `Parse` assembles the whole `Tarray (len COUNT, TYPE)` itself, which is why the count takes a
|
||||
constant's name for free — `len` is the same function `[n T]` goes through — and why a non-type second argument is
|
||||
refused by the type reader's own message rather than as an unknown name. The checker resolves it and hands back
|
||||
`Tast.Zero`, the same node a declaration with no initialiser gets. There is no new backend node and no new type.
|
||||
|
||||
**`(zeroed)` was the first proposal and was rejected on how it reads.** `(zeroed [4 rl/Vector2])` is unambiguous to the
|
||||
*parser* — a bracket in argument position could be a type there — but to a person it still looks like a two-element
|
||||
vector, which is the exact confusion being fixed. `zeroed` keeps its existing job: the empty value of whatever type the
|
||||
destination wants, inferred and never written. `array` is the one that is told.
|
||||
|
||||
## `defer` may be written in a `let`
|
||||
|
||||
The whole of this project's resource-cleanup answer, and NEXT.md records `drop` and a `with-cleanup` form as both
|
||||
|
||||
2
NEXT.md
2
NEXT.md
@ -685,7 +685,7 @@ debug tracking allocator, which is the leak safety net and a good candidate when
|
||||
|
||||
## Decided in discussion — the array constructor and the module system
|
||||
|
||||
**`(array 4 rl/Vector2)` makes a fixed array; `[4 T]` stays the type syntax.** The problem this solves: a `let`
|
||||
**`(array 4 rl/Vector2)` makes a fixed array; `[4 T]` stays the type syntax. Built** — see BUILT.md, "`(array 4 rl/Vector2)`, and the one position with no type slot". The problem it solved: a `let`
|
||||
binding takes no type, so `(let [pts [4 rl/Vector2]] ...)` reads `[4 rl/Vector2]` as a two-element array *literal* and
|
||||
fails with *unknown name rl/Vector2*. It cost 32 hand-written `Vector2`s in one raylib example.
|
||||
|
||||
|
||||
@ -48,6 +48,13 @@ and expr_kind =
|
||||
| Match of expr * arm list
|
||||
| Struct of string * (string * expr) list (* (Cursor {.src s}) *)
|
||||
| Arr of expr list (* [0xE6B800FF ...] — a fixed array value *)
|
||||
(* (array 4 rl/Vector2) — a zeroed fixed array, given its count and its
|
||||
element type. [n T] is the ordinary *type* syntax and already works
|
||||
everywhere a type is expected; a [let] binding is the one position with no
|
||||
type slot, so there [4 rl/Vector2] reads as a two-element [Arr] literal and
|
||||
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. *)
|
||||
| ArrayOf of texpr (* the whole array type, built by Parse *)
|
||||
(* 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 *)
|
||||
| Dotimes of string * expr * expr list (* (dotimes [i n] ...) *)
|
||||
|
||||
@ -920,6 +920,12 @@ let rec check ctx ?want (e : Ast.expr) : Tast.expr =
|
||||
expect loc ~want (mk loc fty (Tast.Field (target, i))))
|
||||
| Ast.Struct (name, kvs) -> check_struct ctx ~want loc name kvs
|
||||
| Ast.Arr items -> check_arr ctx ~want loc items
|
||||
(* (array 4 rl/Vector2). Parse already assembled the whole array type, so
|
||||
there is nothing to infer: resolve it and hand back its all-bytes-zero
|
||||
value, which is what a declared array with no initialiser gets. *)
|
||||
| Ast.ArrayOf t ->
|
||||
let ty = resolve ctx.env t in
|
||||
expect loc ~want (mk loc ty (Tast.Zero ty))
|
||||
| Ast.Match (scrutinee, arms) -> check_match ctx ?want loc scrutinee arms
|
||||
| Ast.Call (head, args) -> check_call ctx ~want loc head args
|
||||
| Ast.Unwrap (Ast.Usome, v) ->
|
||||
|
||||
@ -201,6 +201,7 @@ let rec rename_expr owned alias bound (e : Ast.expr) : Ast.expr =
|
||||
| Ast.Struct (n, kvs) ->
|
||||
Ast.Struct (name n, List.map (fun (k, v) -> (k, go v)) kvs)
|
||||
| Ast.Arr items -> Ast.Arr (gos items)
|
||||
| Ast.ArrayOf t -> Ast.ArrayOf (rename_texpr owned alias t)
|
||||
| Ast.Fn (ps, body) ->
|
||||
Ast.Fn (ps, List.map (rename_expr owned alias (ps @ bound)) body)
|
||||
| Ast.Dotimes (i, n, body) ->
|
||||
@ -382,6 +383,7 @@ let rec expr_uses acc (e : Ast.expr) =
|
||||
acc := (n, e.Ast.loc) :: !acc;
|
||||
List.iter (fun (_, v) -> go v) kvs
|
||||
| Ast.Arr items -> gos items
|
||||
| Ast.ArrayOf t -> texpr_uses acc t
|
||||
| Ast.Fn (_, body) -> gos body
|
||||
| Ast.Dotimes (_, n, body) -> go n; gos body
|
||||
| Ast.Defer body -> gos body
|
||||
|
||||
18
lib/parse.ml
18
lib/parse.ml
@ -177,6 +177,24 @@ and form f mk (head : Form.t) (args : Form.t list) : Ast.expr =
|
||||
| [ target; value ] -> mk (Ast.Set (place target, expr value))
|
||||
| _ -> fail f "set is (set place value)")
|
||||
|
||||
(* ── (array 4 rl/Vector2) ───────────────────────────────────────────
|
||||
A zeroed fixed array, told its count and its element type. The type
|
||||
spelling [4 rl/Vector2] is unchanged and still works everywhere a type is
|
||||
expected; what it cannot do is appear in a [let] binding, which has no
|
||||
type slot, because there the brackets are an array *literal* of two
|
||||
elements and the second of them is a name nothing declares. So the count
|
||||
and the type arrive as plain arguments and Parse assembles the type
|
||||
itself. [(zeroed)] keeps its own job — the empty value of whatever the
|
||||
destination wants — and this is the one that is told. *)
|
||||
| Sym "array" ->
|
||||
(match args with
|
||||
| [ n; t ] ->
|
||||
mk (Ast.ArrayOf { Ast.t = Ast.Tarray (len n, texpr t); tloc = f.loc })
|
||||
| _ ->
|
||||
fail f
|
||||
"array is (array COUNT TYPE), as in (array 4 rl/Vector2) — a zeroed \
|
||||
fixed array of COUNT of them")
|
||||
|
||||
| Sym "match" ->
|
||||
(match args with
|
||||
| scrutinee :: rest -> mk (Ast.Match (expr scrutinee, arms f rest))
|
||||
|
||||
43
test/programs/array-ctor.flan
Normal file
43
test/programs/array-ctor.flan
Normal file
@ -0,0 +1,43 @@
|
||||
;;;; (array COUNT TYPE) — the zeroed fixed array a [let] binding could not ask
|
||||
;;;; for. A let has no type slot, so [4 V2] there is an array *literal* of two
|
||||
;;;; elements and the second of them is a type name, which is an unknown name
|
||||
;;;; and not a helpful error. The type spelling is untouched: `points` below is
|
||||
;;;; still declared [3 V2], and the two are the same type, which is the point —
|
||||
;;;; the constructor is assignable to the declaration.
|
||||
(defstruct V2 [x f32 y f32])
|
||||
|
||||
(defconst n 3)
|
||||
(defvar points [3 V2])
|
||||
|
||||
(defn sumx [ps [3 V2]] i32
|
||||
(let [t (f32 0.0)]
|
||||
(dotimes [i 3]
|
||||
(set t (+ t (.x (at ps i)))))
|
||||
(i32 t)))
|
||||
|
||||
(defn main [] i32
|
||||
;; The case that cost 32 hand-written Vector2s: a local array of structs.
|
||||
(let [pts (array 3 V2)]
|
||||
(set (.x (at pts 0)) 1.5)
|
||||
(set (.x (at pts 2)) 2.5)
|
||||
(print (sumx pts)) (println "")) ; 4
|
||||
|
||||
;; Zeroed, not uninitialised: every element reads as the all-zero value.
|
||||
(let [z (array 4 i32)]
|
||||
(print (at z 3)) (println "")) ; 0
|
||||
|
||||
;; The count takes a constant's name, exactly as [n V2] does.
|
||||
(let [c (array n V2)]
|
||||
(set (.y (at c 1)) 7.0)
|
||||
(print (i32 (.y (at c 1)))) (println "")) ; 7
|
||||
|
||||
;; Element types nest, and the result is assignable to a declaration written
|
||||
;; the other way round — same type, two spellings.
|
||||
(let [g (array 2 [2 i32])]
|
||||
(set (at (at g 1) 1) 9)
|
||||
(print (at (at g 1) 1)) (println "")) ; 9
|
||||
|
||||
(set points (array 3 V2))
|
||||
(set (.x (at points 0)) 4.0)
|
||||
(print (sumx points)) (println "") ; 4
|
||||
0)
|
||||
@ -117,6 +117,9 @@ let () =
|
||||
outputs "value semantics" "programs/values.flan" values_out;
|
||||
outputs "machine surface" "programs/machine.flan" machine_out;
|
||||
outputs "unit main exits 0" "programs/unit-main.flan" "ok\n";
|
||||
(* (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. *)
|
||||
outputs "array constructor" "programs/array-ctor.flan" "4\n0\n7\n9\n4\n";
|
||||
(* The prelude's slice algorithms. Every assertion here is over an input a
|
||||
wrong implementation fails: unsorted with duplicates, negatives and an
|
||||
odd length; a reverse-sorted slice; and a sort of a subslice whose
|
||||
|
||||
@ -462,6 +462,12 @@ let () =
|
||||
parse_rejects "odd field pairs" "(defstruct S [a])";
|
||||
parse_rejects "cond without body" "(cond a)";
|
||||
parse_rejects "unknown top form" "(nope x)";
|
||||
parse_rejects "array with no type" "(defn f [] (array 4))"
|
||||
~needle:"array is (array COUNT TYPE)";
|
||||
parse_rejects "array given a value, not a type" "(defn f [] (array 4 5))"
|
||||
~needle:"expected a type";
|
||||
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";
|
||||
|
||||
(* ── The corpus parses ─────────────────────────────────────────── *)
|
||||
List.iter
|
||||
@ -612,6 +618,14 @@ let () =
|
||||
infers "cast" "(f64 3)" "f64";
|
||||
infers "array literal" "[1 2 3]" "[3 i32]";
|
||||
infers "nested array" "[[1 2] [3 4]]" "[2 [2 i32]]";
|
||||
(* (array COUNT TYPE): the constructor a [let] binding needs, because a let
|
||||
has no type slot and [4 P] there is a two-element literal whose second
|
||||
element is a name nothing declares. The type spelling is unchanged — the
|
||||
two [infers] above still hold — and this is the position that had no way
|
||||
to say it. *)
|
||||
infers "array constructor" "(array 4 f32)" "[4 f32]";
|
||||
infers "array of a struct" "(array 2 i32)" "[2 i32]";
|
||||
infers "array of an array" "(array 2 [3 u8])" "[2 [3 u8]]";
|
||||
infers "bytes of a string" "(bytes \"hi\")" "[u8]";
|
||||
infers "len is i32" "(len (bytes \"hi\"))" "i32";
|
||||
infers "slice of a slice" "(slice (bytes \"hi\") 0 1)" "[u8]";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user