The printed struct moves to the dot, with the reader that parses it

render.ml's output and emacs/flan-inspect.el's parser are the two ends of one
wire format, which is why the printer was left on the colon when the rest of
the corpus moved: shifting it alone would have broken inspection in the dev
loop without breaking a test that said so. They move together here.

The field list in the inspector is labelled with the dot too, which is the
spelling flan-inspect-step-expr already used to build `(.x b)' — the label and
the expression it stands for now read the same.

One case needed a guard the colon never did: `...' also begins with a dot and
is the renderer saying it stopped, not a field called `..'. A field name never
starts with a second dot, so one character of lookahead separates them.

The colon is not gone from the rendered grammar. An enum member is `:green' and
is a *value*, so the two are now told apart by the character alone, which is
the only thing that distinguishes them.

Also font lock, handed over with the same change: `:name' was the rule that
drew field labels, and with the colon belonging to keywords every label in the
corpus was left unfontified. `.name' is drawn as a constant, in both the places
it appears — the label in `{.x 1.0}' and the accessor in `(.x v)', which are
the same name.
This commit is contained in:
Joseph Ferano 2026-09-12 15:29:34 +07:00
parent a0610cecd5
commit 0f3b633449
8 changed files with 110 additions and 57 deletions

View File

@ -60,12 +60,12 @@ reply without a daemon behind them, and so that this file names
;;; Reading what the renderer wrote ;;; Reading what the renderer wrote
;; The value comes back as a string, and that string is very nearly an ;; The value comes back as a string, and that string is very nearly an
;; s-expression — `(Blob {:id 7 :pos (V {:x 1.5 :y 0})})'. Nearly, because ;; s-expression — `(Blob {.id 7 .pos (V {.x 1.5 .y 0})})'. Nearly, because
;; Emacs' `read' has no `{', so it is parsed here instead. The grammar is ;; Emacs' `read' has no `{', so it is parsed here instead. The grammar is
;; small and fixed by `Session.render' (lib/session.ml), and every case below ;; small and fixed by `Session.render' (lib/session.ml), and every case below
;; names the line of it that produces it. ;; names the line of it that produces it.
;; ;;
;; (Name {:f V :f V}) a struct, with ` ...' before the `}' if the walk hit ;; (Name {.f V .f V}) a struct, with ` ...' before the `}' if the walk hit
;; its span bound of 8 fields ;; its span bound of 8 fields
;; [ 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
@ -153,7 +153,7 @@ reply without a daemon behind them, and so that this file names
:children (list (cons "some" (car r)))) :children (list (cons "some" (car r))))
(if (and (< k (length s)) (eq (aref s k) ?\))) (1+ k) k)))) (if (and (< k (length s)) (eq (aref s k) ?\))) (1+ k) k))))
(t (t
;; `(Name {' then `:field VALUE' pairs, then `})'. ;; `(Name {' then `.field VALUE' pairs, then `})'.
(setq j (flan-inspect--skip-space s j)) (setq j (flan-inspect--skip-space s j))
(when (and (< j (length s)) (eq (aref s j) ?\{)) (setq j (1+ j))) (when (and (< j (length s)) (eq (aref s j) ?\{)) (setq j (1+ j)))
(let ((kids nil) (more nil) (done nil)) (let ((kids nil) (more nil) (done nil))
@ -162,7 +162,18 @@ reply without a daemon behind them, and so that this file names
(cond (cond
((>= j (length s)) (setq done t)) ((>= j (length s)) (setq done t))
((eq (aref s j) ?\}) (setq j (1+ j)) (setq done t)) ((eq (aref s j) ?\}) (setq j (1+ j)) (setq done t))
((eq (aref s j) ?:) ;; A dot, which is how a struct literal is written in the
;; source and now how `Render.render' prints one. The colon
;; belongs to keywords — an enum member renders as `:green', and
;; one of those is a *value* here, never a field label — so the
;; two cannot be told apart by anything but this character.
;; `...' also begins with a dot and is the renderer saying it
;; stopped, not a field called `..'. A field name never starts
;; with a second dot, so one character of lookahead separates
;; them.
((and (eq (aref s j) ?.)
(< (1+ j) (length s))
(not (eq (aref s (1+ j)) ?.)))
(let ((start j)) (let ((start j))
(while (and (< j (length s)) (not (memq (aref s j) '(?\s ?\})))) (while (and (< j (length s)) (not (memq (aref s j) '(?\s ?\}))))
(setq j (1+ j))) (setq j (1+ j)))
@ -253,8 +264,11 @@ reply without a daemon behind them, and so that this file names
(defvar-local flan-inspect--node nil "Its parsed value.") (defvar-local flan-inspect--node nil "Its parsed value.")
(defun flan-inspect--label (child) (defun flan-inspect--label (child)
"How CHILD is named in the list: `0.' for an element, `.x' for a field.
A field is written with the dot it is written with in the source, which is also
what `flan-inspect-step-expr' puts in the expression it sends."
(let ((k (car child))) (let ((k (car child)))
(if (integerp k) (format "%d." k) (format ":%s" k)))) (if (integerp k) (format "%d." k) (format ".%s" k))))
;;; What a leaf is, beyond its decimal ;;; What a leaf is, beyond its decimal

View File

@ -31,10 +31,10 @@
;; - field access is `(.x v)', an ordinary call whose head happens to begin ;; - field access is `(.x v)', an ordinary call whose head happens to begin
;; with a dot. `.' is a symbol constituent in the syntax table below, so ;; with a dot. `.' is a symbol constituent in the syntax table below, so
;; nothing special is needed for it to read as a head. ;; nothing special is needed for it to read as a head.
;; - field labels are moving from `:x' to `.x', so `{.x 1.0}' is a struct ;; - a field label is a dot: `{.x 1.0}' is a struct literal, and the colon
;; literal and not a map with symbol keys. The indenter is correct for ;; now belongs to keywords, which a `Map' will use as keys. The indenter
;; both spellings *because* it never looks at the key: a brace aligns under ;; is correct for both *because* it never looks at the key: a brace aligns
;; its first element whatever that element is spelled like. ;; under its first element whatever that element is spelled like.
;;; Code: ;;; Code:
@ -99,10 +99,11 @@
;; A keyword resolves against an enum at the call site, so it reads as a ;; A keyword resolves against an enum at the call site, so it reads as a
;; constant rather than as a string. ;; constant rather than as a string.
("\\_<:\\(?:\\sw\\|\\s_\\)+" . font-lock-constant-face) ("\\_<:\\(?:\\sw\\|\\s_\\)+" . font-lock-constant-face)
;; A field, in both of the spellings that exist while the corpus moves from ;; A field. The label in a struct literal — `{.x 1.0}' — and the accessor
;; `{:x 1}' to `{.x 1}': the label in a struct literal and the accessor ;; `(.x v)' are the same name and are drawn the same way. Without this
;; `(.x v)' are the same name and are drawn the same way, which is also ;; rule every field label in the corpus is unfontified, which is what the
;; what the keyword rule above did for the spelling being replaced. ;; colon-to-dot change left behind: the keyword rule above used to cover
;; them and no longer does, because the colon belongs to keywords now.
("\\_<\\.\\(?:\\sw\\|\\s_\\)+" . font-lock-constant-face) ("\\_<\\.\\(?:\\sw\\|\\s_\\)+" . font-lock-constant-face)
;; The machine types, which are ordinary symbols but never anything else. ;; The machine types, which are ordinary symbols but never anything else.
("\\_<\\(?:[iu]\\(?:8\\|16\\|32\\|64\\)\\|f\\(?:32\\|64\\)\\|bool\\|string\\|Unit\\|Never\\|Ptr\\|Option\\)\\_>" ("\\_<\\(?:[iu]\\(?:8\\|16\\|32\\|64\\)\\|f\\(?:32\\|64\\)\\|bool\\|string\\|Unit\\|Never\\|Ptr\\|Option\\)\\_>"
@ -306,9 +307,9 @@ Point is on the opening delimiter.
is an argument and there is no first argument to align under. Aligning under is an argument and there is no first argument to align under. Aligning under
the first *element* instead is what makes a binding vector line its names up the first *element* instead is what makes a binding vector line its names up
name-under-name, and by the same rule it lines up `defn' parameter lists, name-under-name, and by the same rule it lines up `defn' parameter lists,
`restart-case' clause parameters, `handler-bind' clause vectors, and both `restart-case' clause parameters, `handler-bind' clause vectors, and a brace
spellings of a struct literal `{:x 1}' and `{.x 1}' with no case for any whatever its keys are spelled like `{.x 1}' and `{:north 0}' with no case
of them, because the rule is about the bracket and never about what is for any of them, because the rule is about the bracket and never about what is
written inside it. written inside it.
A head that is not a symbol is the third case: `((f x) y)' has nothing to look A head that is not a symbol is the third case: `((f x) y)' has nothing to look

View File

@ -44,8 +44,8 @@
(message "\nreading what the renderer wrote") (message "\nreading what the renderer wrote")
;; (V {:x 1.5 :y 0}) — Types.Named, lib/session.ml. ;; (V {.x 1.5 .y 0}) — Types.Named, lib/session.ml.
(let ((n (flan-inspect-parse "(V {:x 1.5 :y 0})"))) (let ((n (flan-inspect-parse "(V {.x 1.5 .y 0})")))
(test-flan--check "a struct is a struct" (eq (plist-get n :kind) 'struct)) (test-flan--check "a struct is a struct" (eq (plist-get n :kind) 'struct))
(test-flan--check "with its type name" (equal (plist-get n :type) "V")) (test-flan--check "with its type name" (equal (plist-get n :type) "V"))
(test-flan--check "and its fields in order" (test-flan--check "and its fields in order"
@ -56,7 +56,7 @@
;; The whole of NEXT.md's worked example, nested two deep with a string that ;; The whole of NEXT.md's worked example, nested two deep with a string that
;; has escaped quotes in it and a slice at the end. ;; 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)) (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"
@ -102,7 +102,7 @@
;; A struct with a `...' where a field would be: span truncation *inside* a ;; A struct with a `...' where a field would be: span truncation *inside* a
;; struct, which is a different position in the grammar from a sequence's. ;; struct, which is a different position in the grammar from a sequence's.
(let ((n (flan-inspect-parse "(Wide {:a 1 :b 2 ...})"))) (let ((n (flan-inspect-parse "(Wide {.a 1 .b 2 ...})")))
(test-flan--check "a struct's span bound is truncation, not a field" (test-flan--check "a struct's span bound is truncation, not a field"
(and (equal (mapcar #'car (plist-get n :children)) '("a" "b")) (and (equal (mapcar #'car (plist-get n :children)) '("a" "b"))
(plist-get n :truncated)))) (plist-get n :truncated))))
@ -134,7 +134,7 @@
(and why (string-match-p (regexp-quote (cadr case)) why))))) (and why (string-match-p (regexp-quote (cadr case)) why)))))
(test-flan--check "a struct with fields does not refuse" (test-flan--check "a struct with fields does not refuse"
(null (flan-inspect-refusal (flan-inspect-parse "(V {:x 1 :y 2})")))) (null (flan-inspect-refusal (flan-inspect-parse "(V {.x 1 .y 2})"))))
(test-flan--check "and a pointer says the address is not there either" (test-flan--check "and a pointer says the address is not there either"
(string-match-p "does not write the address" (string-match-p "does not write the address"
@ -195,14 +195,14 @@
(string-match-p "0xFF 0b1111_1111" text)))) (string-match-p "0xFF 0b1111_1111" text))))
(let ((flan-inspect-request-function (let ((flan-inspect-request-function
(lambda (_) '(:status "ok" :value "(Mask {:bits 255 :name \"all\"})"))) (lambda (_) '(:status "ok" :value "(Mask {.bits 255 .name \"all\"})")))
(flan-inspect-buffer " *test-inspect*")) (flan-inspect-buffer " *test-inspect*"))
(let ((text (with-current-buffer (save-window-excursion (flan-inspect "m")) (let ((text (with-current-buffer (save-window-excursion (flan-inspect "m"))
(buffer-string)))) (buffer-string))))
(test-flan--check "and a numeric field shows them in the field list" (test-flan--check "and a numeric field shows them in the field list"
(string-match-p ":bits +255 +0xFF 0b1111_1111" text)) (string-match-p "\\.bits +255 +0xFF 0b1111_1111" text))
(test-flan--check "while a string field is left alone" (test-flan--check "while a string field is left alone"
(string-match-p ":name +\"all\"\n" text)))) (string-match-p "\\.name +\"all\"\n" text))))
;;; The inspector buffer ;;; The inspector buffer
@ -218,13 +218,13 @@
(save-window-excursion (flan-inspect--show expr nil)))) (save-window-excursion (flan-inspect--show expr nil))))
(let* ((buf (test-flan--inspect (let* ((buf (test-flan--inspect
"b" "(Blob {:id 7 :name \"sandy\" :pos (V {:x 1.5 :y 0})})")) "b" "(Blob {.id 7 .name \"sandy\" .pos (V {.x 1.5 .y 0})})"))
(text (with-current-buffer buf (buffer-string)))) (text (with-current-buffer buf (buffer-string))))
(test-flan--check "the expression is at the top" (string-match-p "\\`b\n" text)) (test-flan--check "the expression is at the top" (string-match-p "\\`b\n" text))
(test-flan--check "and the type under it" (string-match-p "a Blob" text)) (test-flan--check "and the type under it" (string-match-p "a Blob" text))
(test-flan--check "the fields are listed" (string-match-p ":id.*7" text)) (test-flan--check "the fields are listed" (string-match-p "\\.id.*7" text))
(test-flan--check "a nested struct is summarised, not expanded" (test-flan--check "a nested struct is summarised, not expanded"
(string-match-p ":pos +(V … 2 fields)" text)) (string-match-p "\\.pos +(V … 2 fields)" text))
(test-flan--check "and the keys are shown" (string-match-p "RET inspect" text)) (test-flan--check "and the keys are shown" (string-match-p "RET inspect" text))
;; Every field line carries the step that reaches it. ;; Every field line carries the step that reaches it.
(with-current-buffer buf (with-current-buffer buf
@ -276,8 +276,8 @@
(push (plist-get form :code) asked) (push (plist-get form :code) asked)
(list :status "ok" (list :status "ok"
:value (if (equal (plist-get form :code) "(.pos b)") :value (if (equal (plist-get form :code) "(.pos b)")
"(V {:x 1.5 :y 0})" "(V {.x 1.5 .y 0})"
"(Blob {:id 7 :pos (V {:x 1.5 :y 0})})")))) "(Blob {.id 7 .pos (V {.x 1.5 .y 0})})"))))
(flan-inspect-buffer " *test-inspect*")) (flan-inspect-buffer " *test-inspect*"))
(when (get-buffer " *test-inspect*") (kill-buffer " *test-inspect*")) (when (get-buffer " *test-inspect*") (kill-buffer " *test-inspect*"))
(save-window-excursion (save-window-excursion
@ -304,7 +304,7 @@
;; RET on something that cannot be entered refuses there, rather than sending ;; RET on something that cannot be entered refuses there, rather than sending
;; an expression the program would reject. ;; an expression the program would reject.
(let* ((buf (test-flan--inspect "p" "(Node {:next <ptr> :n 1})"))) (let* ((buf (test-flan--inspect "p" "(Node {.next <ptr> .n 1})")))
(with-current-buffer buf (with-current-buffer buf
(goto-char (point-min)) (goto-char (point-min))
(flan-inspect-next) (flan-inspect-next)
@ -321,7 +321,7 @@
;; The span bound is drawn, because a field list that silently stops is a lie ;; The span bound is drawn, because a field list that silently stops is a lie
;; about the value. ;; about the value.
(let ((text (with-current-buffer (let ((text (with-current-buffer
(test-flan--inspect "w" "(Wide {:a 1 :b 2 ...})") (buffer-string)))) (test-flan--inspect "w" "(Wide {.a 1 .b 2 ...})") (buffer-string))))
(test-flan--check "span truncation is drawn, not dropped" (test-flan--check "span truncation is drawn, not dropped"
(string-match-p "span bound of 8" text))) (string-match-p "span bound of 8" text)))
@ -456,7 +456,7 @@
:stack (list (list :fn "sim/settle" :loc "sand.flan:42:3" :stack (list (list :fn "sim/settle" :loc "sand.flan:42:3"
:fetched t :fetched t
:locals '(("i" "i32" "7") :locals '(("i" "i32" "7")
("b" "Blob" "(Blob {:id 7})"))) ("b" "Blob" "(Blob {.id 7})")))
(list :fn "sim/step" :loc "sand.flan:60:1" (list :fn "sim/step" :loc "sand.flan:60:1"
:fetched t :locals nil)))) :fetched t :locals nil))))
(buf (test-flan--cnr state)) (buf (test-flan--cnr state))
@ -496,7 +496,7 @@
(let ((asked nil)) (let ((asked nil))
(let ((flan-inspect-request-function (let ((flan-inspect-request-function
(lambda (form) (push (plist-get form :code) asked) (lambda (form) (push (plist-get form :code) asked)
(list :status "ok" :value "(Blob {:id 7})"))) (list :status "ok" :value "(Blob {.id 7})")))
(flan-inspect-buffer " *test-inspect*")) (flan-inspect-buffer " *test-inspect*"))
(with-current-buffer (test-flan--cnr (with-current-buffer (test-flan--cnr
(list :condition "Missing" :restarts '("retry") (list :condition "Missing" :restarts '("retry")

View File

@ -81,7 +81,7 @@
;; survive a `{' in the middle of it. ;; survive a `{' in the middle of it.
(test-flan-mode--check (test-flan-mode--check
"and still does with a struct literal in a value" "and still does with a struct literal in a value"
"(let [tip (rl/Vector2 {:x 15.0 :y 12.0}) "(let [tip (rl/Vector2 {.x 15.0 .y 12.0})
vel (+ gravity 1.0)] vel (+ gravity 1.0)]
(draw tip vel))") (draw tip vel))")
@ -90,17 +90,21 @@
;; element is — and this is the test that keeps it that way while the corpus ;; element is — and this is the test that keeps it that way while the corpus
;; moves from `:x' to `.x'. ;; moves from `:x' to `.x'.
(test-flan-mode--check (test-flan-mode--check
"a struct literal aligns under its first field, keyword spelling" "a struct literal aligns under its first field"
"(rl/Rectangle {:x 0.0 :y 0.0
:width (f32 screen-width)
:height (f32 screen-height)})")
(test-flan-mode--check
"and identically in the dot spelling it is moving to"
"(rl/Rectangle {.x 0.0 .y 0.0 "(rl/Rectangle {.x 0.0 .y 0.0
.width (f32 screen-width) .width (f32 screen-width)
.height (f32 screen-height)})") .height (f32 screen-height)})")
;; The colon spelling is gone from the language — the parser refuses it — but a
;; map literal will be written this way, and the indenter must not have learned
;; the difference: it aligns under the first element of a brace and never looks
;; at what that element is spelled like.
(test-flan-mode--check
"and a brace of keyword keys aligns exactly the same way"
"(counts {:north 0 :south 1
:east 2
:west 3})")
;; `defn' carries a return type between the parameters and the body, and it is ;; `defn' carries a return type between the parameters and the body, and it is
;; optional. Both spellings indent the body by two, which is why the spec is ;; optional. Both spellings indent the body by two, which is why the spec is
;; `:defn' and not a count: a count would have to know whether the type is ;; `:defn' and not a count: a count would have to know whether the type is
@ -204,5 +208,39 @@
c 3] c 3]
(print c))") (print c))")
;;; Font lock
;; The colon-to-dot change left every field label in the corpus unfontified:
;; the keyword rule used to cover them, and the colon belongs to keywords now.
(defun test-flan-mode--face-at (text needle)
"The face on the first character of NEEDLE in TEXT, under `flan-mode'."
(with-temp-buffer
(insert text)
(flan-mode)
(font-lock-ensure)
(goto-char (point-min))
(search-forward needle)
(get-text-property (- (point) (length needle)) 'face)))
(message "\n-- font lock")
(dolist (case '(("(rl/Vector2 {.x 1.0 .y 2.0})" ".x" "a struct literal's field label")
("(rl/Vector2 {.x 1.0 .y 2.0})" ".y" "and the second one")
("(set total (.bytes c))" ".bytes" "a field accessor")
;; The keyword rule is still there: an enum member is one.
("(rl/mouse-button-down? :left)" ":left" "an enum member")))
(test-flan--check (format "%s is a constant" (nth 2 case))
(eq (test-flan-mode--face-at (nth 0 case) (nth 1 case))
'font-lock-constant-face)))
;; A dot inside a number is not a label, and neither is the dot in a name that
;; has one in the middle.
;; A dot in the middle of a name is not a label: `\_<' anchors the rule to the
;; start of a symbol, and `a.b' is one symbol starting at `a'.
(test-flan--check "a dot inside a name does not start a label"
(null (test-flan-mode--face-at "(f alpha.beta)" ".beta")))
(provide 'test-flan-mode) (provide 'test-flan-mode)
;;; test-flan-mode.el ends here ;;; test-flan-mode.el ends here

View File

@ -147,11 +147,12 @@ let rec render c depth (e : Tast.expr) : Tast.expr list =
(fun i (f : Tast.field) -> (fun i (f : Tast.field) ->
let v = { Tast.e = Tast.Field (e, i); ty = f.Tast.fty; loc } in let v = { Tast.e = Tast.Field (e, i); ty = f.Tast.fty; loc } in
(if i = 0 then [] else [ lit " " ]) (if i = 0 then [] else [ lit " " ])
(* Still a colon, deliberately. This printed form is a wire (* A dot, matching the source spelling a struct literal
format: emacs/flan-inspect.el parses it back, and it is written in. This printed form is a wire format --
hard-codes the colon. Moving the printer to the dot has emacs/flan-inspect.el parses it back to build the field
to land with that reader, in the Emacs lane. *) list -- so the two moved together; see that file's
@ [ lit (":" ^ f.Tast.fname ^ " ") ] [flan-inspect--read-struct]. *)
@ [ lit ("." ^ f.Tast.fname ^ " ") ]
@ render c (depth + 1) v) @ render c (depth + 1) v)
shown) shown)
in in

View File

@ -150,15 +150,15 @@ 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
the reserve shows up here as a number and not as a wall of x. *) the reserve shows up here as a number and not as a wall of x. *)
^ "(Long {:s \"" ^ String.make 1014 'x' ^ "...\"})\n" ^ "(Long {.s \"" ^ String.make 1014 'x' ^ "...\"})\n"
^ "abc\n" ^ "abc\n"
in in
outputs "println, every arm" "programs/println.flan" println_out; outputs "println, every arm" "programs/println.flan" println_out;

View File

@ -775,12 +775,11 @@ let () =
let want = let want =
[ ("n", "i64", "3"); [ ("n", "i64", "3");
("label", "string", "\"hello\""); ("label", "string", "\"hello\"");
(* Colons, deliberately: this is the *renderer's* output, not (* The *renderer's* output, not source — and it is a dot now.
source. [render.ml] still prints [{:x ...}] because [render.ml] and [emacs/flan-inspect.el] are the two ends of
[flan-inspect.el] parses that text and the two move together one wire format, and they moved together, which is what the
or not at all see NEXT.md. Converting this line with the note here used to say was still owed. *)
rest was wrong and the suite caught it. *) ("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") ]
in in

View File

@ -96,9 +96,9 @@ let () =
this would otherwise come back as -1. *) this would otherwise come back as -1. *)
value "u64 at its maximum" "big" "18446744073709551615"; value "u64 at its maximum" "big" "18446744073709551615";
(* 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. *)