Dyn and typed containers print without a space after the bracket, and a value that cannot cross into dyn gets a fix that compiles
This commit is contained in:
commit
41273c9d22
3
TODO.org
3
TODO.org
@ -324,9 +324,6 @@ on its own.
|
|||||||
Decided 2026-09-25: a let whose name no later statement of its block mentions prints
|
Decided 2026-09-25: a let whose name no later statement of its block mentions prints
|
||||||
flat, not as a nested block; a one-argument and/or prints as its argument.
|
flat, not as a nested block; a one-argument and/or prints as its argument.
|
||||||
|
|
||||||
** NEXT A dyn vector prints as [1 2 3], a map as {:a 1}
|
|
||||||
Decided 2026-09-25: no space after the opening bracket, in every renderer.
|
|
||||||
|
|
||||||
** WAIT ML-style patterns
|
** WAIT ML-style patterns
|
||||||
Held 2026-09-25 as a future direction, like the JS backend: nested destructuring,
|
Held 2026-09-25 as a future direction, like the JS backend: nested destructuring,
|
||||||
guards, or-patterns, literals at any depth, exhaustiveness over the nesting.
|
guards, or-patterns, literals at any depth, exhaustiveness over the nesting.
|
||||||
|
|||||||
@ -2211,10 +2211,10 @@ allocator the language does not have.
|
|||||||
big 18446744073709551615
|
big 18446744073709551615
|
||||||
col :blue
|
col :blue
|
||||||
(.pos b) (V {.x 1.5 .y 0})
|
(.pos b) (V {.x 1.5 .y 0})
|
||||||
b (Blob {.id 7 .name "sandy \"quoted\"" .pos (V {.x 1.5 .y 0}) .tags [ 0 42 0]})
|
b (Blob {.id 7 .name "sandy \"quoted\"" .pos (V {.x 1.5 .y 0}) .tags [0 42 0]})
|
||||||
(slice (.tags b) 0 3) [ 0 42 0]
|
(slice (.tags b) 0 3) [0 42 0]
|
||||||
(rl/get-color 0x11223344) (rl/Color {.r 17 .g 34 .b 51 .a 68})
|
(rl/get-color 0x11223344) (rl/Color {.r 17 .g 34 .b 51 .a 68})
|
||||||
sim/grid [ [ 0 0 0 0 0 0 0 0 ...] [ 0 ... ] ...]
|
sim/grid [[0 0 0 0 0 0 0 0 ...] [0 ...] ...]
|
||||||
```
|
```
|
||||||
|
|
||||||
Details that are decisions rather than formatting:
|
Details that are decisions rather than formatting:
|
||||||
@ -2663,7 +2663,7 @@ The half the shadow stack was built for. `(:op "locals" :frame N)` answers what
|
|||||||
(:op "locals" :frame 0) → (:status "ok" :frame "look"
|
(:op "locals" :frame 0) → (:status "ok" :frame "look"
|
||||||
:locals (("n" "i64" "3") ("label" "string" "\"hello\"")
|
:locals (("n" "i64" "3") ("label" "string" "\"hello\"")
|
||||||
("p" "Point" "(Point {:x 1.5 :y 2.5})")
|
("p" "Point" "(Point {:x 1.5 :y 2.5})")
|
||||||
("xs" "[3 i32]" "[ 10 20 30]") ("flag" "bool" "true"))
|
("xs" "[3 i32]" "[10 20 30]") ("flag" "bool" "true"))
|
||||||
:refused (("after" "not bound yet at the point the program stopped")))
|
:refused (("after" "not bound yet at the point the program stopped")))
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -2744,7 +2744,7 @@ with nowhere to ask it.
|
|||||||
|
|
||||||
```
|
```
|
||||||
(:op "globals") → (:status "ok"
|
(:op "globals") → (:status "ok"
|
||||||
:globals (("grid" "[4 i32]" "[ 7 5 0 0]" (0 1))
|
:globals (("grid" "[4 i32]" "[7 5 0 0]" (0 1))
|
||||||
("pressure" "i64" "12" (0))
|
("pressure" "i64" "12" (0))
|
||||||
("label" "string" "\"running\"" (1)))
|
("label" "string" "\"running\"" (1)))
|
||||||
:refused () :skipped ())
|
:refused () :skipped ())
|
||||||
|
|||||||
@ -733,7 +733,7 @@ what it wants seen, from inside its own loop, with `(watch "name" value)`:
|
|||||||
|
|
||||||
The value is rendered the way `print` renders it, so a struct, an array, a
|
The value is rendered the way `print` renders it, so a struct, an array, a
|
||||||
slice, an option or a dyn value watches as it prints: `(Pos {.x 3 .y 1.5})`,
|
slice, an option or a dyn value watches as it prints: `(Pos {.x 3 .y 1.5})`,
|
||||||
`[ 1 2 3]`. A string is quoted. The value is evaluated once whether or not a
|
`[1 2 3]`. A string is quoted. The value is evaluated once whether or not a
|
||||||
watch buffer is open.
|
watch buffer is open.
|
||||||
|
|
||||||
That is the whole of it. `C-c C-c` on `step` adds or removes a watched value
|
That is the whole of it. `C-c C-c` on `step` adds or removes a watched value
|
||||||
|
|||||||
@ -133,7 +133,7 @@ reply without a daemon behind them, and so that this file names
|
|||||||
;; its span bound of 8 fields
|
;; its span bound of 8 fields
|
||||||
;; (Name A {.f V}) an instance of a generic struct, its type arguments
|
;; (Name A {.f V}) an instance of a generic struct, its type arguments
|
||||||
;; kept on the head (:text) and the name alone as :type
|
;; kept on the head (:text) and the name alone as :type
|
||||||
;; [ V V V] an array or a slice, ` ...' likewise
|
;; [V V V] an array or a slice, ` ...' likewise
|
||||||
;; (some V) / none an option
|
;; (some V) / none an option
|
||||||
;; <ptr> a pointer, never followed
|
;; <ptr> a pointer, never followed
|
||||||
;; <Name> a named type the walk had no structure for
|
;; <Name> a named type the walk had no structure for
|
||||||
@ -179,7 +179,7 @@ reply without a daemon behind them, and so that this file names
|
|||||||
(1+ end)))))
|
(1+ end)))))
|
||||||
|
|
||||||
(defun flan-inspect--read-seq (s i)
|
(defun flan-inspect--read-seq (s i)
|
||||||
"Read `[ V V]' at I, which is `[' — an array or a slice."
|
"Read `[V V]' at I, which is `[' — an array or a slice."
|
||||||
(let ((i (1+ i)) (kids nil) (n 0) (more nil) (done nil))
|
(let ((i (1+ i)) (kids nil) (n 0) (more nil) (done nil))
|
||||||
(while (not done)
|
(while (not done)
|
||||||
(setq i (flan-inspect--skip-space s i))
|
(setq i (flan-inspect--skip-space s i))
|
||||||
|
|||||||
@ -60,7 +60,7 @@
|
|||||||
;; The whole of the worked example in docs/BUILT.md, "`C-x C-e` — evaluating
|
;; The whole of the worked example in docs/BUILT.md, "`C-x C-e` — evaluating
|
||||||
;; an expression", nested two deep with a string that has escaped quotes in it
|
;; an expression", nested two deep with a string that has escaped quotes in it
|
||||||
;; and a slice at the end.
|
;; and a slice at the end.
|
||||||
(let* ((src "(Blob {.id 7 .name \"sandy \\\"quoted\\\"\" .pos (V {.x 1.5 .y 0}) .tags [ 0 42 0]})")
|
(let* ((src "(Blob {.id 7 .name \"sandy \\\"quoted\\\"\" .pos (V {.x 1.5 .y 0}) .tags [0 42 0]})")
|
||||||
(n (flan-inspect-parse src))
|
(n (flan-inspect-parse src))
|
||||||
(kids (plist-get n :children)))
|
(kids (plist-get n :children)))
|
||||||
(test-flan--check "every field of a nested struct"
|
(test-flan--check "every field of a nested struct"
|
||||||
@ -76,7 +76,7 @@
|
|||||||
|
|
||||||
;; A generic struct's instance, its type arguments between the name and the
|
;; A generic struct's instance, its type arguments between the name and the
|
||||||
;; brace, compound ones included. Written back as it was read.
|
;; brace, compound ones included. Written back as it was read.
|
||||||
(let* ((src "(Pair (Option u8) [3 i32] {.a (some 1) .b [ 1 2 3]})")
|
(let* ((src "(Pair (Option u8) [3 i32] {.a (some 1) .b [1 2 3]})")
|
||||||
(n (flan-inspect-parse src)))
|
(n (flan-inspect-parse src)))
|
||||||
(test-flan--check "a generic instance is a struct"
|
(test-flan--check "a generic instance is a struct"
|
||||||
(eq (plist-get n :kind) 'struct))
|
(eq (plist-get n :kind) 'struct))
|
||||||
@ -92,15 +92,15 @@
|
|||||||
:children))
|
:children))
|
||||||
'("a")))
|
'("a")))
|
||||||
|
|
||||||
;; [ 0 42 0] — Types.Slice and Types.Array both write this.
|
;; [0 42 0] — Types.Slice and Types.Array both write this.
|
||||||
(let ((n (flan-inspect-parse "[ 0 42 0]")))
|
(let ((n (flan-inspect-parse "[0 42 0]")))
|
||||||
(test-flan--check "a sequence is a sequence" (eq (plist-get n :kind) 'seq))
|
(test-flan--check "a sequence is a sequence" (eq (plist-get n :kind) 'seq))
|
||||||
(test-flan--check "indexed from zero"
|
(test-flan--check "indexed from zero"
|
||||||
(equal (mapcar #'car (plist-get n :children)) '(0 1 2))))
|
(equal (mapcar #'car (plist-get n :children)) '(0 1 2))))
|
||||||
|
|
||||||
;; [ [ 0 0] [ 1 ...] ...] — span truncation at both levels, which is what
|
;; [[0 0] [1 ...] ...] — span truncation at both levels, which is what
|
||||||
;; sand's [100 [100 u32]] actually produces.
|
;; sand's [100 [100 u32]] actually produces.
|
||||||
(let* ((n (flan-inspect-parse "[ [ 0 0] [ 1 ...] ...]"))
|
(let* ((n (flan-inspect-parse "[[0 0] [1 ...] ...]"))
|
||||||
(kids (plist-get n :children)))
|
(kids (plist-get n :children)))
|
||||||
(test-flan--check "a trailing ... is truncation, not an element"
|
(test-flan--check "a trailing ... is truncation, not an element"
|
||||||
(and (= (length kids) 2) (plist-get n :truncated)))
|
(and (= (length kids) 2) (plist-get n :truncated)))
|
||||||
@ -841,7 +841,7 @@ would be overwritten. Look again and re-do the edit")
|
|||||||
(test-flan--check "a struct round-trips through the editable spelling"
|
(test-flan--check "a struct round-trips through the editable spelling"
|
||||||
(funcall round "(Blob {.id 7 .name \"sandy\" .pos (V {.x 1.5 .y 0})})"))
|
(funcall round "(Blob {.id 7 .name \"sandy\" .pos (V {.x 1.5 .y 0})})"))
|
||||||
(test-flan--check "an array does too"
|
(test-flan--check "an array does too"
|
||||||
(funcall round "[ 10 20 30]"))
|
(funcall round "[10 20 30]"))
|
||||||
(test-flan--check "and an option, and a nested one"
|
(test-flan--check "and an option, and a nested one"
|
||||||
(funcall round "(some (V {.x 1 .y (some 2)}))")))
|
(funcall round "(some (V {.x 1 .y (some 2)}))")))
|
||||||
|
|
||||||
@ -881,8 +881,8 @@ would be overwritten. Look again and re-do the edit")
|
|||||||
(and (null (car d))
|
(and (null (car d))
|
||||||
(string-match-p "does not add or rename" (cdr d))))))
|
(string-match-p "does not add or rename" (cdr d))))))
|
||||||
|
|
||||||
(let ((d (flan-inspect--diff (flan-inspect-parse "[ 1 2 3]")
|
(let ((d (flan-inspect--diff (flan-inspect-parse "[1 2 3]")
|
||||||
(flan-inspect-parse "[ 1 2 3 4]"))))
|
(flan-inspect-parse "[1 2 3 4]"))))
|
||||||
(test-flan--check "an element added is a change to the container, and refused"
|
(test-flan--check "an element added is a change to the container, and refused"
|
||||||
(and (null (car d))
|
(and (null (car d))
|
||||||
(string-match-p "3 elements and the buffer has 4" (cdr d)))))
|
(string-match-p "3 elements and the buffer has 4" (cdr d)))))
|
||||||
@ -1781,7 +1781,7 @@ would be overwritten. Look again and re-do the edit")
|
|||||||
(list :fn "main" :loc "g.flan:30:1"))
|
(list :fn "main" :loc "g.flan:30:1"))
|
||||||
;; Ordered as the daemon orders it: by the innermost frame
|
;; Ordered as the daemon orders it: by the innermost frame
|
||||||
;; that touches each one.
|
;; that touches each one.
|
||||||
:globals '(("grid" "[4 i32]" "[ 7 5 0 0]" (0 1))
|
:globals '(("grid" "[4 i32]" "[7 5 0 0]" (0 1))
|
||||||
("pressure" "i64" "12" (0))
|
("pressure" "i64" "12" (0))
|
||||||
("label" "string" "\"running\"" (1)))))
|
("label" "string" "\"running\"" (1)))))
|
||||||
(buf (test-flan--cnr state))
|
(buf (test-flan--cnr state))
|
||||||
|
|||||||
185
lib/check.ml
185
lib/check.ml
@ -3451,11 +3451,87 @@ let view_elem (t : Types.t) : int64 option =
|
|||||||
let view_elem_lit loc (k : int64) =
|
let view_elem_lit loc (k : int64) =
|
||||||
mk loc (Types.Int Types.I32) (Tast.Int (k, Types.I32))
|
mk loc (Types.Int Types.I32) (Tast.Int (k, Types.I32))
|
||||||
|
|
||||||
let view_not_yet loc (container : Types.t) (elem : Types.t) =
|
(* A fix is spelled in the syntax of the file the mistake is in: the checker
|
||||||
no_dyn_yet loc ~into:true container
|
sees one AST for both, so the location's file is the only thing left that
|
||||||
|
says which one the reader is looking at. *)
|
||||||
|
let fln_source (loc : Loc.t) = Source.indented_at loc
|
||||||
|
|
||||||
|
(* Which form defined each mutable global, [defonce] or [def], so a fix that
|
||||||
|
rewrites the definition keeps the form the programmer chose. Filled where
|
||||||
|
globals are collected; a name missing from it (a defconst) is given
|
||||||
|
[defonce]. *)
|
||||||
|
let global_forms : (string, Ast.reinit) Hashtbl.t = Hashtbl.create 16
|
||||||
|
|
||||||
|
(* The function being checked and its parameters, by slot. A stack because
|
||||||
|
a generic's copy is checked from inside the body that called it. *)
|
||||||
|
let grow_params : (ctx * (int * Ast.field) list) list ref = ref []
|
||||||
|
|
||||||
|
(* The two refusals below share their subject and their fix. The subject is
|
||||||
|
the name as written when the refused value is a bare name, so the message
|
||||||
|
can say [a is a [4 i64]]; anything longer is "this". The fix is the one
|
||||||
|
spelling that works for every container either refusal reaches, whatever
|
||||||
|
its element type or wherever it lives: make it a dyn value where it is
|
||||||
|
built, and there is no view to refuse. *)
|
||||||
|
let view_subject (e : Tast.expr) =
|
||||||
|
match Loc.snippet e.Tast.loc with
|
||||||
|
| Some s
|
||||||
|
when s <> ""
|
||||||
|
&& String.for_all
|
||||||
|
(fun c -> not (List.mem c [ ' '; '('; ')'; '['; ']'; '{'; '}'; '"'; '.'; ',' ]))
|
||||||
|
s ->
|
||||||
|
Some s
|
||||||
|
| _ -> None
|
||||||
|
|
||||||
|
let view_refusal kind loc (e : Tast.expr) reason =
|
||||||
|
let ty = Types.to_string e.Tast.ty in
|
||||||
|
let fln = fln_source loc in
|
||||||
|
(* A parameter is made by the caller, so its fix is its declaration. The
|
||||||
|
name is compared as well as the slot: a closure numbers its slots from
|
||||||
|
zero too, and is checked while its enclosing function is on the stack. *)
|
||||||
|
let param =
|
||||||
|
match e.Tast.e, view_subject e, !grow_params with
|
||||||
|
| Tast.Local s, Some n, (ctx, ps) :: _ ->
|
||||||
|
(match List.assoc_opt s ps with
|
||||||
|
| Some (p : Ast.field) when p.Ast.fname = n -> Some (ctx.owner, n)
|
||||||
|
| _ -> None)
|
||||||
|
| _ -> None
|
||||||
|
in
|
||||||
|
let subject, fix =
|
||||||
|
match param, view_subject e with
|
||||||
|
| Some (f, n), _ ->
|
||||||
|
( Printf.sprintf "%s is a %s parameter" n ty,
|
||||||
|
Printf.sprintf "Declare %s as dyn in %s's parameters: %s%s" n f n
|
||||||
|
(if fln then ": dyn" else " dyn") )
|
||||||
|
| None, Some n when (match e.Tast.e with Tast.Global _ -> true | _ -> false) ->
|
||||||
|
let every =
|
||||||
|
match e.Tast.e with
|
||||||
|
| Tast.Global g -> Hashtbl.find_opt global_forms g = Some Ast.Every
|
||||||
|
| _ -> false
|
||||||
|
in
|
||||||
|
( Printf.sprintf "%s is a %s" n ty,
|
||||||
|
Printf.sprintf "Define %s as a dyn value, as in %s" n
|
||||||
|
(if fln then
|
||||||
|
Printf.sprintf "%s %s: dyn = [...]" (if every then "def" else "once") n
|
||||||
|
else
|
||||||
|
Printf.sprintf "(%s %s dyn [...])" (if every then "def" else "defonce") n) )
|
||||||
|
| None, Some n ->
|
||||||
|
( Printf.sprintf "%s is a %s" n ty,
|
||||||
|
Printf.sprintf "Build %s as a dyn value where it is made, as in %s" n
|
||||||
|
(if fln then Printf.sprintf "let %s: dyn = [...]" n
|
||||||
|
else Printf.sprintf "(let [%s (the dyn [...])] ...)" n) )
|
||||||
|
| None, None ->
|
||||||
|
( Printf.sprintf "This is a %s" ty,
|
||||||
|
Printf.sprintf "Build it as a dyn value where it is made, as in %s"
|
||||||
|
(if fln then "the(dyn, [...])" else "(the dyn [...])") )
|
||||||
|
in
|
||||||
|
Loc.failk kind loc "%s, and a dyn value is wanted here. %s. %s" subject
|
||||||
|
reason fix
|
||||||
|
|
||||||
|
let view_not_yet loc (e : Tast.expr) (elem : Types.t) =
|
||||||
|
view_refusal "check/dyn-not-yet" loc e
|
||||||
(Printf.sprintf
|
(Printf.sprintf
|
||||||
". A container view carries i64, f64 or bool elements, and %s is not \
|
"A dyn value can see into a typed container only when its elements \
|
||||||
one of them"
|
are i64, f64 or bool, and these are %s"
|
||||||
(Types.to_string elem))
|
(Types.to_string elem))
|
||||||
|
|
||||||
(* M2 item 3's second guard, added on review: a view's descriptor holds an
|
(* M2 item 3's second guard, added on review: a view's descriptor holds an
|
||||||
@ -3539,20 +3615,11 @@ let rec permanent_root (e : Tast.expr) : bool =
|
|||||||
| Tast.Prim (Tast.Slice, [ target; _; _ ]) -> permanent_root target
|
| Tast.Prim (Tast.Slice, [ target; _; _ ]) -> permanent_root target
|
||||||
| _ -> false
|
| _ -> false
|
||||||
|
|
||||||
let view_not_permanent loc (container : Types.t) =
|
let view_not_permanent loc (e : Tast.expr) =
|
||||||
Loc.failk "check/dyn-view-lifetime" loc
|
view_refusal "check/dyn-view-lifetime" loc e
|
||||||
"%s does not cross into dyn as a view here — its storage is not known \
|
"A dyn value can see into a typed container only when it is a global: a \
|
||||||
to outlive the view, and a view is exactly as stale-safe as the thing \
|
local, a parameter or a temporary can be gone while the dyn value still \
|
||||||
it is a view of, no more and no less. A global's storage does outlive \
|
points at it"
|
||||||
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 \
|
|
||||||
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 \
|
|
||||||
frame that is gone — or \
|
|
||||||
anything reached through a (Ptr T) is refused: the checker cannot tell \
|
|
||||||
a heap-durable pointer from a frame's own, and admitting one admits \
|
|
||||||
the other"
|
|
||||||
(Types.to_string container) (Types.to_string container)
|
|
||||||
|
|
||||||
(* A value handed out of [f] that points into [f]'s own frame: returned (the
|
(* A value handed out of [f] that points into [f]'s own frame: returned (the
|
||||||
last form's tails, or a [return]), or stored into a global or a field or
|
last form's tails, or a [return]), or stored into a global or a field or
|
||||||
@ -3860,15 +3927,15 @@ 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 [(defonce g ...)] as the spelling that works, and for an
|
says a global can be seen into, and for an element type no view can
|
||||||
element type no view can carry — a string, an i32 — the global spelling
|
carry — a string, an i32 — a global is refused too, so the wrong order
|
||||||
is refused too, so the wrong order hands the programmer advice that
|
hands the programmer a reason that is false for their case. Whichever
|
||||||
fails when they take it. Whichever refusal is unconditional wins. *)
|
refusal is unconditional wins. *)
|
||||||
| Types.Vec elem ->
|
| Types.Vec elem ->
|
||||||
(match view_elem elem with
|
(match view_elem elem with
|
||||||
| None -> view_not_yet loc e.Tast.ty elem
|
| None -> view_not_yet loc e elem
|
||||||
| Some k ->
|
| Some k ->
|
||||||
if not (permanent_root e) then view_not_permanent loc e.Tast.ty
|
if not (permanent_root e) then view_not_permanent loc e
|
||||||
else dyn "flan_dyn_view_vec" [ e; view_elem_lit loc k ])
|
else dyn "flan_dyn_view_vec" [ e; view_elem_lit loc k ])
|
||||||
(* A dyn view is written through by (set (at d i) x), and nothing on the
|
(* A dyn view is written through by (set (at d i) x), and nothing on the
|
||||||
dyn side can tell a read-only one apart, so a [[const T]] does not
|
dyn side can tell a read-only one apart, so a [[const T]] does not
|
||||||
@ -3881,15 +3948,15 @@ let box loc (e : Tast.expr) : Tast.expr =
|
|||||||
(Types.to_string e.Tast.ty) (Types.to_string elem)
|
(Types.to_string e.Tast.ty) (Types.to_string elem)
|
||||||
| Types.Slice (Types.Mut, elem) ->
|
| Types.Slice (Types.Mut, elem) ->
|
||||||
(match view_elem elem with
|
(match view_elem elem with
|
||||||
| None -> view_not_yet loc e.Tast.ty elem
|
| None -> view_not_yet loc e elem
|
||||||
| Some k ->
|
| Some k ->
|
||||||
if not (permanent_root e) then view_not_permanent loc e.Tast.ty
|
if not (permanent_root e) then view_not_permanent loc e
|
||||||
else dyn "flan_dyn_view_flat" [ e; view_elem_lit loc k ])
|
else dyn "flan_dyn_view_flat" [ e; view_elem_lit loc k ])
|
||||||
| Types.Array (n, elem) ->
|
| Types.Array (n, elem) ->
|
||||||
(match view_elem elem with
|
(match view_elem elem with
|
||||||
| None -> view_not_yet loc e.Tast.ty elem
|
| None -> view_not_yet loc e elem
|
||||||
| Some k ->
|
| Some k ->
|
||||||
if not (permanent_root e) then view_not_permanent loc e.Tast.ty
|
if not (permanent_root e) then view_not_permanent loc e
|
||||||
else
|
else
|
||||||
dyn "flan_dyn_view_flat"
|
dyn "flan_dyn_view_flat"
|
||||||
[ e; mk loc dyn_i64 (Tast.Int (n, Types.I64)); view_elem_lit loc k ])
|
[ e; mk loc dyn_i64 (Tast.Int (n, Types.I64)); view_elem_lit loc k ])
|
||||||
@ -5123,11 +5190,9 @@ let if_depth = ref 0
|
|||||||
|
|
||||||
(* A Vec or a Map parameter is a copy of the caller's header — Odin's rule —
|
(* A Vec or a Map parameter is a copy of the caller's header — Odin's rule —
|
||||||
so growing it reallocates a block only this function's copy points at, and
|
so growing it reallocates a block only this function's copy points at, and
|
||||||
the caller's container never sees the elements. The function being checked
|
the caller's container never sees the elements. The warnings found so far,
|
||||||
and its container parameters, by slot, and the warnings found so far, one
|
one per parameter, printed by [build_program]; the parameters themselves
|
||||||
per parameter, printed by [build_program]. A stack because a generic's copy
|
are [grow_params], above [view_refusal], which reads them too. *)
|
||||||
is checked from inside the body that called it. *)
|
|
||||||
let grow_params : (ctx * (int * Ast.field) list) list ref = ref []
|
|
||||||
let grow_warnings : Loc.diag list ref = ref []
|
let grow_warnings : Loc.diag list ref = ref []
|
||||||
|
|
||||||
let note_grown ctx op loc (target : Tast.expr) =
|
let note_grown ctx op loc (target : Tast.expr) =
|
||||||
@ -14554,6 +14619,7 @@ let collect env (decls : Ast.decl list) =
|
|||||||
(match k with Ast.Once -> "defonce" | Ast.Every -> "def") n
|
(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 global_forms n k;
|
||||||
Hashtbl.replace env.global_locs n loc
|
Hashtbl.replace env.global_locs n loc
|
||||||
| Ast.Defconst (n, Some t, _) ->
|
| Ast.Defconst (n, Some t, _) ->
|
||||||
Hashtbl.replace env.globals n (resolve env t, true);
|
Hashtbl.replace env.globals n (resolve env t, true);
|
||||||
@ -14806,10 +14872,61 @@ let rec check_fn env (fn : Ast.fn) : Tast.fn =
|
|||||||
let is_defer (e : Ast.expr) =
|
let is_defer (e : Ast.expr) =
|
||||||
match e.Ast.e with Ast.Defer _ -> true | _ -> false
|
match e.Ast.e with Ast.Defer _ -> true | _ -> false
|
||||||
in
|
in
|
||||||
|
(* A dyn function whose value-giving form gives none — a [while], a
|
||||||
|
[set] — reaches [box]'s unit refusal, which can only say that () is
|
||||||
|
not a dyn value. Here the function is known, so the refusal is
|
||||||
|
restated as what went wrong with it. Only a refusal at the body's
|
||||||
|
own tail is: one deeper in the last form (a unit argument to a dyn
|
||||||
|
parameter) is about that argument and keeps its own message. *)
|
||||||
|
let rec tail_locs (e : Ast.expr) =
|
||||||
|
e.Ast.loc
|
||||||
|
:: (match e.Ast.e with
|
||||||
|
| Ast.Do es | Ast.Let (_, es) ->
|
||||||
|
(match List.rev es with x :: _ -> tail_locs x | [] -> [])
|
||||||
|
| Ast.If (_, a, b) ->
|
||||||
|
tail_locs a @ (match b with Some b -> tail_locs b | None -> [])
|
||||||
|
| _ -> [])
|
||||||
|
in
|
||||||
|
let restate_unit last (d : Loc.diag) =
|
||||||
|
let at (l : Loc.t) =
|
||||||
|
l.Loc.file = d.Loc.dloc.Loc.file && l.Loc.line = d.Loc.dloc.Loc.line
|
||||||
|
&& l.Loc.col = d.Loc.dloc.Loc.col
|
||||||
|
in
|
||||||
|
if d.Loc.kind = "check/dyn-unit" && Types.equal ret Types.Dyn
|
||||||
|
&& List.exists at (tail_locs last)
|
||||||
|
then
|
||||||
|
Loc.diag ~kind:"check/dyn-unit" d.Loc.dloc
|
||||||
|
(Printf.sprintf
|
||||||
|
"%s is declared to return dyn, but the last form of its body \
|
||||||
|
gives no value. End the body with the value to return (nil \
|
||||||
|
for none), or declare %s to return nothing: %s"
|
||||||
|
fn.Ast.name fn.Ast.name
|
||||||
|
(if fln_source d.Loc.dloc then
|
||||||
|
Printf.sprintf "fn %s(...) -> ()" fn.Ast.name
|
||||||
|
else Printf.sprintf "(defn %s [...] () ...)" fn.Ast.name))
|
||||||
|
else d
|
||||||
|
in
|
||||||
|
(* The refusal arrives either raised or, under recovery, recorded on
|
||||||
|
[env.recovered] while checking goes on; both are restated. *)
|
||||||
|
let check_last last =
|
||||||
|
let env = ctx.env in
|
||||||
|
let before = env.recovered in
|
||||||
|
let r =
|
||||||
|
try check ctx ?want last
|
||||||
|
with Loc.Error d -> Loc.raise_diag (restate_unit last d)
|
||||||
|
in
|
||||||
|
let rec fresh = function
|
||||||
|
| l when l == before -> l
|
||||||
|
| d :: rest -> restate_unit last d :: fresh rest
|
||||||
|
| [] -> []
|
||||||
|
in
|
||||||
|
env.recovered <- fresh env.recovered;
|
||||||
|
r
|
||||||
|
in
|
||||||
let rec go = function
|
let rec go = function
|
||||||
| [ last ] ->
|
| [ last ] ->
|
||||||
ctx.defer_ok <- true;
|
ctx.defer_ok <- true;
|
||||||
[ (if is_defer last then check ctx last else check ctx ?want last) ]
|
[ (if is_defer last then check ctx last else check_last last) ]
|
||||||
| x :: rest ->
|
| x :: rest ->
|
||||||
ctx.defer_ok <- true;
|
ctx.defer_ok <- true;
|
||||||
let x = check ctx x in
|
let x = check ctx x in
|
||||||
|
|||||||
@ -295,7 +295,7 @@ let rec walk c b depth addr (ty : Types.t) =
|
|||||||
let shown = min n Render.max_span and sz = size c t in
|
let shown = min n Render.max_span and sz = size c t in
|
||||||
put b "[";
|
put b "[";
|
||||||
for i = 0 to shown - 1 do
|
for i = 0 to shown - 1 do
|
||||||
put b " ";
|
if i > 0 then put b " ";
|
||||||
walk c b (depth + 1) (addr + (i * sz)) t
|
walk c b (depth + 1) (addr + (i * sz)) t
|
||||||
done;
|
done;
|
||||||
if n > shown then put b " ...";
|
if n > shown then put b " ...";
|
||||||
@ -307,7 +307,7 @@ let rec walk c b depth addr (ty : Types.t) =
|
|||||||
let sz = size c t in
|
let sz = size c t in
|
||||||
put b "[";
|
put b "[";
|
||||||
for i = 0 to n - 1 do
|
for i = 0 to n - 1 do
|
||||||
put b " ";
|
if i > 0 then put b " ";
|
||||||
walk c b (depth + 1) (p + (i * sz)) t
|
walk c b (depth + 1) (p + (i * sz)) t
|
||||||
done;
|
done;
|
||||||
put b "]"
|
put b "]"
|
||||||
|
|||||||
@ -358,7 +358,7 @@ let rec render ?(refuse = print_refusal) c depth (e : Tast.expr) : Tast.expr lis
|
|||||||
let v =
|
let v =
|
||||||
{ Tast.e = Tast.Prim (Tast.At, [ e; i32 i ]); ty = t; loc }
|
{ Tast.e = Tast.Prim (Tast.At, [ e; i32 i ]); ty = t; loc }
|
||||||
in
|
in
|
||||||
lit " " :: render c (depth + 1) v))
|
(if i = 0 then [] else [ lit " " ]) @ render c (depth + 1) v))
|
||||||
in
|
in
|
||||||
[ do_ ((lit "[" :: parts)
|
[ do_ ((lit "[" :: parts)
|
||||||
@ (if Int64.to_int n > shown then [ lit " ..." ] else [])
|
@ (if Int64.to_int n > shown then [ lit " ..." ] else [])
|
||||||
@ -382,6 +382,16 @@ let rec render ?(refuse = print_refusal) c depth (e : Tast.expr) : Tast.expr lis
|
|||||||
Tast.Prim (Tast.At, [ local sv e.Tast.ty; local iv (Types.Int Types.I32) ]);
|
Tast.Prim (Tast.At, [ local sv e.Tast.ty; local iv (Types.Int Types.I32) ]);
|
||||||
ty = t; loc }
|
ty = t; loc }
|
||||||
in
|
in
|
||||||
|
(* A space between elements and none after the bracket: [1 2 3], the
|
||||||
|
runtime's spelling for a dyn vector. The empty literal is the else
|
||||||
|
arm because an If needs one; it writes nothing. *)
|
||||||
|
let sep =
|
||||||
|
unit_
|
||||||
|
(Tast.If
|
||||||
|
({ Tast.e = Tast.Prim (Tast.Gt, [ local iv (Types.Int Types.I32); i32 0 ]);
|
||||||
|
ty = Types.Bool; loc },
|
||||||
|
lit " ", lit ""))
|
||||||
|
in
|
||||||
let step =
|
let step =
|
||||||
unit_
|
unit_
|
||||||
(Tast.Set
|
(Tast.Set
|
||||||
@ -396,7 +406,7 @@ let rec render ?(refuse = print_refusal) c depth (e : Tast.expr) : Tast.expr lis
|
|||||||
[ lit "[";
|
[ lit "[";
|
||||||
unit_
|
unit_
|
||||||
(Tast.While
|
(Tast.While
|
||||||
(cond, lit " " :: render c (depth + 1) elem, [ step ]));
|
(cond, sep :: render c (depth + 1) elem, [ step ]));
|
||||||
lit "]" ])) ]
|
lit "]" ])) ]
|
||||||
(* The one type this walk does not walk. Every other arm is here because a
|
(* The one type this walk does not walk. Every other arm is here because a
|
||||||
Flan value carries no header and only the compiler knows what it is; a
|
Flan value carries no header and only the compiler knows what it is; a
|
||||||
|
|||||||
@ -555,11 +555,10 @@ static inline const char *tag_of(flan_dyn v) {
|
|||||||
* bool the word true / false
|
* bool the word true / false
|
||||||
* text bare at the top level, hi / "a b"
|
* text bare at the top level, hi / "a b"
|
||||||
* quoted and escaped inside
|
* quoted and escaped inside
|
||||||
* vec a slice's spelling [ 1 2 3]
|
* vec a slice's spelling [1 2 3]
|
||||||
*
|
*
|
||||||
* The leading space before every element is not a slip: it is what
|
* A space between elements and none inside the brackets, the same as
|
||||||
* lib/render.ml's slice loop emits and what a Flan program prints today, and
|
* lib/render.ml's slice loop: an acceptance test compares the two.
|
||||||
* an acceptance test comparing the two would notice a tidier answer.
|
|
||||||
*
|
*
|
||||||
* nil is the one tag with no typed counterpart, and it renders as `nil`.
|
* nil is the one tag with no typed counterpart, and it renders as `nil`.
|
||||||
*
|
*
|
||||||
@ -681,15 +680,15 @@ static void render(dyn_sink w, flan_dyn v, int depth, int nested) {
|
|||||||
emit_n(w, kw_bytes(k), k->len);
|
emit_n(w, kw_bytes(k), k->len);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* The map prints in edn's shape with the vec's spacing: a space before
|
/* The map prints in edn's shape with the vec's spacing: a space between
|
||||||
* every element, key and value alike, so { :a 1 :b 2} sits beside the vec's
|
* elements, key and value alike, so {:a 1 :b 2} sits beside the vec's
|
||||||
* [ 1 2 3] rather than inventing a fourth convention. Entries come out in
|
* [1 2 3]. Entries come out in insertion order, which is the only order
|
||||||
* insertion order, which is the only order the representation has. */
|
* the representation has. */
|
||||||
case FLAN_DYN_TAG_MAP: {
|
case FLAN_DYN_TAG_MAP: {
|
||||||
flan_obj *o = dyn_obj(v);
|
flan_obj *o = dyn_obj(v);
|
||||||
int64_t i;
|
int64_t i;
|
||||||
/* A class instance prints its shape tag in front, Clojure's own spelling
|
/* A class instance prints its shape tag in front, Clojure's own spelling
|
||||||
* for a record: #point{ :x 1 :y 2}. The tag is not an entry, so it is
|
* for a record: #point{:x 1 :y 2}. The tag is not an entry, so it is
|
||||||
* written here or it is not written at all. */
|
* written here or it is not written at all. */
|
||||||
if (o->u.v.klass != NULL) {
|
if (o->u.v.klass != NULL) {
|
||||||
emit(w, "#");
|
emit(w, "#");
|
||||||
@ -697,7 +696,7 @@ static void render(dyn_sink w, flan_dyn v, int depth, int nested) {
|
|||||||
}
|
}
|
||||||
emit(w, "{");
|
emit(w, "{");
|
||||||
for (i = 0; i < o->len; i++) {
|
for (i = 0; i < o->len; i++) {
|
||||||
emit(w, " ");
|
if (i > 0) emit(w, " ");
|
||||||
render(w, o->u.v.items[i * 2], depth + 1, 1);
|
render(w, o->u.v.items[i * 2], depth + 1, 1);
|
||||||
emit(w, " ");
|
emit(w, " ");
|
||||||
render(w, o->u.v.items[i * 2 + 1], depth + 1, 1);
|
render(w, o->u.v.items[i * 2 + 1], depth + 1, 1);
|
||||||
@ -710,7 +709,7 @@ static void render(dyn_sink w, flan_dyn v, int depth, int nested) {
|
|||||||
int64_t i, n = o->kind == OBJ_VIEW ? view_len(NULL, 0, "print", o) : o->len;
|
int64_t i, n = o->kind == OBJ_VIEW ? view_len(NULL, 0, "print", o) : o->len;
|
||||||
emit(w, "[");
|
emit(w, "[");
|
||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
emit(w, " ");
|
if (i > 0) emit(w, " ");
|
||||||
if (o->kind == OBJ_VIEW)
|
if (o->kind == OBJ_VIEW)
|
||||||
render(w, view_box(o->u.view.elem,
|
render(w, view_box(o->u.view.elem,
|
||||||
(const uint8_t *)view_base(o)
|
(const uint8_t *)view_base(o)
|
||||||
@ -818,12 +817,12 @@ static void say_render(sayer *s, flan_dyn v, int depth) {
|
|||||||
}
|
}
|
||||||
say_puts(s, "{");
|
say_puts(s, "{");
|
||||||
for (i = 0; i < o->len && s->n < s->cap - 8; i++) {
|
for (i = 0; i < o->len && s->n < s->cap - 8; i++) {
|
||||||
say_puts(s, " ");
|
if (i > 0) say_puts(s, " ");
|
||||||
say_render(s, o->u.v.items[i * 2], depth + 1);
|
say_render(s, o->u.v.items[i * 2], depth + 1);
|
||||||
say_puts(s, " ");
|
say_puts(s, " ");
|
||||||
say_render(s, o->u.v.items[i * 2 + 1], depth + 1);
|
say_render(s, o->u.v.items[i * 2 + 1], depth + 1);
|
||||||
}
|
}
|
||||||
say_puts(s, i < o->len ? " ...}" : "}");
|
say_puts(s, i == o->len ? "}" : i > 0 ? " ...}" : "...}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@ -832,7 +831,7 @@ static void say_render(sayer *s, flan_dyn v, int depth) {
|
|||||||
if (depth >= 2) { say_puts(s, "[...]"); return; }
|
if (depth >= 2) { say_puts(s, "[...]"); return; }
|
||||||
say_puts(s, "[");
|
say_puts(s, "[");
|
||||||
for (i = 0; i < n && s->n < s->cap - 8; i++) {
|
for (i = 0; i < n && s->n < s->cap - 8; i++) {
|
||||||
say_puts(s, " ");
|
if (i > 0) say_puts(s, " ");
|
||||||
if (o->kind == OBJ_VIEW)
|
if (o->kind == OBJ_VIEW)
|
||||||
say_render(s,
|
say_render(s,
|
||||||
view_box(o->u.view.elem,
|
view_box(o->u.view.elem,
|
||||||
@ -842,7 +841,7 @@ static void say_render(sayer *s, flan_dyn v, int depth) {
|
|||||||
else
|
else
|
||||||
say_render(s, o->u.v.items[i], depth + 1);
|
say_render(s, o->u.v.items[i], depth + 1);
|
||||||
}
|
}
|
||||||
say_puts(s, i < n ? " ...]" : "]");
|
say_puts(s, i == n ? "]" : i > 0 ? " ...]" : "...]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -106,7 +106,7 @@ flan_dyn flan_dyn_map_new(void);
|
|||||||
* is its slot count and no key a program can write collides with it. What can
|
* is its slot count and no key a program can write collides with it. What can
|
||||||
* see it is [flan_dyn_class_of], [flan_dyn_eq] (two values of different
|
* see it is [flan_dyn_class_of], [flan_dyn_eq] (two values of different
|
||||||
* classes are unequal, and an instance is never equal to a plain map) and
|
* classes are unequal, and an instance is never equal to a plain map) and
|
||||||
* [flan_dyn_print] (an instance renders as #point{ :x 1 :y 2}).
|
* [flan_dyn_print] (an instance renders as #point{:x 1 :y 2}).
|
||||||
*
|
*
|
||||||
* The tag is not traced and does not have to be: an interned keyword entry is
|
* The tag is not traced and does not have to be: an interned keyword entry is
|
||||||
* immortal and is not a collector object. */
|
* immortal and is not a collector object. */
|
||||||
|
|||||||
@ -342,21 +342,21 @@ static void ops(void) {
|
|||||||
FDYN_push(nums, flan_dyn_from_i64(1));
|
FDYN_push(nums, flan_dyn_from_i64(1));
|
||||||
FDYN_push(nums, flan_dyn_from_i64(2));
|
FDYN_push(nums, flan_dyn_from_i64(2));
|
||||||
FDYN_push(nums, flan_dyn_from_i64(3));
|
FDYN_push(nums, flan_dyn_from_i64(3));
|
||||||
prints(nums, "[ 1 2 3]");
|
prints(nums, "[1 2 3]");
|
||||||
/* A text inside a structure is quoted and escaped, and bare at the top
|
/* A text inside a structure is quoted and escaped, and bare at the top
|
||||||
level. That is flan_rt.c's rule and the two have to agree, because the
|
level. That is flan_rt.c's rule and the two have to agree, because the
|
||||||
REPL parses the printed form back. */
|
REPL parses the printed form back. */
|
||||||
FDYN_push(strs, text("x"));
|
FDYN_push(strs, text("x"));
|
||||||
FDYN_push(strs, text("a b"));
|
FDYN_push(strs, text("a b"));
|
||||||
FDYN_push(strs, text("q\"\n"));
|
FDYN_push(strs, text("q\"\n"));
|
||||||
prints(strs, "[ \"x\" \"a b\" \"q\\\"\\n\"]");
|
prints(strs, "[\"x\" \"a b\" \"q\\\"\\n\"]");
|
||||||
/* And a vec of vecs, nested twice. */
|
/* And a vec of vecs, nested twice. */
|
||||||
{
|
{
|
||||||
flan_dyn outer = flan_dyn_vec_new();
|
flan_dyn outer = flan_dyn_vec_new();
|
||||||
flan_dyn_root_push(&outer);
|
flan_dyn_root_push(&outer);
|
||||||
FDYN_push(outer, nums);
|
FDYN_push(outer, nums);
|
||||||
FDYN_push(outer, strs);
|
FDYN_push(outer, strs);
|
||||||
prints(outer, "[ [ 1 2 3] [ \"x\" \"a b\" \"q\\\"\\n\"]]");
|
prints(outer, "[[1 2 3] [\"x\" \"a b\" \"q\\\"\\n\"]]");
|
||||||
flan_dyn_root_pop(1);
|
flan_dyn_root_pop(1);
|
||||||
}
|
}
|
||||||
prints(flan_dyn_vec_new(), "[]");
|
prints(flan_dyn_vec_new(), "[]");
|
||||||
@ -440,14 +440,14 @@ static void view(void) {
|
|||||||
buf[3] = 7;
|
buf[3] = 7;
|
||||||
check(num(FDYN_at(flat, flan_dyn_from_i64(3))) == 7,
|
check(num(FDYN_at(flat, flan_dyn_from_i64(3))) == 7,
|
||||||
"the array's own write reaches the view — it is not a copy");
|
"the array's own write reaches the view — it is not a copy");
|
||||||
prints(flat, "[ 10 20 99 7]");
|
prints(flat, "[10 20 99 7]");
|
||||||
|
|
||||||
/* Structural equality, view-aware — review's third finding. [dyn_equal]'s
|
/* Structural equality, view-aware — review's third finding. [dyn_equal]'s
|
||||||
VEC arm used to read [x->len]/[x->u.v.items] regardless of kind, which
|
VEC arm used to read [x->len]/[x->u.v.items] regardless of kind, which
|
||||||
for a view answers 0 and garbage: two views with different contents
|
for a view answers 0 and garbage: two views with different contents
|
||||||
compared equal, a view and an equal heap vec compared unequal, and a
|
compared equal, a view and an equal heap vec compared unequal, and a
|
||||||
map keyed by any view collided with every other view. [buf] now reads
|
map keyed by any view collided with every other view. [buf] now reads
|
||||||
[ 10 20 99 7]; [same] is a second, independent view over the identical
|
[10 20 99 7]; [same] is a second, independent view over the identical
|
||||||
bytes, and [other] a view over one differing element. */
|
bytes, and [other] a view over one differing element. */
|
||||||
{
|
{
|
||||||
int64_t same_buf[4] = { 10, 20, 99, 7 };
|
int64_t same_buf[4] = { 10, 20, 99, 7 };
|
||||||
@ -1111,7 +1111,7 @@ static void classes(void) {
|
|||||||
A program built and never reloaded has no registry at all, and its
|
A program built and never reloaded has no registry at all, and its
|
||||||
instances must behave exactly as they did before any of this existed. */
|
instances must behave exactly as they did before any of this existed. */
|
||||||
p = a_point(1, 2);
|
p = a_point(1, 2);
|
||||||
prints(p, "#point{ :x 1 :y 2}");
|
prints(p, "#point{:x 1 :y 2}");
|
||||||
check(num(slot(p, "x")) == 1, "an unregistered class reads its slot");
|
check(num(slot(p, "x")) == 1, "an unregistered class reads its slot");
|
||||||
check(num(flan_dyn_len(p)) == 2, "an unregistered class has its length");
|
check(num(flan_dyn_len(p)) == 2, "an unregistered class has its length");
|
||||||
|
|
||||||
@ -1125,7 +1125,7 @@ static void classes(void) {
|
|||||||
"a gained slot arrives as nil");
|
"a gained slot arrives as nil");
|
||||||
check(num(slot(p, "x")) == 1, "a kept slot keeps its value");
|
check(num(slot(p, "x")) == 1, "a kept slot keeps its value");
|
||||||
check(num(flan_dyn_len(p)) == 3, "a gained slot is counted");
|
check(num(flan_dyn_len(p)) == 3, "a gained slot is counted");
|
||||||
prints(p, "#point{ :x 1 :y 2 :z nil}");
|
prints(p, "#point{:x 1 :y 2 :z nil}");
|
||||||
/* And the tag survived: a migration must not turn an instance into a map. */
|
/* And the tag survived: a migration must not turn an instance into a map. */
|
||||||
check(truth(flan_dyn_eq(flan_dyn_class_of(p),
|
check(truth(flan_dyn_eq(flan_dyn_class_of(p),
|
||||||
flan_dyn_kw((const uint8_t *)"point", 5))),
|
flan_dyn_kw((const uint8_t *)"point", 5))),
|
||||||
@ -1143,7 +1143,7 @@ static void classes(void) {
|
|||||||
check(flan_dyn_tag(slot(p, "y")) == FLAN_DYN_TAG_NIL,
|
check(flan_dyn_tag(slot(p, "y")) == FLAN_DYN_TAG_NIL,
|
||||||
"a lost slot reads as absent");
|
"a lost slot reads as absent");
|
||||||
check(num(slot(p, "z")) == 9, "a slot either side of a lost one is kept");
|
check(num(slot(p, "z")) == 9, "a slot either side of a lost one is kept");
|
||||||
prints(p, "#point{ :x 1 :z 9}");
|
prints(p, "#point{:x 1 :z 9}");
|
||||||
|
|
||||||
/* ── Gained and lost at once, and the third bump ──
|
/* ── Gained and lost at once, and the third bump ──
|
||||||
Three definitions have now been registered after the first, so the
|
Three definitions have now been registered after the first, so the
|
||||||
@ -1158,7 +1158,7 @@ static void classes(void) {
|
|||||||
/* The slot order is the class's, not the instance's history: a migrated
|
/* The slot order is the class's, not the instance's history: a migrated
|
||||||
instance has to be indistinguishable from a freshly constructed one, or
|
instance has to be indistinguishable from a freshly constructed one, or
|
||||||
[len], [render] and insertion order would each tell a different story. */
|
[len], [render] and insertion order would each tell a different story. */
|
||||||
prints(p, "#point{ :z 9 :w nil}");
|
prints(p, "#point{:z 9 :w nil}");
|
||||||
|
|
||||||
/* ── Re-registering the same list changes nothing ──
|
/* ── Re-registering the same list changes nothing ──
|
||||||
This is what makes evaluating a whole file idempotent. If a bump
|
This is what makes evaluating a whole file idempotent. If a bump
|
||||||
@ -1218,7 +1218,7 @@ static void classes(void) {
|
|||||||
flan_dyn_from_i64(8));
|
flan_dyn_from_i64(8));
|
||||||
define("point", "x\ny\nz\nzz");
|
define("point", "x\ny\nz\nzz");
|
||||||
check(num(flan_dyn_len(plain)) == 2, "a plain map gains no slot");
|
check(num(flan_dyn_len(plain)) == 2, "a plain map gains no slot");
|
||||||
prints(plain, "{ :y 7 :x 8}");
|
prints(plain, "{:y 7 :x 8}");
|
||||||
check(flan_dyn_tag(flan_dyn_class_of(plain)) == FLAN_DYN_TAG_NIL,
|
check(flan_dyn_tag(flan_dyn_class_of(plain)) == FLAN_DYN_TAG_NIL,
|
||||||
"a plain map has no class");
|
"a plain map has no class");
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
;;;; object's header and not in the entries: (length p) is the slot count, no
|
;;;; object's header and not in the entries: (length p) is the slot count, no
|
||||||
;;;; key
|
;;;; key
|
||||||
;;;; a program can write collides with it, and it shows up in exactly three
|
;;;; a program can write collides with it, and it shows up in exactly three
|
||||||
;;;; places -- class-of, equality, and the printed form #point{ :x 1 :y 2}.
|
;;;; places -- class-of, equality, and the printed form #point{:x 1 :y 2}.
|
||||||
;;;;
|
;;;;
|
||||||
;;;; The two dispatch styles are one mechanism. A defgeneric dispatches on the
|
;;;; The two dispatch styles are one mechanism. A defgeneric dispatches on the
|
||||||
;;;; class of its first argument, which is Common Lisp's; a defmulti's body IS
|
;;;; class of its first argument, which is Common Lisp's; a defmulti's body IS
|
||||||
|
|||||||
@ -613,8 +613,8 @@ let () =
|
|||||||
(* An array literal whose elements agree is typed and one whose elements
|
(* An array literal whose elements agree is typed and one whose elements
|
||||||
mix is a dyn vector; (the T e) gives any expression its type. *)
|
mix is a dyn vector; (the T e) gives any expression its type. *)
|
||||||
let mixed_out =
|
let mixed_out =
|
||||||
"3\n301\n2.5\n18446744073709551615\n[ 10 \"Hi\"]\n[ nil 1]\n2\n3\n4\n\
|
"3\n301\n2.5\n18446744073709551615\n[10 \"Hi\"]\n[nil 1]\n2\n3\n4\n\
|
||||||
3\n1\n9000000004\n[ :a \"b\" 3]\n\
|
3\n1\n9000000004\n[:a \"b\" 3]\n\
|
||||||
255\n5000000000\n5\n4.5\n2\n0\n7\n3\n3\n0\n" in
|
255\n5000000000\n5\n4.5\n2\n0\n7\n3\n3\n0\n" in
|
||||||
outputs "mixed array literals and the" "programs/array-mixed.flan" mixed_out;
|
outputs "mixed array literals and the" "programs/array-mixed.flan" mixed_out;
|
||||||
outputs ~x86:true "mixed array literals and the, x86"
|
outputs ~x86:true "mixed array literals and the, x86"
|
||||||
@ -733,10 +733,10 @@ let () =
|
|||||||
let println_out =
|
let println_out =
|
||||||
"plain string\nplain bytes\n42\n-7\n5\n18446744073709551615\n3.5\n\
|
"plain string\nplain bytes\n42\n-7\n5\n18446744073709551615\n3.5\n\
|
||||||
-0.25\ntrue\nfalse\n()\n:green\n:red\n<ptr>\n(some 0)\nnone\n\
|
-0.25\ntrue\nfalse\n()\n:green\n:red\n<ptr>\n(some 0)\nnone\n\
|
||||||
(Blob {.id 7 .name \"sandy \\\"quoted\\\"\" .pos (V {.x 1.5 .y -2}) .tags [ 0 42 0]})\n\
|
(Blob {.id 7 .name \"sandy \\\"quoted\\\"\" .pos (V {.x 1.5 .y -2}) .tags [0 42 0]})\n\
|
||||||
[ 0 0 9 0]\n[ 0 0 0 0 0 0 0 0 ...]\n[ 0 9 0]\n\
|
[0 0 9 0]\n[0 0 0 0 0 0 0 0 ...]\n[0 9 0]\n\
|
||||||
(D1 {.d (D2 {.d (D3 {.d (D4 {.d (D5 {.n ...})})})})})\n[ 0 0]\n\
|
(D1 {.d (D2 {.d (D3 {.d (D4 {.d (D5 {.n ...})})})})})\n[0 0]\n\
|
||||||
[ 0 0]\n[ 9 0]\n"
|
[0 0]\n[9 0]\n"
|
||||||
(* The escape buffer is 1024 and the input is 1100 x's, so this is the
|
(* The escape buffer is 1024 and the input is 1100 x's, so this is the
|
||||||
truncation: the ellipsis goes *inside* the quotes, and the count is
|
truncation: the ellipsis goes *inside* the quotes, and the count is
|
||||||
spelled out rather than pasted so that a change to the buffer or to
|
spelled out rather than pasted so that a change to the buffer or to
|
||||||
@ -5210,13 +5210,13 @@ level "1"
|
|||||||
written against did not do, and the real renderer does on purpose. The
|
written against did not do, and the real renderer does on purpose. The
|
||||||
space is a prefix per element rather than a separator between them, so
|
space is a prefix per element rather than a separator between them, so
|
||||||
the open bracket is followed by one — runtime/flan_dyn.c's [render],
|
the open bracket is followed by one — runtime/flan_dyn.c's [render],
|
||||||
pinned by test/dyn_ops.c's own "[ 1 2 3]". And a text *nested* in a
|
pinned by test/dyn_ops.c's own "[1 2 3]". And a text *nested* in a
|
||||||
container is escaped and quoted while the same text printed on its own
|
container is escaped and quoted while the same text printed on its own
|
||||||
is not, which is the third line here: [three] bare, ["three"] inside
|
is not, which is the third line here: [three] bare, ["three"] inside
|
||||||
the vector. Both were red against the expectation below until this was
|
the vector. Both were red against the expectation below until this was
|
||||||
corrected — on LLVM as much as on x86, because neither is a backend's
|
corrected — on LLVM as much as on x86, because neither is a backend's
|
||||||
business. *)
|
business. *)
|
||||||
let dyn_vec_out = "4\n[ 1 2.5 \"three\" true]\n1 2.5 three true \n" in
|
let dyn_vec_out = "4\n[1 2.5 \"three\" true]\n1 2.5 three true \n" in
|
||||||
outputs "dyn: a heterogeneous vector"
|
outputs "dyn: a heterogeneous vector"
|
||||||
"programs/dyn-vec.flan" dyn_vec_out;
|
"programs/dyn-vec.flan" dyn_vec_out;
|
||||||
outputs ~opt:"-O0" "dyn: a heterogeneous vector, -O0"
|
outputs ~opt:"-O0" "dyn: a heterogeneous vector, -O0"
|
||||||
@ -5253,8 +5253,8 @@ level "1"
|
|||||||
that lost a map's keys or values frees something live and the sum
|
that lost a map's keys or values frees something live and the sum
|
||||||
comes out wrong. *)
|
comes out wrong. *)
|
||||||
let dyn_map_out =
|
let dyn_map_out =
|
||||||
"{ :a 1 :b \"two\" :xs [ 1 2 3] :inner { :c 2.5}}\n4\n1\ntwo\n\
|
"{:a 1 :b \"two\" :xs [1 2 3] :inner {:c 2.5}}\n4\n1\ntwo\n\
|
||||||
[ 1 2 3]\n2.5\nnil\ntrue\ntrue\nfalse\nnil\ntrue\n99\n5\n\
|
[1 2 3]\n2.5\nnil\ntrue\ntrue\nfalse\nnil\ntrue\n99\n5\n\
|
||||||
true\nfalse\nfalse\ntrue\n:standalone\n6\ntext key\nvec key\n\
|
true\nfalse\nfalse\ntrue\n:standalone\n6\ntext key\nvec key\n\
|
||||||
true\nfalse\nfalse\n600000\n"
|
true\nfalse\nfalse\n600000\n"
|
||||||
in
|
in
|
||||||
@ -5286,11 +5286,11 @@ level "1"
|
|||||||
found nothing. The 100000 at the end is 50000 instances allocated
|
found nothing. The 100000 at the end is 50000 instances allocated
|
||||||
against one live instance, well past the collector's 1 MiB floor. *)
|
against one live instance, well past the collector's 1 MiB floor. *)
|
||||||
let dyn_class_out =
|
let dyn_class_out =
|
||||||
"#point{ :x 3 :y 4}\n2\n3\n10\ntrue\nfalse\nnil\n:point\n\
|
"#point{:x 3 :y 4}\n2\n3\n10\ntrue\nfalse\nnil\n:point\n\
|
||||||
:circle\nnil\nnil\nnil\ntrue\nfalse\nfalse\ntrue\n40\n12\n5\n\
|
:circle\nnil\nnil\nnil\ntrue\nfalse\nfalse\ntrue\n40\n12\n5\n\
|
||||||
a round thing, keyed by a string\nthe one keyed by a number\n\
|
a round thing, keyed by a string\nthe one keyed by a number\n\
|
||||||
something else\nsomething else\n2\n:circle\n\
|
something else\nsomething else\n2\n:circle\n\
|
||||||
[ #point{ :x 10 :y 4} 99]\n[ #point{ :x 10 :y 4} 99]\n\
|
[#point{:x 10 :y 4} 99]\n[#point{:x 10 :y 4} 99]\n\
|
||||||
a point\nname-of\nnil\n100000\n:point\n"
|
a point\nname-of\nnil\n100000\n:point\n"
|
||||||
in
|
in
|
||||||
outputs "dyn: classes and dispatch" "programs/dyn-class.flan" dyn_class_out;
|
outputs "dyn: classes and dispatch" "programs/dyn-class.flan" dyn_class_out;
|
||||||
@ -5609,15 +5609,15 @@ level "1"
|
|||||||
key. On both backends, because every one of these is a runtime call
|
key. On both backends, because every one of these is a runtime call
|
||||||
whose arguments the two emit separately. *)
|
whose arguments the two emit separately. *)
|
||||||
let slots_out =
|
let slots_out =
|
||||||
"#state{ :pause false :step 3 :speed 1.5 :name \"sand\" :tag :x}\n\
|
"#state{:pause false :step 3 :speed 1.5 :name \"sand\" :tag :x}\n\
|
||||||
true\n-7\n[ 1 2]\n2.5\n9\n6\n12\n3.5\ntrue\n2\n:state\n"
|
true\n-7\n[1 2]\n2.5\n9\n6\n12\n3.5\ntrue\n2\n:state\n"
|
||||||
in
|
in
|
||||||
outputs "dyn: typed class slots" "programs/dyn-class-slots.flan" slots_out;
|
outputs "dyn: typed class slots" "programs/dyn-class-slots.flan" slots_out;
|
||||||
outputs ~x86:true "dyn: typed class slots, --x86"
|
outputs ~x86:true "dyn: typed class slots, --x86"
|
||||||
"programs/dyn-class-slots.flan" slots_out;
|
"programs/dyn-class-slots.flan" slots_out;
|
||||||
outputs "dyn: a package's slot type names its own class"
|
outputs "dyn: a package's slot type names its own class"
|
||||||
"programs/dyn-class-pkg.flan"
|
"programs/dyn-class-pkg.flan"
|
||||||
"#g/seg{ :a #g/pt{ :x 1 :y 2} :b nil :tag :t}\n#pt{ :z 1}\n";
|
"#g/seg{:a #g/pt{:x 1 :y 2} :b nil :tag :t}\n#pt{:z 1}\n";
|
||||||
let slot_trap ?x86 () =
|
let slot_trap ?x86 () =
|
||||||
let exe = compile ?x86 "programs/dyn-slot-trap.flan" in
|
let exe = compile ?x86 "programs/dyn-slot-trap.flan" in
|
||||||
List.iter
|
List.iter
|
||||||
@ -5748,11 +5748,11 @@ level "1"
|
|||||||
write whose dyn tag does not match the element type. The expected
|
write whose dyn tag does not match the element type. The expected
|
||||||
text for mode 0 was captured from the running program. *)
|
text for mode 0 was captured from the running program. *)
|
||||||
let dyn_view_out =
|
let dyn_view_out =
|
||||||
"[ 10 20 30]\n999\n777\n4\n40\n\
|
"[10 20 30]\n999\n777\n4\n40\n\
|
||||||
[ 1 2 3 4]\n100\n400\n\
|
[1 2 3 4]\n100\n400\n\
|
||||||
[ 1.5 2.5 3.5]\n9.5\n\
|
[1.5 2.5 3.5]\n9.5\n\
|
||||||
[ true false]\ntrue\n\
|
[true false]\ntrue\n\
|
||||||
[ 111 222]\n3\n333\n"
|
[111 222]\n3\n333\n"
|
||||||
in
|
in
|
||||||
let dyn_view ?opt ?x86 () =
|
let dyn_view ?opt ?x86 () =
|
||||||
let exe = compile ?opt ?x86 "programs/dyn-view.flan" in
|
let exe = compile ?opt ?x86 "programs/dyn-view.flan" in
|
||||||
|
|||||||
@ -2552,7 +2552,7 @@ let () =
|
|||||||
one wire format, and they moved together, which is what the
|
one wire format, and they moved together, which is what the
|
||||||
note here used to say was still owed. *)
|
note here used to say was still owed. *)
|
||||||
("p", "Point", "(Point {.x 1.5 .y 2.5})");
|
("p", "Point", "(Point {.x 1.5 .y 2.5})");
|
||||||
("xs", "[3 i32]", "[ 10 20 30]");
|
("xs", "[3 i32]", "[10 20 30]");
|
||||||
("flag", "bool", "true");
|
("flag", "bool", "true");
|
||||||
(* The byte's character half, in the three shapes it has. The
|
(* The byte's character half, in the three shapes it has. The
|
||||||
spelling is one [lib/reader.ml]'s [read_byte] accepts, so
|
spelling is one [lib/reader.ml]'s [read_byte] accepts, so
|
||||||
@ -3108,7 +3108,7 @@ let () =
|
|||||||
program. *)
|
program. *)
|
||||||
let r = set "xs" "((:path (1) :code \"(+ 20 5)\"))" in
|
let r = set "xs" "((:path (1) :code \"(+ 20 5)\"))" in
|
||||||
if status r <> "ok" then fail "setting xs[1]: %s" (message r)
|
if status r <> "ok" then fail "setting xs[1]: %s" (message r)
|
||||||
else if value r <> "[ 10 25 30]" then
|
else if value r <> "[10 25 30]" then
|
||||||
fail "setting xs[1] answered %s" (value r);
|
fail "setting xs[1] answered %s" (value r);
|
||||||
|
|
||||||
(* A value that does not fit is refused in the checker's own words,
|
(* A value that does not fit is refused in the checker's own words,
|
||||||
@ -3604,7 +3604,7 @@ let () =
|
|||||||
[grid]'s two writes are the two frames: [main] set element 1
|
[grid]'s two writes are the two frames: [main] set element 1
|
||||||
before calling, [inner] set element 0 after. The value is read
|
before calling, [inner] set element 0 after. The value is read
|
||||||
out of the program's own storage, so both are in it. *)
|
out of the program's own storage, so both are in it. *)
|
||||||
[ ("grid", "[4 i32]", "[ 7 5 0 0]", [ 0; 1 ]);
|
[ ("grid", "[4 i32]", "[7 5 0 0]", [ 0; 1 ]);
|
||||||
("pressure", "i64", "12", [ 0 ]);
|
("pressure", "i64", "12", [ 0 ]);
|
||||||
("label", "string", "\"running\"", [ 1 ]) ]
|
("label", "string", "\"running\"", [ 1 ]) ]
|
||||||
in
|
in
|
||||||
@ -4886,7 +4886,7 @@ let () =
|
|||||||
| Some v -> fail "watch rendered a struct as %s" v
|
| Some v -> fail "watch rendered a struct as %s" v
|
||||||
| None -> fail "the (watch ...) form never wrote a struct");
|
| None -> fail "the (watch ...) form never wrote a struct");
|
||||||
(match List.assoc_opt "row" t with
|
(match List.assoc_opt "row" t with
|
||||||
| Some "[ 1 2 3]" -> ()
|
| Some "[1 2 3]" -> ()
|
||||||
| Some v -> fail "watch rendered a slice as %s" v
|
| Some v -> fail "watch rendered a slice as %s" v
|
||||||
| None -> fail "the (watch ...) form never wrote a slice");
|
| None -> fail "the (watch ...) form never wrote a slice");
|
||||||
(match List.assoc_opt "t2" t with
|
(match List.assoc_opt "t2" t with
|
||||||
@ -4894,7 +4894,7 @@ let () =
|
|||||||
| Some v -> fail "watch rendered a computed i64 as %s" v
|
| Some v -> fail "watch rendered a computed i64 as %s" v
|
||||||
| None -> fail "the (watch ...) form never wrote a scalar");
|
| None -> fail "the (watch ...) form never wrote a scalar");
|
||||||
(match List.assoc_opt "d" t with
|
(match List.assoc_opt "d" t with
|
||||||
| Some "{ :a 1}" -> ()
|
| Some "{:a 1}" -> ()
|
||||||
| Some v -> fail "watch rendered a dyn map as %s" v
|
| Some v -> fail "watch rendered a dyn map as %s" v
|
||||||
| None -> fail "the (watch ...) form never wrote a dyn value");
|
| None -> fail "the (watch ...) form never wrote a dyn value");
|
||||||
(match List.assoc_opt "s" t with
|
(match List.assoc_opt "s" t with
|
||||||
@ -6557,7 +6557,7 @@ let () =
|
|||||||
[ ("n", "i64", "3");
|
[ ("n", "i64", "3");
|
||||||
("label", "string", "\"hello\"");
|
("label", "string", "\"hello\"");
|
||||||
("p", "Point", "(Point {.x 1.5 .y 2.5})");
|
("p", "Point", "(Point {.x 1.5 .y 2.5})");
|
||||||
("xs", "[3 i32]", "[ 10 20 30]");
|
("xs", "[3 i32]", "[10 20 30]");
|
||||||
("flag", "bool", "true");
|
("flag", "bool", "true");
|
||||||
(* And the byte's character half under this backend too: the
|
(* And the byte's character half under this backend too: the
|
||||||
spelling table is the dev runtime's, but the slot the byte
|
spelling table is the dev runtime's, but the slot the byte
|
||||||
@ -6917,8 +6917,8 @@ let () =
|
|||||||
the reason the inspector block above gives — a backend the break loop
|
the reason the inspector block above gives — a backend the break loop
|
||||||
can tell apart is a backend the break loop cannot be trusted on. It was
|
can tell apart is a backend the break loop cannot be trusted on. It was
|
||||||
tellable apart: x86-64 builds an aggregate in its destination, so this
|
tellable apart: x86-64 builds an aggregate in its destination, so this
|
||||||
daemon used to answer [ 2 2 5 1] for the local and [ 3 3 5 1] for the
|
daemon used to answer [2 2 5 1] for the local and [3 3 5 1] for the
|
||||||
global where the LLVM one answered [ 1 1 5 1] for both.
|
global where the LLVM one answered [1 1 5 1] for both.
|
||||||
|
|
||||||
The local is the half that can only be asked here. A half-built local
|
The local is the half that can only be asked here. A half-built local
|
||||||
is invisible to the running program — the name is not in scope until
|
is invisible to the running program — the name is not in scope until
|
||||||
@ -6978,7 +6978,7 @@ let () =
|
|||||||
if status r <> "ok" then fail "%s half-write locals: %s" flag (said r)
|
if status r <> "ok" then fail "%s half-write locals: %s" flag (said r)
|
||||||
else
|
else
|
||||||
(match List.filter (fun (n, _, _) -> n = "v") (triples r "locals") with
|
(match List.filter (fun (n, _, _) -> n = "v") (triples r "locals") with
|
||||||
| [ ("v", "[4 u32]", "[ 1 1 5 1]") ] -> ()
|
| [ ("v", "[4 u32]", "[1 1 5 1]") ] -> ()
|
||||||
| got ->
|
| got ->
|
||||||
fail
|
fail
|
||||||
"%s: a local caught mid-assignment reads %s, not its whole \
|
"%s: a local caught mid-assignment reads %s, not its whole \
|
||||||
@ -7000,7 +7000,7 @@ let () =
|
|||||||
match
|
match
|
||||||
List.filter (fun (n, _, _) -> n = "colors") (triples r "globals")
|
List.filter (fun (n, _, _) -> n = "colors") (triples r "globals")
|
||||||
with
|
with
|
||||||
| [ ("colors", "[4 u32]", "[ 1 1 5 1]") ] -> ()
|
| [ ("colors", "[4 u32]", "[1 1 5 1]") ] -> ()
|
||||||
| got ->
|
| got ->
|
||||||
fail
|
fail
|
||||||
"%s: a global caught mid-assignment reads %s, not its whole \
|
"%s: a global caught mid-assignment reads %s, not its whole \
|
||||||
|
|||||||
@ -1522,12 +1522,12 @@ let () =
|
|||||||
"(defonce v (Vec string) (vec-new string))\n\
|
"(defonce v (Vec string) (vec-new string))\n\
|
||||||
(defn take [d dyn] i32 1)\n\
|
(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (take v))"
|
(defn main [] i32 (take v))"
|
||||||
~needle:"does not cross into dyn yet";
|
~needle:"only when its elements are i64, f64 or bool";
|
||||||
rejects_check "an i32 element is not one of the view's three"
|
rejects_check "an i32 element is not one of the view's three"
|
||||||
"(defonce v (Vec i32) (vec-new i32))\n\
|
"(defonce v (Vec i32) (vec-new i32))\n\
|
||||||
(defn take [d dyn] i32 1)\n\
|
(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (take v))"
|
(defn main [] i32 (take v))"
|
||||||
~needle:"does not cross into dyn yet";
|
~needle:"only when its elements are i64, f64 or bool";
|
||||||
(* A typed (Map K V) is unrelated to item 3 and keeps its own refusal. *)
|
(* A typed (Map K V) is unrelated to item 3 and keeps its own refusal. *)
|
||||||
rejects_check "a typed Map still refuses into dyn"
|
rejects_check "a typed Map still refuses into dyn"
|
||||||
"(defonce m (Map i64 i64) (map-new i64 i64))\n\
|
"(defonce m (Map i64 i64) (map-new i64 i64))\n\
|
||||||
@ -1544,7 +1544,7 @@ let () =
|
|||||||
lifetime one"
|
lifetime one"
|
||||||
"(defn take [d dyn] i32 1)\n\
|
"(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (let [v (vec-new string)] (take v)))"
|
(defn main [] i32 (let [v (vec-new string)] (take v)))"
|
||||||
~needle:"does not cross into dyn yet";
|
~needle:"only when its elements are i64, f64 or bool";
|
||||||
(* ── The lifetime guard, added on review ─────────────────────────
|
(* ── The lifetime guard, added on review ─────────────────────────
|
||||||
A local, a parameter and a temporary all answer false to
|
A local, a parameter and a temporary all answer false to
|
||||||
[permanent_root], and each gets the same message rather than "cannot be
|
[permanent_root], and each gets the same message rather than "cannot be
|
||||||
@ -1552,16 +1552,16 @@ let () =
|
|||||||
rejects_check "a local Vec does not view into dyn — its frame ends"
|
rejects_check "a local Vec does not view into dyn — its frame ends"
|
||||||
"(defn take [d dyn] i32 1)\n\
|
"(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (let [v (vec-new i64)] (take v)))"
|
(defn main [] i32 (let [v (vec-new i64)] (take v)))"
|
||||||
~needle:"does not cross into dyn as a view here";
|
~needle:"only when it is a global";
|
||||||
rejects_check "a Vec parameter does not view into dyn"
|
rejects_check "a Vec parameter does not view into dyn"
|
||||||
"(defn take [d dyn] i32 1)\n\
|
"(defn take [d dyn] i32 1)\n\
|
||||||
(defn give [v (Vec i64)] i32 (take v))\n\
|
(defn give [v (Vec i64)] i32 (take v))\n\
|
||||||
(defn main [] i32 0)"
|
(defn main [] i32 0)"
|
||||||
~needle:"does not cross into dyn as a view here";
|
~needle:"only when it is a global";
|
||||||
rejects_check "a fixed array local does not view into dyn"
|
rejects_check "a fixed array local does not view into dyn"
|
||||||
"(defn take [d dyn] i32 1)\n\
|
"(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (let [a (array 4 i64)] (take a)))"
|
(defn main [] i32 (let [a (array 4 i64)] (take a)))"
|
||||||
~needle:"does not cross into dyn as a view here";
|
~needle:"only when it is a global";
|
||||||
(* A slice cut from a global is permanent; the same slice expression
|
(* A slice cut from a global is permanent; the same slice expression
|
||||||
rebound to a local first loses the trace back to it and is refused —
|
rebound to a local first loses the trace back to it and is refused —
|
||||||
conservative rather than wrong, and the message says what does work. *)
|
conservative rather than wrong, and the message says what does work. *)
|
||||||
@ -1573,7 +1573,7 @@ let () =
|
|||||||
"(defonce xs [3 i64])\n\
|
"(defonce xs [3 i64])\n\
|
||||||
(defn take [d dyn] i32 1)\n\
|
(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (let [s (slice xs 0 3)] (take s)))"
|
(defn main [] i32 (let [s (slice xs 0 3)] (take s)))"
|
||||||
~needle:"does not cross into dyn as a view here";
|
~needle:"only when it is a global";
|
||||||
(* An element of a global is permanent only when the global is an ARRAY.
|
(* An element of a global is permanent only when the global is an ARRAY.
|
||||||
An array's elements are inside the global's own storage; a slice's are
|
An array's elements are inside the global's own storage; a slice's are
|
||||||
not — a global [[T]] holds ptr+len and nothing more, and what they
|
not — a global [[T]] holds ptr+len and nothing more, and what they
|
||||||
@ -1590,7 +1590,7 @@ let () =
|
|||||||
"(defonce sv [(Vec i64)])\n\
|
"(defonce sv [(Vec i64)])\n\
|
||||||
(defn take [d dyn] i32 1)\n\
|
(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (take (at sv 0)))"
|
(defn main [] i32 (take (at sv 0)))"
|
||||||
~needle:"does not cross into dyn as a view here";
|
~needle:"only when it is a global";
|
||||||
(* [(at g i j)] is ONE typed node holding both indices, not two nested
|
(* [(at g i j)] is ONE typed node holding both indices, not two nested
|
||||||
ones, so a guard that reads the target's type alone sees level zero and
|
ones, so a guard that reads the target's type alone sees level zero and
|
||||||
nothing after it. These two rows pin the multi-index spelling on both
|
nothing after it. These two rows pin the multi-index spelling on both
|
||||||
@ -1605,7 +1605,7 @@ let () =
|
|||||||
"(defonce g [2 [[3 i64]]])\n\
|
"(defonce g [2 [[3 i64]]])\n\
|
||||||
(defn take [d dyn] i32 1)\n\
|
(defn take [d dyn] i32 1)\n\
|
||||||
(defn main [] i32 (take (at g 0 1)))"
|
(defn main [] i32 (take (at g 0 1)))"
|
||||||
~needle:"does not cross into dyn as a view here";
|
~needle:"only when it is a global";
|
||||||
(* A Vec behind a Ptr is refused even though some Ptrs really are
|
(* A Vec behind a Ptr is refused even though some Ptrs really are
|
||||||
heap-durable — the checker cannot tell this one from a Ptr taken off a
|
heap-durable — the checker cannot tell this one from a Ptr taken off a
|
||||||
local, and admitting one admits the other. *)
|
local, and admitting one admits the other. *)
|
||||||
@ -1613,7 +1613,7 @@ let () =
|
|||||||
"(defn take [d dyn] i32 1)\n\
|
"(defn take [d dyn] i32 1)\n\
|
||||||
(defn use [p (Ptr (Vec i64))] i32 (take (deref p)))\n\
|
(defn use [p (Ptr (Vec i64))] i32 (take (deref p)))\n\
|
||||||
(defn main [] i32 0)"
|
(defn main [] i32 0)"
|
||||||
~needle:"does not cross into dyn as a view here";
|
~needle:"only when it is a global";
|
||||||
(* A bracket *literal* is not a typed container yet, and where a dyn is
|
(* A bracket *literal* is not a typed container yet, and where a dyn is
|
||||||
wanted it builds the runtime's own vec instead — the lowering the map
|
wanted it builds the runtime's own vec instead — the lowering the map
|
||||||
literal's values ride on, and what makes {:xs [1 2]} mean what it
|
literal's values ride on, and what makes {:xs [1 2]} mean what it
|
||||||
@ -3656,10 +3656,10 @@ let () =
|
|||||||
"grid" ~ty:"[2 [3 u8]]" ~zeroed:false;
|
"grid" ~ty:"[2 [3 u8]]" ~zeroed:false;
|
||||||
rejects_check "a three-element array-fill defonce is the dyn reading"
|
rejects_check "a three-element array-fill defonce is the dyn reading"
|
||||||
"(defonce xs (array-fill [3] (i64 1))) (defn f [] ())"
|
"(defonce xs (array-fill [3] (i64 1))) (defn f [] ())"
|
||||||
~needle:"does not cross into dyn as a view here";
|
~needle:"only when it is a global";
|
||||||
rejects_check "and its element type is asked about first"
|
rejects_check "and its element type is asked about first"
|
||||||
"(defonce grid (array-fill [2 3] 255)) (defn f [] ())"
|
"(defonce grid (array-fill [2 3] 255)) (defn f [] ())"
|
||||||
~needle:"does not cross into dyn yet";
|
~needle:"only when its elements are i64, f64 or bool";
|
||||||
(* A defconst is not a second path to it: its value is what the linker
|
(* 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. *)
|
writes into the image, and a fill is a loop. *)
|
||||||
rejects_check "array-fill is not a constant's value"
|
rejects_check "array-fill is not a constant's value"
|
||||||
|
|||||||
@ -87,11 +87,11 @@ let () =
|
|||||||
(* A struct, nested, with a fixed array inside it. *)
|
(* A struct, nested, with a fixed array inside it. *)
|
||||||
value "a struct" "(.pos b)" "(V {.x 1.5 .y 0})";
|
value "a struct" "(.pos b)" "(V {.x 1.5 .y 0})";
|
||||||
value "a nested struct" "b"
|
value "a nested struct" "b"
|
||||||
"(Blob {.id 7 .name \"sandy \\\"quoted\\\"\" .pos (V {.x 1.5 .y 0}) .tags [ 0 42 0]})";
|
"(Blob {.id 7 .name \"sandy \\\"quoted\\\"\" .pos (V {.x 1.5 .y 0}) .tags [0 42 0]})";
|
||||||
value "a fixed array" "arr" "[ 0 0 9 0]";
|
value "a fixed array" "arr" "[0 0 9 0]";
|
||||||
(* A slice's length is not known until it runs, so this one renders
|
(* A slice's length is not known until it runs, so this one renders
|
||||||
through a loop rather than by unrolling. *)
|
through a loop rather than by unrolling. *)
|
||||||
value "a slice" "(slice (.tags b) 0 3)" "[ 0 42 0]";
|
value "a slice" "(slice (.tags b) 0 3)" "[0 42 0]";
|
||||||
(* An enum's members are erased to i32 before the backend sees them, so
|
(* An enum's members are erased to i32 before the backend sees them, so
|
||||||
the name is recovered from the checker's table. *)
|
the name is recovered from the checker's table. *)
|
||||||
value "an enum" "col" ":blue";
|
value "an enum" "col" ":blue";
|
||||||
|
|||||||
@ -556,6 +556,106 @@ let () =
|
|||||||
fail "twin files: %s" d.Loc.dmsg
|
fail "twin files: %s" d.Loc.dmsg
|
||||||
| exception e -> fail "twin files: %s" (Printexc.to_string e)
|
| exception e -> fail "twin files: %s" (Printexc.to_string e)
|
||||||
|
|
||||||
|
(* ── A refusal's fix is spelled in the file's own syntax, and compiles ── *)
|
||||||
|
|
||||||
|
let refused name text needles =
|
||||||
|
let f = Filename.concat scratch name in
|
||||||
|
write f text;
|
||||||
|
match Front.checked f with
|
||||||
|
| _ -> fail "%s checked" name
|
||||||
|
| exception (Loc.Error d | Loc.Errors [ d ]) ->
|
||||||
|
List.iter
|
||||||
|
(fun n ->
|
||||||
|
if not (Test_support.contains d.Loc.dmsg n) then
|
||||||
|
fail "%s: wanted %S in: %s" name n d.Loc.dmsg)
|
||||||
|
needles
|
||||||
|
| exception e -> fail "%s: %s" name (diag_text e)
|
||||||
|
|
||||||
|
let checks name text =
|
||||||
|
let f = Filename.concat scratch name in
|
||||||
|
write f text;
|
||||||
|
match Front.checked f with
|
||||||
|
| _ -> ()
|
||||||
|
| exception e -> fail "%s does not check: %s" name (diag_text e)
|
||||||
|
|
||||||
|
let () =
|
||||||
|
let poke_fln = "fn poke(coll) -> dyn\n coll[0] = 99\n coll\n\n" in
|
||||||
|
let poke_flan = "(defn poke [coll] dyn (set (at coll 0) 99) coll)\n" in
|
||||||
|
(* A typed local is not a global, so no dyn value may see into it. *)
|
||||||
|
refused "view-local.fln"
|
||||||
|
(poke_fln ^ "fn main() -> ()\n let a: [4 i64] = [6 2 4 9]\n poke(a)\n")
|
||||||
|
[ "a is a [4 i64], and a dyn value is wanted here";
|
||||||
|
"a local, a parameter or a temporary";
|
||||||
|
"as in let a: dyn = [...]" ];
|
||||||
|
refused "view-local.flan"
|
||||||
|
(poke_flan ^ "(defn main [] () (let [a (array 4 i64)] (poke a)))\n")
|
||||||
|
[ "a is a [4 i64]"; "as in (let [a (the dyn [...])] ...)" ];
|
||||||
|
refused "view-temp.flan"
|
||||||
|
(poke_flan ^ "(defn main [] () (poke (array 4 i64)))\n")
|
||||||
|
[ "This is a [4 i64]"; "as in (the dyn [...])" ];
|
||||||
|
(* An unannotated literal is [4 i32], whose elements no view carries. *)
|
||||||
|
refused "view-elem.fln"
|
||||||
|
(poke_fln ^ "fn main() -> ()\n let d = [6 2 4 9]\n poke(d)\n")
|
||||||
|
[ "d is a [4 i32]"; "only when its elements are i64, f64 or bool, and these are i32";
|
||||||
|
"as in let d: dyn = [...]" ];
|
||||||
|
(* A parameter is made by the caller, so its fix is its declaration. *)
|
||||||
|
refused "view-param.fln"
|
||||||
|
"fn take(d) -> i32 = 1\n\nfn give(n: i32, v: [4 i64]) -> i32\n take(v)\n\n\
|
||||||
|
fn main() -> i32 = 0\n"
|
||||||
|
[ "v is a [4 i64] parameter"; "Declare v as dyn in give's parameters: v: dyn" ];
|
||||||
|
refused "view-param.flan"
|
||||||
|
"(defn take [d dyn] i32 1)\n(defn give [n i32 v (Vec i64)] i32 (take v))\n\
|
||||||
|
(defn main [] i32 0)\n"
|
||||||
|
[ "v is a (Vec i64) parameter"; "Declare v as dyn in give's parameters: v dyn" ];
|
||||||
|
checks "view-param-fix.fln"
|
||||||
|
"fn take(d) -> i32 = 1\n\nfn give(n: i32, v: dyn) -> i32\n take(v)\n\n\
|
||||||
|
fn main() -> i32 = 0\n";
|
||||||
|
(* A global's fix redefines it, in the form it was defined with. *)
|
||||||
|
let show_flan = "(defn show [d dyn] i32 1)\n" in
|
||||||
|
refused "view-global.flan"
|
||||||
|
("(defonce gs [2 i32] [1 2])\n" ^ show_flan ^ "(defn main [] i32 (show gs))\n")
|
||||||
|
[ "gs is a [2 i32]"; "as in (defonce gs dyn [...])" ];
|
||||||
|
refused "view-global-def.flan"
|
||||||
|
("(def gs [2 i32] [1 2])\n" ^ show_flan ^ "(defn main [] i32 (show gs))\n")
|
||||||
|
[ "as in (def gs dyn [...])" ];
|
||||||
|
refused "view-global.fln"
|
||||||
|
"once gs: [2 i32] = [1 2]\n\nfn show(d) -> i32 = 1\n\nfn main() -> i32 = show(gs)\n"
|
||||||
|
[ "gs is a [2 i32]"; "as in once gs: dyn = [...]" ];
|
||||||
|
checks "view-global-fix.flan"
|
||||||
|
("(defonce gs dyn [1 2])\n(def hs dyn [1 2])\n" ^ show_flan
|
||||||
|
^ "(defn main [] i32 (show gs) (show hs))\n");
|
||||||
|
checks "view-global-fix.fln"
|
||||||
|
"once gs: dyn = [1 2]\n\nfn show(d) -> i32 = 1\n\nfn main() -> i32 = show(gs)\n";
|
||||||
|
(* The fix is spelled in the syntax the code was sent in, not the one the
|
||||||
|
file's name implies: an editor request from an indented buffer. *)
|
||||||
|
Source.with_code ~syntax:Source.Indented ~at:None (fun () ->
|
||||||
|
refused "unit-tail-request.flan"
|
||||||
|
"(defn f [coll] dyn (let [i 1] (while (< i 3) (++ i))))\n(defn main [] () (f 1))\n"
|
||||||
|
[ "fn f(...) -> ()" ]);
|
||||||
|
(* The fix both of them name. *)
|
||||||
|
checks "view-fix.fln"
|
||||||
|
(poke_fln ^ "fn main() -> ()\n let d: dyn = [6 2 4 9]\n poke(d)\n poke(the(dyn, [1 2]))\n");
|
||||||
|
checks "view-fix.flan"
|
||||||
|
(poke_flan
|
||||||
|
^ "(defn main [] () (let [a (the dyn [6 2 4 9])] (poke a)) (poke (the dyn [1 2])))\n");
|
||||||
|
(* A dyn function whose body ends in a while gives no value. *)
|
||||||
|
let loop_fln ret tail =
|
||||||
|
"fn f(coll) -> " ^ ret ^ "\n let i = 1\n while i < 3\n ++(i)\n" ^ tail
|
||||||
|
^ "\nfn main() -> ()\n f(1)\n"
|
||||||
|
in
|
||||||
|
refused "unit-tail.fln" (loop_fln "dyn" "")
|
||||||
|
[ "f is declared to return dyn, but the last form of its body gives no value";
|
||||||
|
"fn f(...) -> ()" ];
|
||||||
|
refused "unit-tail.flan"
|
||||||
|
"(defn f [coll] dyn (let [i 1] (while (< i 3) (++ i))))\n(defn main [] () (f 1))\n"
|
||||||
|
[ "f is declared to return dyn"; "(defn f [...] () ...)" ];
|
||||||
|
checks "unit-tail-nil.fln" (loop_fln "dyn" " nil\n");
|
||||||
|
checks "unit-tail-unit.fln" (loop_fln "()" "");
|
||||||
|
(* A unit argument deeper in the last form is about that argument. *)
|
||||||
|
refused "unit-arg.flan"
|
||||||
|
"(defn g [x dyn] dyn x)\n(defn f [coll] dyn (g (println 1)))\n(defn main [] () (f 1))\n"
|
||||||
|
[ "() does not box into dyn" ]
|
||||||
|
|
||||||
(* ── Both directions of an import, on both backends ────────────────── *)
|
(* ── Both directions of an import, on both backends ────────────────── *)
|
||||||
|
|
||||||
let run_both path want =
|
let run_both path want =
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
4
|
4
|
||||||
:point
|
:point
|
||||||
nil
|
nil
|
||||||
#point{ :x 3 :y 4}
|
#point{:x 3 :y 4}
|
||||||
9
|
9
|
||||||
something else
|
something else
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@ -770,11 +770,11 @@ once, for every method; a method has no return slot; and every parameter of both
|
|||||||
4
|
4
|
||||||
:point
|
:point
|
||||||
nil
|
nil
|
||||||
#point{ :x 3 :y 4}
|
#point{:x 3 :y 4}
|
||||||
9
|
9
|
||||||
something else</code></pre>
|
something else</code></pre>
|
||||||
|
|
||||||
<p>An instance renders as <code>#point{ :x 3 :y 4}</code>, Clojure's spelling for a
|
<p>An instance renders as <code>#point{:x 3 :y 4}</code>, Clojure's spelling for a
|
||||||
record, and the tag is why two instances of one class compare by their slots while an
|
record, and the tag is why two instances of one class compare by their slots while an
|
||||||
instance is never equal to a plain map with the same entries. A dispatch that matches
|
instance is never equal to a plain map with the same entries. A dispatch that matches
|
||||||
no method signals <code>NoMethod</code>, carrying the generic's name and the value
|
no method signals <code>NoMethod</code>, carrying the generic's name and the value
|
||||||
@ -2079,10 +2079,10 @@ there too, from its tag. What comes back looks like this:</p>
|
|||||||
<pre><code class="sh">big 18446744073709551615
|
<pre><code class="sh">big 18446744073709551615
|
||||||
col :blue
|
col :blue
|
||||||
(.pos b) (V {.x 1.5 .y 0})
|
(.pos b) (V {.x 1.5 .y 0})
|
||||||
b (Blob {.id 7 .name "sandy \"quoted\"" .pos (V {.x 1.5 .y 0}) .tags [ 0 42 0]})
|
b (Blob {.id 7 .name "sandy \"quoted\"" .pos (V {.x 1.5 .y 0}) .tags [0 42 0]})
|
||||||
(slice (.tags b) 0 3) [ 0 42 0]
|
(slice (.tags b) 0 3) [0 42 0]
|
||||||
(rl/get-color 0x11223344) (rl/Color {.r 17 .g 34 .b 51 .a 68})
|
(rl/get-color 0x11223344) (rl/Color {.r 17 .g 34 .b 51 .a 68})
|
||||||
sim/grid [ [ 0 0 0 0 0 0 0 0 ...] [ 0 ... ] ...]</code></pre>
|
sim/grid [[0 0 0 0 0 0 0 0 ...] [0 ...] ...]</code></pre>
|
||||||
|
|
||||||
<p>A pointer is never followed; it renders as <code><ptr></code>. Following one
|
<p>A pointer is never followed; it renders as <code><ptr></code>. Following one
|
||||||
would make the walk cycle, and dereferencing a pointer a REPL was handed is not safe.
|
would make the walk cycle, and dereferencing a pointer a REPL was handed is not safe.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user