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:
parent
a0610cecd5
commit
0f3b633449
@ -60,12 +60,12 @@ reply without a daemon behind them, and so that this file names
|
||||
;;; Reading what the renderer wrote
|
||||
|
||||
;; 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
|
||||
;; small and fixed by `Session.render' (lib/session.ml), and every case below
|
||||
;; 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
|
||||
;; [ V V V] an array or a slice, ` ...' likewise
|
||||
;; (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))))
|
||||
(if (and (< k (length s)) (eq (aref s k) ?\))) (1+ k) k))))
|
||||
(t
|
||||
;; `(Name {' then `:field VALUE' pairs, then `})'.
|
||||
;; `(Name {' then `.field VALUE' pairs, then `})'.
|
||||
(setq j (flan-inspect--skip-space s j))
|
||||
(when (and (< j (length s)) (eq (aref s j) ?\{)) (setq j (1+ j)))
|
||||
(let ((kids nil) (more nil) (done nil))
|
||||
@ -162,7 +162,18 @@ reply without a daemon behind them, and so that this file names
|
||||
(cond
|
||||
((>= j (length s)) (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))
|
||||
(while (and (< j (length s)) (not (memq (aref s j) '(?\s ?\}))))
|
||||
(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.")
|
||||
|
||||
(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)))
|
||||
(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
|
||||
|
||||
|
||||
@ -31,10 +31,10 @@
|
||||
;; - 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
|
||||
;; 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
|
||||
;; literal and not a map with symbol keys. The indenter is correct for
|
||||
;; both spellings *because* it never looks at the key: a brace aligns under
|
||||
;; its first element whatever that element is spelled like.
|
||||
;; - a field label is a dot: `{.x 1.0}' is a struct literal, and the colon
|
||||
;; now belongs to keywords, which a `Map' will use as keys. The indenter
|
||||
;; is correct for both *because* it never looks at the key: a brace aligns
|
||||
;; under its first element whatever that element is spelled like.
|
||||
|
||||
;;; Code:
|
||||
|
||||
@ -99,10 +99,11 @@
|
||||
;; A keyword resolves against an enum at the call site, so it reads as a
|
||||
;; constant rather than as a string.
|
||||
("\\_<:\\(?:\\sw\\|\\s_\\)+" . font-lock-constant-face)
|
||||
;; A field, in both of the spellings that exist while the corpus moves from
|
||||
;; `{: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, which is also
|
||||
;; what the keyword rule above did for the spelling being replaced.
|
||||
;; A field. The label in a struct literal — `{.x 1.0}' — and the accessor
|
||||
;; `(.x v)' are the same name and are drawn the same way. Without this
|
||||
;; rule every field label in the corpus is unfontified, which is what the
|
||||
;; 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)
|
||||
;; 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\\)\\_>"
|
||||
@ -306,9 +307,9 @@ Point is on the opening delimiter.
|
||||
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
|
||||
name-under-name, and by the same rule it lines up `defn' parameter lists,
|
||||
`restart-case' clause parameters, `handler-bind' clause vectors, and both
|
||||
spellings of a struct literal — `{:x 1}' and `{.x 1}' — with no case for any
|
||||
of them, because the rule is about the bracket and never about what is
|
||||
`restart-case' clause parameters, `handler-bind' clause vectors, and a brace
|
||||
whatever its keys are spelled like — `{.x 1}' and `{:north 0}' — with no case
|
||||
for any of them, because the rule is about the bracket and never about what is
|
||||
written inside it.
|
||||
|
||||
A head that is not a symbol is the third case: `((f x) y)' has nothing to look
|
||||
|
||||
@ -44,8 +44,8 @@
|
||||
|
||||
(message "\nreading what the renderer wrote")
|
||||
|
||||
;; (V {:x 1.5 :y 0}) — Types.Named, lib/session.ml.
|
||||
(let ((n (flan-inspect-parse "(V {:x 1.5 :y 0})")))
|
||||
;; (V {.x 1.5 .y 0}) — Types.Named, lib/session.ml.
|
||||
(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 "with its type name" (equal (plist-get n :type) "V"))
|
||||
(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
|
||||
;; 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"
|
||||
@ -102,7 +102,7 @@
|
||||
|
||||
;; 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.
|
||||
(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"
|
||||
(and (equal (mapcar #'car (plist-get n :children)) '("a" "b"))
|
||||
(plist-get n :truncated))))
|
||||
@ -134,7 +134,7 @@
|
||||
(and why (string-match-p (regexp-quote (cadr case)) why)))))
|
||||
|
||||
(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"
|
||||
(string-match-p "does not write the address"
|
||||
@ -195,14 +195,14 @@
|
||||
(string-match-p "0xFF 0b1111_1111" text))))
|
||||
|
||||
(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*"))
|
||||
(let ((text (with-current-buffer (save-window-excursion (flan-inspect "m"))
|
||||
(buffer-string))))
|
||||
(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"
|
||||
(string-match-p ":name +\"all\"\n" text))))
|
||||
(string-match-p "\\.name +\"all\"\n" text))))
|
||||
|
||||
|
||||
;;; The inspector buffer
|
||||
@ -218,13 +218,13 @@
|
||||
(save-window-excursion (flan-inspect--show expr nil))))
|
||||
|
||||
(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))))
|
||||
(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 "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"
|
||||
(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))
|
||||
;; Every field line carries the step that reaches it.
|
||||
(with-current-buffer buf
|
||||
@ -276,8 +276,8 @@
|
||||
(push (plist-get form :code) asked)
|
||||
(list :status "ok"
|
||||
:value (if (equal (plist-get form :code) "(.pos b)")
|
||||
"(V {:x 1.5 :y 0})"
|
||||
"(Blob {:id 7 :pos (V {:x 1.5 :y 0})})"))))
|
||||
"(V {.x 1.5 .y 0})"
|
||||
"(Blob {.id 7 .pos (V {.x 1.5 .y 0})})"))))
|
||||
(flan-inspect-buffer " *test-inspect*"))
|
||||
(when (get-buffer " *test-inspect*") (kill-buffer " *test-inspect*"))
|
||||
(save-window-excursion
|
||||
@ -304,7 +304,7 @@
|
||||
|
||||
;; RET on something that cannot be entered refuses there, rather than sending
|
||||
;; 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
|
||||
(goto-char (point-min))
|
||||
(flan-inspect-next)
|
||||
@ -321,7 +321,7 @@
|
||||
;; The span bound is drawn, because a field list that silently stops is a lie
|
||||
;; about the value.
|
||||
(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"
|
||||
(string-match-p "span bound of 8" text)))
|
||||
|
||||
@ -456,7 +456,7 @@
|
||||
:stack (list (list :fn "sim/settle" :loc "sand.flan:42:3"
|
||||
:fetched t
|
||||
:locals '(("i" "i32" "7")
|
||||
("b" "Blob" "(Blob {:id 7})")))
|
||||
("b" "Blob" "(Blob {.id 7})")))
|
||||
(list :fn "sim/step" :loc "sand.flan:60:1"
|
||||
:fetched t :locals nil))))
|
||||
(buf (test-flan--cnr state))
|
||||
@ -496,7 +496,7 @@
|
||||
(let ((asked nil))
|
||||
(let ((flan-inspect-request-function
|
||||
(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*"))
|
||||
(with-current-buffer (test-flan--cnr
|
||||
(list :condition "Missing" :restarts '("retry")
|
||||
|
||||
@ -81,7 +81,7 @@
|
||||
;; survive a `{' in the middle of it.
|
||||
(test-flan-mode--check
|
||||
"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)]
|
||||
(draw tip vel))")
|
||||
|
||||
@ -90,17 +90,21 @@
|
||||
;; element is — and this is the test that keeps it that way while the corpus
|
||||
;; moves from `:x' to `.x'.
|
||||
(test-flan-mode--check
|
||||
"a struct literal aligns under its first field, keyword spelling"
|
||||
"(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"
|
||||
"a struct literal aligns under its first field"
|
||||
"(rl/Rectangle {.x 0.0 .y 0.0
|
||||
.width (f32 screen-width)
|
||||
.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
|
||||
;; 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
|
||||
@ -204,5 +208,39 @@
|
||||
c 3]
|
||||
(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)
|
||||
;;; test-flan-mode.el ends here
|
||||
|
||||
@ -147,11 +147,12 @@ let rec render c depth (e : Tast.expr) : Tast.expr list =
|
||||
(fun i (f : Tast.field) ->
|
||||
let v = { Tast.e = Tast.Field (e, i); ty = f.Tast.fty; loc } in
|
||||
(if i = 0 then [] else [ lit " " ])
|
||||
(* Still a colon, deliberately. This printed form is a wire
|
||||
format: emacs/flan-inspect.el parses it back, and it
|
||||
hard-codes the colon. Moving the printer to the dot has
|
||||
to land with that reader, in the Emacs lane. *)
|
||||
@ [ lit (":" ^ f.Tast.fname ^ " ") ]
|
||||
(* A dot, matching the source spelling a struct literal
|
||||
is written in. This printed form is a wire format --
|
||||
emacs/flan-inspect.el parses it back to build the field
|
||||
list -- so the two moved together; see that file's
|
||||
[flan-inspect--read-struct]. *)
|
||||
@ [ lit ("." ^ f.Tast.fname ^ " ") ]
|
||||
@ render c (depth + 1) v)
|
||||
shown)
|
||||
in
|
||||
|
||||
@ -150,15 +150,15 @@ 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\
|
||||
(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\
|
||||
(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
|
||||
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"
|
||||
in
|
||||
outputs "println, every arm" "programs/println.flan" println_out;
|
||||
|
||||
@ -775,12 +775,11 @@ let () =
|
||||
let want =
|
||||
[ ("n", "i64", "3");
|
||||
("label", "string", "\"hello\"");
|
||||
(* Colons, deliberately: this is the *renderer's* output, not
|
||||
source. [render.ml] still prints [{:x ...}] because
|
||||
[flan-inspect.el] parses that text and the two move together
|
||||
or not at all — see NEXT.md. Converting this line with the
|
||||
rest was wrong and the suite caught it. *)
|
||||
("p", "Point", "(Point {:x 1.5 :y 2.5})");
|
||||
(* The *renderer's* output, not source — and it is a dot now.
|
||||
[render.ml] and [emacs/flan-inspect.el] are the two ends of
|
||||
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]");
|
||||
("flag", "bool", "true") ]
|
||||
in
|
||||
|
||||
@ -96,9 +96,9 @@ let () =
|
||||
this would otherwise come back as -1. *)
|
||||
value "u64 at its maximum" "big" "18446744073709551615";
|
||||
(* 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"
|
||||
"(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]";
|
||||
(* A slice's length is not known until it runs, so this one renders
|
||||
through a loop rather than by unrolling. *)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user