println prints a char as the character itself, and a char inside a value, in the inspector or at the REPL prints as its literal.
This commit is contained in:
parent
fd8620516c
commit
92fbeb4fc9
7
TODO.org
7
TODO.org
@ -33,13 +33,14 @@ Decision 127: a char literal is the number typed code wants there, and a =char=
|
||||
an untyped array of char literals included; a =char= crosses into dyn as a dyn char, and
|
||||
only a dyn char unboxes into one. No arithmetic: =(i32 c)= and =(char n)= convert, the
|
||||
latter checked. An untyped defconst of one is that literal where a number is wanted.
|
||||
Printed as its literal, as dyn prints one. =runes-next= and =rune-at= give a char; the
|
||||
Printed as dyn prints one (129a). =runes-next= and =rune-at= give a char; the
|
||||
UTF-8 codec (=decode-rune=, =encode-rune=) stays on i32. Rules out the f(\a) fork.
|
||||
|
||||
** DONE Dyn has a char, and dyn text counts characters
|
||||
CLOSED: [2026-09-26]
|
||||
Only a char literal, =at= on a text and =chars= make one, and it prints as its
|
||||
literal, bare too, a control character as \\uXXXX. Into any integer width it gives its
|
||||
Only a char literal, =at= on a text and =chars= make one. =println= prints it as the
|
||||
character itself (129a); inside a value, in the inspector and the REPL it is its
|
||||
literal, a control character as \\uXXXX. Into any integer width it gives its
|
||||
code point where that fits, into a byte only when ASCII; a dyn int into any width is
|
||||
range-checked, while a cast on either wraps as a typed cast does. length, at and slice
|
||||
on dyn text count code points, a malformed byte counting as one U+FFFD. A non-ASCII
|
||||
|
||||
14
lib/check.ml
14
lib/check.ml
@ -5185,7 +5185,8 @@ let condition_desc ctx loc name =
|
||||
ei64 = (fun x -> emit (to_bytes hctx loc Tast.I64ToBytes x));
|
||||
eu64 = (fun x -> emit (to_bytes hctx loc Tast.U64ToBytes x));
|
||||
ef64 = (fun x -> emit (to_bytes hctx loc Tast.F64ToBytes x));
|
||||
edyn = (fun x -> mk loc Types.Unit (Tast.Prim (Tast.Rt "flan_dyn_emit_msg", [ x ]))) }
|
||||
edyn = (fun x -> mk loc Types.Unit (Tast.Prim (Tast.Rt "flan_dyn_emit_msg", [ x ])));
|
||||
enested = (fun x -> mk loc Types.Unit (Tast.Prim (Tast.Rt "flan_dyn_emit_msg", [ x ]))) }
|
||||
in
|
||||
let value =
|
||||
mk loc ty (Tast.Deref (mk loc (Types.Ptr (Types.Mut, ty)) (Tast.Local pslot)))
|
||||
@ -14486,13 +14487,19 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
||||
ei64 = (fun x -> write (conv Tast.I64ToBytes x));
|
||||
eu64 = (fun x -> write (conv Tast.U64ToBytes x));
|
||||
ef64 = (fun x -> write (conv Tast.F64ToBytes x));
|
||||
edyn = (fun x -> mk loc Types.Unit (Tast.Prim (Tast.Rt "flan_dyn_print_at", [ x; here loc ]))) }
|
||||
edyn = (fun x -> mk loc Types.Unit (Tast.Prim (Tast.Rt "flan_dyn_print_at", [ x; here loc ])));
|
||||
enested = (fun x -> mk loc Types.Unit (Tast.Prim (Tast.Rt "flan_dyn_print_nested_at", [ x; here loc ]))) }
|
||||
in
|
||||
let rc = render_ctx ctx emitter in
|
||||
let c_top = emitter.Render.edyn in
|
||||
let render_one a =
|
||||
match a.Tast.ty with
|
||||
| Types.String | Types.Slice (_, (Types.Int Types.U8)) ->
|
||||
[ write (mk loc bslice (Tast.Prim (Tast.Bytes, [ a ]))) ]
|
||||
(* A char at the top prints as the character itself (129a), through the
|
||||
runtime's dyn printer so the two sides agree. *)
|
||||
| Types.Char ->
|
||||
[ c_top (box loc a) ]
|
||||
(* A String prints as its text, raw at the top as a str does. *)
|
||||
| t when is_string_ty t ->
|
||||
[ write (mk loc bslice (Tast.Prim (Tast.Bytes, [ string_bytes ctx loc a ]))) ]
|
||||
@ -14562,7 +14569,8 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
||||
ei64 = (fun x -> unit_rt "flan_dev_watch_emit_i64" [ x ]);
|
||||
eu64 = (fun x -> unit_rt "flan_dev_watch_emit_u64" [ x ]);
|
||||
ef64 = (fun x -> unit_rt "flan_dev_watch_emit_f64" [ x ]);
|
||||
edyn = (fun x -> unit_rt "flan_dyn_emit_watch" [ x ]) }
|
||||
edyn = (fun x -> unit_rt "flan_dyn_emit_watch" [ x ]);
|
||||
enested = (fun x -> unit_rt "flan_dyn_emit_watch" [ x ]) }
|
||||
in
|
||||
(* A place is read where it stands; anything else is bound to a slot of
|
||||
this frame first, so the walk — which names its argument once per
|
||||
|
||||
@ -5122,6 +5122,7 @@ declare void @flan_dyn_set_at(i64, i64, i64, ptr, i64)
|
||||
declare void @flan_dyn_push(i64, i64, ptr, i64)
|
||||
declare void @flan_dyn_print(i64)
|
||||
declare void @flan_dyn_print_at(i64, ptr, i64)
|
||||
declare void @flan_dyn_print_nested_at(i64, ptr, i64)
|
||||
declare void @flan_dyn_emit_dev(i64)
|
||||
declare void @flan_dyn_emit_watch(i64)
|
||||
; The watch table, which (watch "name" v) renders into. flan_dev.c is linked
|
||||
|
||||
@ -166,7 +166,7 @@ let refusal c (ty : Types.t) : string option =
|
||||
{ Render.structs = c.structs; datas = c.datas; unions = c.unions;
|
||||
enums = c.enums;
|
||||
emit = { Render.ebytes = emit; estr = emit; ei64 = emit; eu64 = emit;
|
||||
ef64 = emit; edyn = emit };
|
||||
ef64 = emit; edyn = emit; enested = emit };
|
||||
ptrs = Some { Render.live = (fun _ -> { unit_ with ty = Types.Bool });
|
||||
bytechar = emit; epitaph = emit };
|
||||
alloc = (fun _ -> 0) }
|
||||
|
||||
@ -35,6 +35,10 @@ type emitter = {
|
||||
runtime's to read — so the runtime renders it, into the same place the
|
||||
other four write to. *)
|
||||
edyn : Tast.expr -> Tast.expr;
|
||||
(* The same, for a dyn inside a larger value: a text quoted and a char as
|
||||
its literal. The same as [edyn] on the inspecting side, which quotes at
|
||||
the top too; [println]'s differs, since its top level is raw. *)
|
||||
enested : Tast.expr -> Tast.expr;
|
||||
}
|
||||
|
||||
(* What a walk is allowed to do with a pointer, and it is exactly two
|
||||
@ -155,10 +159,12 @@ let rec render ?(refuse = print_refusal) c depth (e : Tast.expr) : Tast.expr lis
|
||||
| Types.Float _ -> [ c.emit.ef64 (cast (Types.Float Types.F64) e) ]
|
||||
| Types.Bool ->
|
||||
[ unit_ (Tast.If (e, lit "true", lit "false")) ]
|
||||
(* A char prints as a dyn char does, as its literal: the runtime's one
|
||||
spelling, so the typed and dyn sides cannot drift apart. *)
|
||||
(* A char inside a value, or in the inspector, prints as its literal, by
|
||||
the runtime's one spelling so the typed and dyn sides cannot drift
|
||||
apart. [println]'s top level is the character itself, and check.ml's
|
||||
print arm takes that case before the walk. *)
|
||||
| Types.Char ->
|
||||
[ c.emit.edyn
|
||||
[ c.emit.enested
|
||||
{ Tast.e = Tast.Prim (Tast.Rt "flan_dyn_from_char", [ cast (Types.Int Types.I32) e ]);
|
||||
ty = Types.Dyn; loc } ]
|
||||
(* Evaluated *and then* reported. A Unit expression is almost always a call
|
||||
@ -443,7 +449,7 @@ let rec render ?(refuse = print_refusal) c depth (e : Tast.expr) : Tast.expr lis
|
||||
does not — so the printing belongs on the side that can see the tag, and
|
||||
the walk hands the whole value to [c.emit.edyn], which names the runtime
|
||||
entry point that renders into this emitter's sink. *)
|
||||
| Types.Dyn -> [ c.emit.edyn e ]
|
||||
| Types.Dyn -> [ (if depth = 0 then c.emit.edyn else c.emit.enested) e ]
|
||||
(* Reachable: [(println m)] on a Map. Everything else in [Types.t] has an
|
||||
arm above, and a [Var] never reaches a backend. So this names the fix
|
||||
rather than only the refusal. *)
|
||||
|
||||
@ -1556,6 +1556,10 @@ let dev_emitter : Render.emitter =
|
||||
(* Into the value buffer, not stdout: a dyn expression's value belongs in
|
||||
the reply's value like any other. *)
|
||||
edyn =
|
||||
(fun x ->
|
||||
{ Tast.e = Tast.Prim (Tast.Rt "flan_dyn_emit_dev", [ x ]);
|
||||
ty = Types.Unit; loc = x.Tast.loc });
|
||||
enested =
|
||||
(fun x ->
|
||||
{ Tast.e = Tast.Prim (Tast.Rt "flan_dyn_emit_dev", [ x ]);
|
||||
ty = Types.Unit; loc = x.Tast.loc }) }
|
||||
|
||||
@ -855,9 +855,12 @@ static void render(dyn_sink w, flan_dyn v, int depth, int nested) {
|
||||
else emit_n(w, obj_text_bytes(o), o->len);
|
||||
return;
|
||||
}
|
||||
/* A char prints as the literal that reads back as it, at every depth. */
|
||||
/* A char at the top of a println is the character itself, and anywhere a
|
||||
* reader must tell it apart — inside a value, in the inspector — the
|
||||
* literal that reads back as it: Clojure's println against its pr. */
|
||||
case FLAN_DYN_TAG_CHAR:
|
||||
char_spell((uint32_t)dyn_payload(v), buf);
|
||||
if (nested) char_spell((uint32_t)dyn_payload(v), buf);
|
||||
else buf[utf8_encode((uint32_t)dyn_payload(v), (uint8_t *)buf)] = '\0';
|
||||
emit(w, buf);
|
||||
return;
|
||||
/* A keyword prints with its colon, bare, at every depth: :a is its own
|
||||
@ -4384,6 +4387,15 @@ void flan_dyn_print_at(flan_dyn v, const uint8_t *loc, int64_t loclen) {
|
||||
|
||||
void flan_dyn_print(flan_dyn v) { flan_dyn_print_at(v, NULL, 0); }
|
||||
|
||||
/* The same onto stdout, as a value inside a larger one prints: a text
|
||||
* quoted and a char as its literal. println's walk calls it for a dyn or a
|
||||
* char inside a typed struct, array or slice. */
|
||||
void flan_dyn_print_nested_at(flan_dyn v, const uint8_t *loc, int64_t loclen) {
|
||||
walk_site was = walk_enter(loc, loclen, "print");
|
||||
render(flan_write_stdout, v, 0, 1);
|
||||
walk_leave(was);
|
||||
}
|
||||
|
||||
flan_dyn flan_dyn_eq_at(flan_dyn a, flan_dyn b, const uint8_t *loc,
|
||||
int64_t loclen) {
|
||||
walk_site was = walk_enter(loc, loclen, "=");
|
||||
|
||||
@ -399,6 +399,8 @@ void flan_dyn_need_as(flan_dyn v, const uint8_t *want, int64_t wantlen,
|
||||
/* print, =, length and has-key? with the site they were written at: a view
|
||||
* that traps inside one names it. */
|
||||
void flan_dyn_print_at(flan_dyn v, const uint8_t *loc, int64_t loclen);
|
||||
void flan_dyn_print_nested_at(flan_dyn v, const uint8_t *loc,
|
||||
int64_t loclen);
|
||||
flan_dyn flan_dyn_eq_at(flan_dyn a, flan_dyn b, const uint8_t *loc,
|
||||
int64_t loclen);
|
||||
flan_dyn flan_dyn_len_at(flan_dyn v, const uint8_t *loc, int64_t loclen);
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
;;;; Every ASCII code point, the C1 controls, and a few past them, as dyn chars printed one per
|
||||
;;;; line. The test reads each line back with the reader and wants the same
|
||||
;;;; code point, so what a char prints as is what reads as it.
|
||||
;;;; line, each inside a vector, where a char prints as its literal. The test
|
||||
;;;; reads each back with the reader and wants the same code point, so what a
|
||||
;;;; char prints as there is what reads as it.
|
||||
|
||||
(defn main [] i32
|
||||
(let [v (vec-new u8)]
|
||||
(dotimes [i 128] (push v (u8 i)))
|
||||
(let [t (the dyn (str (slice v)))]
|
||||
(dotimes [i (length t)] (println (at t i))))
|
||||
(dotimes [i (length t)] (println [(at t i)])))
|
||||
;; the C1 controls, U+0080 to U+009F, and U+00A0, each C2 then one byte
|
||||
(let [w (vec-new u8)]
|
||||
(dotimes [i 33] (push w (u8 0xC2)) (push w (u8 (+ 0x80 i))))
|
||||
(let [c1 (the dyn (str (slice w)))]
|
||||
(dotimes [i (length c1)] (println (at c1 i))))
|
||||
(dotimes [i (length c1)] (println [(at c1 i)])))
|
||||
(free w))
|
||||
(let [u (the dyn "é日😀")]
|
||||
(dotimes [i (length u)] (println (at u i))))
|
||||
(dotimes [i (length u)] (println [(at u i)])))
|
||||
(free v))
|
||||
0)
|
||||
|
||||
@ -5518,8 +5518,8 @@ level "1"
|
||||
code point in a typed i32. With an argument, a dyn int at the i32
|
||||
traps at the call. *)
|
||||
let dyn_char_out =
|
||||
"\\I\n\\é\n\\日\n\\😀\n[\\a \\space \\( \\newline]\n:char\n\
|
||||
6\n\\é\n\\日\n\\😀\n\\o\n日😀\n\
|
||||
"I\né\n日\n😀\n[\\a \\space \\( \\newline]\n:char\n\
|
||||
6\né\n日\n😀\no\n日😀\n\
|
||||
[\\é \\日 \\😀 \\space \\o \\k]\n6\né日😀 ok\ntrue\n日\nok\n\
|
||||
true\nfalse\nfalse\nfalse\ntrue\nfalse\ntrue\n2\n128512\n5\n97\n"
|
||||
in
|
||||
@ -5531,9 +5531,9 @@ level "1"
|
||||
both ways. Then a dyn int at a char parameter and (char n) on a
|
||||
surrogate, each trapping at its own form. *)
|
||||
let char_out =
|
||||
"\\a\n\\a\n\\é [\\é \\z \\space]\n233 233\n\
|
||||
true true true true \\c \\q\n\\A \\😀 65 65 \\z\n\\B\n\
|
||||
(some 1) (some 2) false\n2\ntrue true\n122\né日\n\\q\n[\\x \\y]\n\\y\n"
|
||||
"a\na\né [\\é \\z \\space]\n233 233\n\
|
||||
true true true true c q\nA 😀 65 65 z\nB\n\
|
||||
(some 1) (some 2) false\n2\ntrue true\n122\né日\nq\n[\\x \\y]\ny\n"
|
||||
in
|
||||
outputs "char: a typed char" "programs/char.flan" char_out;
|
||||
outputs ~opt:"-O0" "char: a typed char, -O0" "programs/char.flan" char_out;
|
||||
@ -5560,8 +5560,8 @@ level "1"
|
||||
(* A String, and a str made from one, cross into dyn as text measured
|
||||
like any other: characters counted, ASCII or not. *)
|
||||
let string_char_out =
|
||||
"ab 2 \\b\né日😀! 4 \\日 \\!\n日😀 [\\é \\日 \\😀 \\!]\n\
|
||||
é日😀! 4 \\😀 true\n10 4\n"
|
||||
"ab 2 b\né日😀! 4 日 !\n日😀 [\\é \\日 \\😀 \\!]\n\
|
||||
é日😀! 4 😀 true\n10 4\n"
|
||||
in
|
||||
outputs "dyn: a String crossing counts chars"
|
||||
"programs/dyn-char-string.flan" string_char_out;
|
||||
@ -5572,7 +5572,7 @@ level "1"
|
||||
(* A text pinned by crossing into a str keeps counting characters (the
|
||||
pin's stamp and the text's measure live in different header fields),
|
||||
and a char writes through a view of typed storage. *)
|
||||
let pinned_out = "9\n9\n9\n3\n\\日\né日\n122 26085 3\n97 2\n" in
|
||||
let pinned_out = "9\n9\n9\n3\n日\né日\n122 26085 3\n97 2\n" in
|
||||
outputs "dyn: a pinned text counts chars" "programs/dyn-char-pinned.flan"
|
||||
pinned_out;
|
||||
outputs ~opt:"-O0" "dyn: a pinned text counts chars, -O0"
|
||||
@ -5648,7 +5648,8 @@ level "1"
|
||||
("x", "52:34: dyn: an i64 is wanted here, and this is a text, \
|
||||
\"x\". An i64 takes an int or a char's code point") ])
|
||||
[ false; true ];
|
||||
(* Print, then read: each char dyn-char-spell.flan prints — every ASCII
|
||||
(* Print, then read: each char dyn-char-spell.flan prints inside a vector,
|
||||
where a char shows its literal — every ASCII
|
||||
code point, the C1 controls, then four past them — reads back as the code point it was,
|
||||
and so does the compiler's own spelling of the same literal, which is
|
||||
what flan convert writes. *)
|
||||
@ -5676,7 +5677,12 @@ level "1"
|
||||
let code, text = run exe None in
|
||||
let lines = String.split_on_char '\n' text in
|
||||
let lines = List.filteri (fun i _ -> i < List.length spelled) lines in
|
||||
let got = List.map read_char lines in
|
||||
let unwrap l =
|
||||
let n = String.length l in
|
||||
if n >= 2 && l.[0] = '[' && l.[n - 1] = ']' then String.sub l 1 (n - 2)
|
||||
else l
|
||||
in
|
||||
let got = List.map (fun l -> read_char (unwrap l)) lines in
|
||||
if code <> 0 || got <> List.map Option.some spelled then begin
|
||||
incr failures;
|
||||
Printf.printf "FAIL dyn chars read back as printed%s\n \
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user