C-c C-m. One step on the bare key, the fixpoint under C-u: a macro may quasiquote a call to another macro, and Loc.from_macro is outermost-wins, so by the time a full expansion settles the intermediate name is gone. One step is the only thing that can say which macro produced what. The expansion runs against the macros the *session* holds -- the prelude's, its imports', and every defmacro evaluated since it started -- and writes nothing back: a defmacro handed to C-c C-m does not join the session by having been looked at. Both non-termination refusals stay refusals, and only where they are needed. One step makes one call and does not look at the answer, so (s/spin) one- stepped answers with itself; all the way hits the fuel and names the macro, inside Dev.serve's guard, so the daemon replies rather than hanging. Macro's module handling is a Fun.protect now -- a build that raised was a process about to exit, and the daemon is not that process. No printer for a Form existed. Form.to_string is an error-message renderer and is what Macro.key digests, so it is untouched; Form.to_source round-trips floats, strings and bytes through the reader, and Form.pretty decides where the line breaks go and leaves the columns to flan-mode. The answer is a read-only flan-mode buffer shaped like the disassembly one, with cnr's idea in it: m expands the form at point one more step in place. Three inherited keys refuse by name -- an expansion is in no file. The text is sent padded onto its own line and its own column, unlike C-x C-e, so the refusal lands on the call and not at the start of its line.
187 lines
7.2 KiB
OCaml
187 lines
7.2 KiB
OCaml
(** The reader's output: syntax, before any typing or macro expansion.
|
|
|
|
Deliberately dumb. [true], [false] and [nil] are ordinary symbols here and
|
|
are resolved later; the reader knows nothing about special forms. *)
|
|
|
|
type t = {
|
|
v : value;
|
|
loc : Loc.t;
|
|
}
|
|
|
|
and value =
|
|
| Sym of string (* foo rl/draw-fps .pos + *)
|
|
| Kw of string (* :space :else (leading : dropped) *)
|
|
| Int of int64 (* 42 -1 0xE6B800FF *)
|
|
| Float of float (* 0.05 *)
|
|
| Str of string (* "SAND" *)
|
|
| Byte of int (* \space \0 \( (0..255) *)
|
|
| List of t list (* (f x) *)
|
|
| Vec of t list (* [1 2 3] and every binding/type bracket *)
|
|
| Map of t list (* {.field v} a struct value, {K V} a type. The
|
|
colon spelling is left for map literals. *)
|
|
|
|
let make v loc = { v; loc }
|
|
|
|
let rec to_string f =
|
|
let seq l = String.concat " " (List.map to_string l) in
|
|
match f.v with
|
|
| Sym s -> s
|
|
| Kw s -> ":" ^ s
|
|
| Int i -> Int64.to_string i
|
|
| Float x -> Printf.sprintf "%g" x
|
|
| Str s -> Printf.sprintf "%S" s
|
|
| Byte b ->
|
|
(match Char.chr b with
|
|
| ' ' -> "\\space"
|
|
| '\t' -> "\\tab"
|
|
| '\n' -> "\\newline"
|
|
| c -> Printf.sprintf "\\%c" c)
|
|
| List l -> "(" ^ seq l ^ ")"
|
|
| Vec l -> "[" ^ seq l ^ "]"
|
|
| Map l -> "{" ^ seq l ^ "}"
|
|
|
|
(* ── Printing a Form back as source ─────────────────────────────────
|
|
|
|
[to_string] above is an error-message renderer: one line, no width, and
|
|
[%S] and [%g] where a reader's own spelling was never needed to say
|
|
"expected a name, found this". It is also what [Macro.key] digests, so it
|
|
is left exactly as it is — every cached macro module on disk is keyed by
|
|
what it prints today.
|
|
|
|
What follows is the other job, and it arrived with [C-c C-m]: text a person
|
|
reads and a reader reads back. An expansion is *only* text. A macro answers
|
|
a [Form] and nothing in the language ever wrote it down, so unlike every
|
|
other thing the editor shows there is no file to point at and no source to
|
|
fall back on — whatever this prints is the whole of what anybody sees.
|
|
|
|
Three places where [to_string] is not a round trip, all of them reachable
|
|
from an expansion because a macro may build any literal at all:
|
|
|
|
- [%g] prints 1.0 as "1", which reads back as an [Int], and it truncates at
|
|
six significant digits. Shortest-round-trip here, then a ".0" when
|
|
nothing in the text says "float".
|
|
- [%S] is OCaml's escaping. The reader takes exactly six escapes — newline,
|
|
tab, return, backslash, quote and nul — and every other byte literally,
|
|
so the three-digit decimal escape [%S] writes would not read back.
|
|
- [Byte] falls through to \<char>, which spells 0 and 13 as a NUL and a
|
|
carriage return sitting in the middle of the source. The reader has names
|
|
for those and this uses them. *)
|
|
|
|
let escape s =
|
|
let b = Buffer.create (String.length s + 2) in
|
|
String.iter
|
|
(fun c ->
|
|
match c with
|
|
| '\n' -> Buffer.add_string b "\\n"
|
|
| '\t' -> Buffer.add_string b "\\t"
|
|
| '\r' -> Buffer.add_string b "\\r"
|
|
| '\\' -> Buffer.add_string b "\\\\"
|
|
| '"' -> Buffer.add_string b "\\\""
|
|
| '\000' -> Buffer.add_string b "\\0"
|
|
| c -> Buffer.add_char b c)
|
|
s;
|
|
Buffer.contents b
|
|
|
|
let float_repr x =
|
|
(* The shortest of the three precisions that survives [float_of_string] is
|
|
the one the reader parses back to the same bits. 15 covers almost every
|
|
literal anyone writes; 17 covers every double there is. *)
|
|
let rec shortest = function
|
|
| [] -> Printf.sprintf "%.17g" x
|
|
| p :: ps ->
|
|
let s = Printf.sprintf "%.*g" p x in
|
|
if float_of_string s = x then s else shortest ps
|
|
in
|
|
let s = shortest [ 15; 16; 17 ] in
|
|
(* "1" is an integer to the reader, so a float whose text has no point and
|
|
no exponent needs one. Guarded on the text and not on the value: nan and
|
|
the infinities print as words, and "nan.0" is no improvement on a literal
|
|
no reader accepts either way. *)
|
|
let plain =
|
|
s <> ""
|
|
&& String.for_all (fun c -> (c >= '0' && c <= '9') || c = '-' || c = '+') s
|
|
in
|
|
if plain then s ^ ".0" else s
|
|
|
|
let byte_repr b =
|
|
match b with
|
|
| 32 -> "\\space"
|
|
| 9 -> "\\tab"
|
|
| 10 -> "\\newline"
|
|
| 13 -> "\\return"
|
|
| 0 -> "\\nul"
|
|
(* Printable ASCII is written as itself. Anything else has no spelling in
|
|
the reader at all — [read_byte] takes a name or a single character — so
|
|
it is written as the decimal the reader would have to grow, rather than
|
|
as a byte that would corrupt the line it is on. *)
|
|
| b when b > 32 && b < 127 -> Printf.sprintf "\\%c" (Char.chr b)
|
|
| b -> Printf.sprintf "\\%d" b
|
|
|
|
(** One line, and a reader reads it back. *)
|
|
let rec to_source f =
|
|
let seq l = String.concat " " (List.map to_source l) in
|
|
match f.v with
|
|
| Sym s -> s
|
|
| Kw s -> ":" ^ s
|
|
| Int i -> Int64.to_string i
|
|
| Float x -> float_repr x
|
|
| Str s -> "\"" ^ escape s ^ "\""
|
|
| Byte b -> byte_repr b
|
|
| List l -> "(" ^ seq l ^ ")"
|
|
| Vec l -> "[" ^ seq l ^ "]"
|
|
| Map l -> "{" ^ seq l ^ "}"
|
|
|
|
(** The same text with line breaks in it, for a form too wide to read on one.
|
|
|
|
Where the breaks go, and deliberately not where the columns do. A list
|
|
that fits is written flat; one that does not keeps its head on the opening
|
|
line and puts each remaining element on its own, two columns in. That is
|
|
the structural half, which a printer has to decide. The indentation is the
|
|
editor's: [flan-mode] re-indents what it is shown, and that is where this
|
|
project's indentation rules already live, so nothing here tries to know
|
|
that a [let] aligns its bindings under the bracket. A client with no Emacs
|
|
still gets something readable rather than one very long line. *)
|
|
let pretty ?(width = 72) (f : t) : string =
|
|
let b = Buffer.create 256 in
|
|
let rec go col f =
|
|
let flat = to_source f in
|
|
if col + String.length flat <= width then Buffer.add_string b flat
|
|
else
|
|
let elements ind rest =
|
|
List.iter
|
|
(fun e ->
|
|
Buffer.add_char b '\n';
|
|
Buffer.add_string b (String.make ind ' ');
|
|
go ind e)
|
|
rest
|
|
in
|
|
match f.v with
|
|
(* A call or a special form: the head names what this is, so it stays on
|
|
the opening line whatever the rest of it costs. *)
|
|
| List (({ v = Sym _; _ } as h) :: rest) ->
|
|
Buffer.add_char b '(';
|
|
Buffer.add_string b (to_source h);
|
|
elements (col + 2) rest;
|
|
Buffer.add_char b ')'
|
|
| List (x :: rest) ->
|
|
Buffer.add_char b '(';
|
|
go (col + 1) x;
|
|
elements (col + 1) rest;
|
|
Buffer.add_char b ')'
|
|
| Vec (x :: rest) ->
|
|
Buffer.add_char b '[';
|
|
go (col + 1) x;
|
|
elements (col + 1) rest;
|
|
Buffer.add_char b ']'
|
|
| Map (x :: rest) ->
|
|
Buffer.add_char b '{';
|
|
go (col + 1) x;
|
|
elements (col + 1) rest;
|
|
Buffer.add_char b '}'
|
|
(* An empty bracket, or a single atom longer than the width. Neither has
|
|
a break in it to take. *)
|
|
| _ -> Buffer.add_string b flat
|
|
in
|
|
go 0 f;
|
|
Buffer.contents b
|