Dyn and typed containers print as [1 2 3] and {:a 1}, a refused dyn view names the value and a dyn fix in the file's own syntax, and a dyn function whose body gives no value says so by name
This commit is contained in:
parent
94134da25a
commit
4f060bb648
3
TODO.org
3
TODO.org
@ -304,9 +304,6 @@ compare bools, and a match over a bool takes true/false arms, exhaustive without
|
||||
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.
|
||||
|
||||
** 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
|
||||
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.
|
||||
|
||||
@ -2211,10 +2211,10 @@ allocator the language does not have.
|
||||
big 18446744073709551615
|
||||
col :blue
|
||||
(.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]})
|
||||
(slice (.tags b) 0 3) [ 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]
|
||||
(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:
|
||||
@ -2625,7 +2625,7 @@ The half the shadow stack was built for. `(:op "locals" :frame N)` answers what
|
||||
(:op "locals" :frame 0) → (:status "ok" :frame "look"
|
||||
:locals (("n" "i64" "3") ("label" "string" "\"hello\"")
|
||||
("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")))
|
||||
```
|
||||
|
||||
@ -2706,7 +2706,7 @@ with nowhere to ask it.
|
||||
|
||||
```
|
||||
(: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))
|
||||
("label" "string" "\"running\"" (1)))
|
||||
: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
|
||||
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.
|
||||
|
||||
That is the whole of it. `C-c C-c` on `step` adds or removes a watched value
|
||||
|
||||
@ -175,7 +175,7 @@ reply without a daemon behind them, and so that this file names
|
||||
(1+ end)))))
|
||||
|
||||
(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))
|
||||
(while (not done)
|
||||
(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
|
||||
;; an expression", nested two deep with a string that has escaped quotes in it
|
||||
;; 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))
|
||||
(kids (plist-get n :children)))
|
||||
(test-flan--check "every field of a nested struct"
|
||||
@ -75,14 +75,14 @@
|
||||
'(0 1 2))))
|
||||
|
||||
;; [ 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 "indexed from zero"
|
||||
(equal (mapcar #'car (plist-get n :children)) '(0 1 2))))
|
||||
|
||||
;; [ [ 0 0] [ 1 ...] ...] — span truncation at both levels, which is what
|
||||
;; 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)))
|
||||
(test-flan--check "a trailing ... is truncation, not an element"
|
||||
(and (= (length kids) 2) (plist-get n :truncated)))
|
||||
@ -823,7 +823,7 @@ would be overwritten. Look again and re-do the edit")
|
||||
(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})})"))
|
||||
(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"
|
||||
(funcall round "(some (V {.x 1 .y (some 2)}))")))
|
||||
|
||||
@ -863,8 +863,8 @@ would be overwritten. Look again and re-do the edit")
|
||||
(and (null (car d))
|
||||
(string-match-p "does not add or rename" (cdr d))))))
|
||||
|
||||
(let ((d (flan-inspect--diff (flan-inspect-parse "[ 1 2 3]")
|
||||
(flan-inspect-parse "[ 1 2 3 4]"))))
|
||||
(let ((d (flan-inspect--diff (flan-inspect-parse "[1 2 3]")
|
||||
(flan-inspect-parse "[1 2 3 4]"))))
|
||||
(test-flan--check "an element added is a change to the container, and refused"
|
||||
(and (null (car d))
|
||||
(string-match-p "3 elements and the buffer has 4" (cdr d)))))
|
||||
@ -1763,7 +1763,7 @@ would be overwritten. Look again and re-do the edit")
|
||||
(list :fn "main" :loc "g.flan:30:1"))
|
||||
;; Ordered as the daemon orders it: by the innermost frame
|
||||
;; 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))
|
||||
("label" "string" "\"running\"" (1)))))
|
||||
(buf (test-flan--cnr state))
|
||||
|
||||
139
lib/check.ml
139
lib/check.ml
@ -3451,11 +3451,50 @@ let view_elem (t : Types.t) : int64 option =
|
||||
let view_elem_lit loc (k : int64) =
|
||||
mk loc (Types.Int Types.I32) (Tast.Int (k, Types.I32))
|
||||
|
||||
let view_not_yet loc (container : Types.t) (elem : Types.t) =
|
||||
no_dyn_yet loc ~into:true container
|
||||
(* A fix is spelled in the syntax of the file the mistake is in: the checker
|
||||
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) = Filename.check_suffix loc.Loc.file ".fln"
|
||||
|
||||
(* 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
|
||||
let subject, fix =
|
||||
match view_subject e with
|
||||
| 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 ->
|
||||
( 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
|
||||
". A container view carries i64, f64 or bool elements, and %s is not \
|
||||
one of them"
|
||||
"A dyn value can see into a typed container only when its elements \
|
||||
are i64, f64 or bool, and these are %s"
|
||||
(Types.to_string elem))
|
||||
|
||||
(* M2 item 3's second guard, added on review: a view's descriptor holds an
|
||||
@ -3539,20 +3578,11 @@ let rec permanent_root (e : Tast.expr) : bool =
|
||||
| Tast.Prim (Tast.Slice, [ target; _; _ ]) -> permanent_root target
|
||||
| _ -> false
|
||||
|
||||
let view_not_permanent loc (container : Types.t) =
|
||||
Loc.failk "check/dyn-view-lifetime" loc
|
||||
"%s does not cross into dyn as a view here — its storage is not known \
|
||||
to outlive the view, and a view is exactly as stale-safe as the thing \
|
||||
it is a view of, no more and no less. A global's storage does outlive \
|
||||
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)
|
||||
let view_not_permanent loc (e : Tast.expr) =
|
||||
view_refusal "check/dyn-view-lifetime" loc e
|
||||
"A dyn value can see into a typed container only when it is a global: a \
|
||||
local, a parameter or a temporary can be gone while the dyn value still \
|
||||
points at it"
|
||||
|
||||
(* 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
|
||||
@ -3860,15 +3890,15 @@ let box loc (e : Tast.expr) : Tast.expr =
|
||||
both, and they share [flan_dyn_view_flat]. *)
|
||||
(* The element check runs before the lifetime one in all three arms, and
|
||||
the order is load-bearing rather than incidental: the lifetime message
|
||||
points at [(defonce g ...)] as the spelling that works, and for an
|
||||
element type no view can carry — a string, an i32 — the global spelling
|
||||
is refused too, so the wrong order hands the programmer advice that
|
||||
fails when they take it. Whichever refusal is unconditional wins. *)
|
||||
says a global can be seen into, and for an element type no view can
|
||||
carry — a string, an i32 — a global is refused too, so the wrong order
|
||||
hands the programmer a reason that is false for their case. Whichever
|
||||
refusal is unconditional wins. *)
|
||||
| Types.Vec elem ->
|
||||
(match view_elem elem with
|
||||
| None -> view_not_yet loc e.Tast.ty elem
|
||||
| None -> view_not_yet loc e elem
|
||||
| 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 ])
|
||||
(* 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
|
||||
@ -3881,15 +3911,15 @@ let box loc (e : Tast.expr) : Tast.expr =
|
||||
(Types.to_string e.Tast.ty) (Types.to_string elem)
|
||||
| Types.Slice (Types.Mut, elem) ->
|
||||
(match view_elem elem with
|
||||
| None -> view_not_yet loc e.Tast.ty elem
|
||||
| None -> view_not_yet loc e elem
|
||||
| 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 ])
|
||||
| Types.Array (n, elem) ->
|
||||
(match view_elem elem with
|
||||
| None -> view_not_yet loc e.Tast.ty elem
|
||||
| None -> view_not_yet loc e elem
|
||||
| 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; mk loc dyn_i64 (Tast.Int (n, Types.I64)); view_elem_lit loc k ])
|
||||
@ -14765,10 +14795,61 @@ let rec check_fn env (fn : Ast.fn) : Tast.fn =
|
||||
let is_defer (e : Ast.expr) =
|
||||
match e.Ast.e with Ast.Defer _ -> true | _ -> false
|
||||
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
|
||||
| [ last ] ->
|
||||
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 ->
|
||||
ctx.defer_ok <- true;
|
||||
let x = check ctx x in
|
||||
|
||||
@ -353,7 +353,7 @@ let rec render ?(refuse = print_refusal) c depth (e : Tast.expr) : Tast.expr lis
|
||||
let v =
|
||||
{ Tast.e = Tast.Prim (Tast.At, [ e; i32 i ]); ty = t; loc }
|
||||
in
|
||||
lit " " :: render c (depth + 1) v))
|
||||
(if i = 0 then [] else [ lit " " ]) @ render c (depth + 1) v))
|
||||
in
|
||||
[ do_ ((lit "[" :: parts)
|
||||
@ (if Int64.to_int n > shown then [ lit " ..." ] else [])
|
||||
@ -377,6 +377,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) ]);
|
||||
ty = t; loc }
|
||||
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 =
|
||||
unit_
|
||||
(Tast.Set
|
||||
@ -391,7 +401,7 @@ let rec render ?(refuse = print_refusal) c depth (e : Tast.expr) : Tast.expr lis
|
||||
[ lit "[";
|
||||
unit_
|
||||
(Tast.While
|
||||
(cond, lit " " :: render c (depth + 1) elem, [ step ]));
|
||||
(cond, sep :: render c (depth + 1) elem, [ step ]));
|
||||
lit "]" ])) ]
|
||||
(* 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
|
||||
|
||||
@ -555,11 +555,10 @@ static inline const char *tag_of(flan_dyn v) {
|
||||
* bool the word true / false
|
||||
* text bare at the top level, hi / "a b"
|
||||
* 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
|
||||
* lib/render.ml's slice loop emits and what a Flan program prints today, and
|
||||
* an acceptance test comparing the two would notice a tidier answer.
|
||||
* A space between elements and none inside the brackets, the same as
|
||||
* lib/render.ml's slice loop: an acceptance test compares the two.
|
||||
*
|
||||
* 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);
|
||||
return;
|
||||
}
|
||||
/* The map prints in edn's shape with the vec's spacing: a space before
|
||||
* every element, 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
|
||||
* insertion order, which is the only order the representation has. */
|
||||
/* The map prints in edn's shape with the vec's spacing: a space between
|
||||
* elements, key and value alike, so {:a 1 :b 2} sits beside the vec's
|
||||
* [1 2 3]. Entries come out in insertion order, which is the only order
|
||||
* the representation has. */
|
||||
case FLAN_DYN_TAG_MAP: {
|
||||
flan_obj *o = dyn_obj(v);
|
||||
int64_t i;
|
||||
/* 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. */
|
||||
if (o->u.v.klass != NULL) {
|
||||
emit(w, "#");
|
||||
@ -697,7 +696,7 @@ static void render(dyn_sink w, flan_dyn v, int depth, int nested) {
|
||||
}
|
||||
emit(w, "{");
|
||||
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);
|
||||
emit(w, " ");
|
||||
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;
|
||||
emit(w, "[");
|
||||
for (i = 0; i < n; i++) {
|
||||
emit(w, " ");
|
||||
if (i > 0) emit(w, " ");
|
||||
if (o->kind == OBJ_VIEW)
|
||||
render(w, view_box(o->u.view.elem,
|
||||
(const uint8_t *)view_base(o)
|
||||
@ -818,12 +817,12 @@ static void say_render(sayer *s, flan_dyn v, int depth) {
|
||||
}
|
||||
say_puts(s, "{");
|
||||
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_puts(s, " ");
|
||||
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;
|
||||
}
|
||||
default: {
|
||||
@ -832,7 +831,7 @@ static void say_render(sayer *s, flan_dyn v, int depth) {
|
||||
if (depth >= 2) { say_puts(s, "[...]"); return; }
|
||||
say_puts(s, "[");
|
||||
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)
|
||||
say_render(s,
|
||||
view_box(o->u.view.elem,
|
||||
@ -842,7 +841,7 @@ static void say_render(sayer *s, flan_dyn v, int depth) {
|
||||
else
|
||||
say_render(s, o->u.v.items[i], depth + 1);
|
||||
}
|
||||
say_puts(s, i < n ? " ...]" : "]");
|
||||
say_puts(s, i == n ? "]" : i > 0 ? " ...]" : "...]");
|
||||
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
|
||||
* 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
|
||||
* [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
|
||||
* 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(2));
|
||||
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
|
||||
level. That is flan_rt.c's rule and the two have to agree, because the
|
||||
REPL parses the printed form back. */
|
||||
FDYN_push(strs, text("x"));
|
||||
FDYN_push(strs, text("a b"));
|
||||
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. */
|
||||
{
|
||||
flan_dyn outer = flan_dyn_vec_new();
|
||||
flan_dyn_root_push(&outer);
|
||||
FDYN_push(outer, nums);
|
||||
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);
|
||||
}
|
||||
prints(flan_dyn_vec_new(), "[]");
|
||||
@ -440,14 +440,14 @@ static void view(void) {
|
||||
buf[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");
|
||||
prints(flat, "[ 10 20 99 7]");
|
||||
prints(flat, "[10 20 99 7]");
|
||||
|
||||
/* 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
|
||||
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
|
||||
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. */
|
||||
{
|
||||
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
|
||||
instances must behave exactly as they did before any of this existed. */
|
||||
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(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");
|
||||
check(num(slot(p, "x")) == 1, "a kept slot keeps its value");
|
||||
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. */
|
||||
check(truth(flan_dyn_eq(flan_dyn_class_of(p),
|
||||
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,
|
||||
"a lost slot reads as absent");
|
||||
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 ──
|
||||
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
|
||||
instance has to be indistinguishable from a freshly constructed one, or
|
||||
[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 ──
|
||||
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));
|
||||
define("point", "x\ny\nz\nzz");
|
||||
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,
|
||||
"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
|
||||
;;;; key
|
||||
;;;; 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
|
||||
;;;; class of its first argument, which is Common Lisp's; a defmulti's body IS
|
||||
|
||||
@ -26,15 +26,28 @@
|
||||
{:where (ordered? $t)}
|
||||
(let [i 1
|
||||
length (length coll)]
|
||||
(while (and (< i length))
|
||||
(while (< i length)
|
||||
(let [j i]
|
||||
(while (and (> j 0)
|
||||
(< (at coll j) (at coll (dec j))))
|
||||
(let [temp (at coll j)]
|
||||
(set (at coll j) (at coll (dec j)))
|
||||
(set (at coll (dec j)) temp))
|
||||
(-- j)))
|
||||
(++ i))))
|
||||
(-- j))
|
||||
(++ i)))))
|
||||
|
||||
(defn insertion-sort-dyn [coll dyn] ()
|
||||
(let [i 1
|
||||
length (length coll)]
|
||||
(while (< i length)
|
||||
(let [j i]
|
||||
(while (and (> j 0)
|
||||
(< (at coll j) (at coll (dec j))))
|
||||
(let [temp (at coll j)]
|
||||
(set (at coll j) (at coll (dec j)))
|
||||
(set (at coll (dec j)) temp))
|
||||
(-- j))
|
||||
(++ i)))))
|
||||
|
||||
(defn main [] i32 0)
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ fn insertion-sort(coll: [$t]) -> () where ordered?($t)
|
||||
--(j)
|
||||
++(i)
|
||||
|
||||
fn insertion-sort-dyn(coll)
|
||||
fn insertion-sort-dyn(coll) -> ()
|
||||
let i = 1
|
||||
let length = length(coll)
|
||||
while i < length
|
||||
|
||||
@ -593,8 +593,8 @@ let () =
|
||||
(* 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. *)
|
||||
let mixed_out =
|
||||
"3\n301\n2.5\n18446744073709551615\n[ 10 \"Hi\"]\n[ nil 1]\n2\n3\n4\n\
|
||||
3\n1\n9000000004\n[ :a \"b\" 3]\n\
|
||||
"3\n301\n2.5\n18446744073709551615\n[10 \"Hi\"]\n[nil 1]\n2\n3\n4\n\
|
||||
3\n1\n9000000004\n[:a \"b\" 3]\n\
|
||||
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 ~x86:true "mixed array literals and the, x86"
|
||||
@ -713,10 +713,10 @@ let () =
|
||||
let println_out =
|
||||
"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\
|
||||
(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\
|
||||
(D1 {.d (D2 {.d (D3 {.d (D4 {.d (D5 {.n ...})})})})})\n[ 0 0]\n\
|
||||
[ 0 0]\n[ 9 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\
|
||||
(D1 {.d (D2 {.d (D3 {.d (D4 {.d (D5 {.n ...})})})})})\n[0 0]\n\
|
||||
[0 0]\n[9 0]\n"
|
||||
(* 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
|
||||
spelled out rather than pasted so that a change to the buffer or to
|
||||
@ -5190,13 +5190,13 @@ level "1"
|
||||
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
|
||||
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
|
||||
is not, which is the third line here: [three] bare, ["three"] inside
|
||||
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
|
||||
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"
|
||||
"programs/dyn-vec.flan" dyn_vec_out;
|
||||
outputs ~opt:"-O0" "dyn: a heterogeneous vector, -O0"
|
||||
@ -5233,8 +5233,8 @@ level "1"
|
||||
that lost a map's keys or values frees something live and the sum
|
||||
comes out wrong. *)
|
||||
let dyn_map_out =
|
||||
"{ :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\
|
||||
"{: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\
|
||||
true\nfalse\nfalse\ntrue\n:standalone\n6\ntext key\nvec key\n\
|
||||
true\nfalse\nfalse\n600000\n"
|
||||
in
|
||||
@ -5266,11 +5266,11 @@ level "1"
|
||||
found nothing. The 100000 at the end is 50000 instances allocated
|
||||
against one live instance, well past the collector's 1 MiB floor. *)
|
||||
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\
|
||||
a round thing, keyed by a string\nthe one keyed by a number\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"
|
||||
in
|
||||
outputs "dyn: classes and dispatch" "programs/dyn-class.flan" dyn_class_out;
|
||||
@ -5589,15 +5589,15 @@ level "1"
|
||||
key. On both backends, because every one of these is a runtime call
|
||||
whose arguments the two emit separately. *)
|
||||
let slots_out =
|
||||
"#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"
|
||||
"#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"
|
||||
in
|
||||
outputs "dyn: typed class slots" "programs/dyn-class-slots.flan" slots_out;
|
||||
outputs ~x86:true "dyn: typed class slots, --x86"
|
||||
"programs/dyn-class-slots.flan" slots_out;
|
||||
outputs "dyn: a package's slot type names its own class"
|
||||
"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 exe = compile ?x86 "programs/dyn-slot-trap.flan" in
|
||||
List.iter
|
||||
@ -5728,11 +5728,11 @@ level "1"
|
||||
write whose dyn tag does not match the element type. The expected
|
||||
text for mode 0 was captured from the running program. *)
|
||||
let dyn_view_out =
|
||||
"[ 10 20 30]\n999\n777\n4\n40\n\
|
||||
[ 1 2 3 4]\n100\n400\n\
|
||||
[ 1.5 2.5 3.5]\n9.5\n\
|
||||
[ true false]\ntrue\n\
|
||||
[ 111 222]\n3\n333\n"
|
||||
"[10 20 30]\n999\n777\n4\n40\n\
|
||||
[1 2 3 4]\n100\n400\n\
|
||||
[1.5 2.5 3.5]\n9.5\n\
|
||||
[true false]\ntrue\n\
|
||||
[111 222]\n3\n333\n"
|
||||
in
|
||||
let dyn_view ?opt ?x86 () =
|
||||
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
|
||||
note here used to say was still owed. *)
|
||||
("p", "Point", "(Point {.x 1.5 .y 2.5})");
|
||||
("xs", "[3 i32]", "[ 10 20 30]");
|
||||
("xs", "[3 i32]", "[10 20 30]");
|
||||
("flag", "bool", "true");
|
||||
(* The byte's character half, in the three shapes it has. The
|
||||
spelling is one [lib/reader.ml]'s [read_byte] accepts, so
|
||||
@ -3108,7 +3108,7 @@ let () =
|
||||
program. *)
|
||||
let r = set "xs" "((:path (1) :code \"(+ 20 5)\"))" in
|
||||
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);
|
||||
|
||||
(* 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
|
||||
before calling, [inner] set element 0 after. The value is read
|
||||
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 ]);
|
||||
("label", "string", "\"running\"", [ 1 ]) ]
|
||||
in
|
||||
@ -4886,7 +4886,7 @@ let () =
|
||||
| Some v -> fail "watch rendered a struct as %s" v
|
||||
| None -> fail "the (watch ...) form never wrote a struct");
|
||||
(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
|
||||
| None -> fail "the (watch ...) form never wrote a slice");
|
||||
(match List.assoc_opt "t2" t with
|
||||
@ -4894,7 +4894,7 @@ let () =
|
||||
| Some v -> fail "watch rendered a computed i64 as %s" v
|
||||
| None -> fail "the (watch ...) form never wrote a scalar");
|
||||
(match List.assoc_opt "d" t with
|
||||
| Some "{ :a 1}" -> ()
|
||||
| Some "{:a 1}" -> ()
|
||||
| Some v -> fail "watch rendered a dyn map as %s" v
|
||||
| None -> fail "the (watch ...) form never wrote a dyn value");
|
||||
(match List.assoc_opt "s" t with
|
||||
@ -6557,7 +6557,7 @@ let () =
|
||||
[ ("n", "i64", "3");
|
||||
("label", "string", "\"hello\"");
|
||||
("p", "Point", "(Point {.x 1.5 .y 2.5})");
|
||||
("xs", "[3 i32]", "[ 10 20 30]");
|
||||
("xs", "[3 i32]", "[10 20 30]");
|
||||
("flag", "bool", "true");
|
||||
(* And the byte's character half under this backend too: the
|
||||
spelling table is the dev runtime's, but the slot the byte
|
||||
@ -6668,8 +6668,8 @@ let () =
|
||||
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
|
||||
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
|
||||
global where the LLVM one answered [ 1 1 5 1] for both.
|
||||
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.
|
||||
|
||||
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
|
||||
@ -6729,7 +6729,7 @@ let () =
|
||||
if status r <> "ok" then fail "%s half-write locals: %s" flag (said r)
|
||||
else
|
||||
(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 ->
|
||||
fail
|
||||
"%s: a local caught mid-assignment reads %s, not its whole \
|
||||
@ -6751,7 +6751,7 @@ let () =
|
||||
match
|
||||
List.filter (fun (n, _, _) -> n = "colors") (triples r "globals")
|
||||
with
|
||||
| [ ("colors", "[4 u32]", "[ 1 1 5 1]") ] -> ()
|
||||
| [ ("colors", "[4 u32]", "[1 1 5 1]") ] -> ()
|
||||
| got ->
|
||||
fail
|
||||
"%s: a global caught mid-assignment reads %s, not its whole \
|
||||
|
||||
@ -1522,12 +1522,12 @@ let () =
|
||||
"(defonce v (Vec string) (vec-new string))\n\
|
||||
(defn take [d dyn] i32 1)\n\
|
||||
(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"
|
||||
"(defonce v (Vec i32) (vec-new i32))\n\
|
||||
(defn take [d dyn] i32 1)\n\
|
||||
(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. *)
|
||||
rejects_check "a typed Map still refuses into dyn"
|
||||
"(defonce m (Map i64 i64) (map-new i64 i64))\n\
|
||||
@ -1544,7 +1544,7 @@ let () =
|
||||
lifetime one"
|
||||
"(defn take [d dyn] i32 1)\n\
|
||||
(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 ─────────────────────────
|
||||
A local, a parameter and a temporary all answer false to
|
||||
[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"
|
||||
"(defn take [d dyn] i32 1)\n\
|
||||
(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"
|
||||
"(defn take [d dyn] i32 1)\n\
|
||||
(defn give [v (Vec i64)] i32 (take v))\n\
|
||||
(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"
|
||||
"(defn take [d dyn] i32 1)\n\
|
||||
(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
|
||||
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. *)
|
||||
@ -1573,7 +1573,7 @@ let () =
|
||||
"(defonce xs [3 i64])\n\
|
||||
(defn take [d dyn] i32 1)\n\
|
||||
(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 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
|
||||
@ -1590,7 +1590,7 @@ let () =
|
||||
"(defonce sv [(Vec i64)])\n\
|
||||
(defn take [d dyn] i32 1)\n\
|
||||
(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
|
||||
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
|
||||
@ -1605,7 +1605,7 @@ let () =
|
||||
"(defonce g [2 [[3 i64]]])\n\
|
||||
(defn take [d dyn] i32 1)\n\
|
||||
(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
|
||||
heap-durable — the checker cannot tell this one from a Ptr taken off a
|
||||
local, and admitting one admits the other. *)
|
||||
@ -1613,7 +1613,7 @@ let () =
|
||||
"(defn take [d dyn] i32 1)\n\
|
||||
(defn use [p (Ptr (Vec i64))] i32 (take (deref p)))\n\
|
||||
(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
|
||||
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
|
||||
@ -3651,10 +3651,10 @@ let () =
|
||||
"grid" ~ty:"[2 [3 u8]]" ~zeroed:false;
|
||||
rejects_check "a three-element array-fill defonce is the dyn reading"
|
||||
"(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"
|
||||
"(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
|
||||
writes into the image, and a fill is a loop. *)
|
||||
rejects_check "array-fill is not a constant's value"
|
||||
|
||||
@ -87,11 +87,11 @@ let () =
|
||||
(* A struct, nested, with a fixed array inside it. *)
|
||||
value "a struct" "(.pos b)" "(V {.x 1.5 .y 0})";
|
||||
value "a nested struct" "b"
|
||||
"(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]";
|
||||
"(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]";
|
||||
(* A slice's length is not known until it runs, so this one renders
|
||||
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
|
||||
the name is recovered from the checker's table. *)
|
||||
value "an enum" "col" ":blue";
|
||||
|
||||
@ -547,6 +547,72 @@ let () =
|
||||
fail "twin files: %s" d.Loc.dmsg
|
||||
| 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 = [...]" ];
|
||||
(* 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 ────────────────── *)
|
||||
|
||||
let run_both path want =
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
4
|
||||
:point
|
||||
nil
|
||||
#point{ :x 3 :y 4}
|
||||
#point{:x 3 :y 4}
|
||||
9
|
||||
something else
|
||||
exit 0
|
||||
|
||||
@ -770,11 +770,11 @@ once, for every method; a method has no return slot; and every parameter of both
|
||||
4
|
||||
:point
|
||||
nil
|
||||
#point{ :x 3 :y 4}
|
||||
#point{:x 3 :y 4}
|
||||
9
|
||||
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
|
||||
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
|
||||
@ -2077,10 +2077,10 @@ there too, from its tag. What comes back looks like this:</p>
|
||||
<pre><code class="sh">big 18446744073709551615
|
||||
col :blue
|
||||
(.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]})
|
||||
(slice (.tags b) 0 3) [ 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]
|
||||
(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
|
||||
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