#_ discards the next form, so commenting one out is not paren counting
Clojure's spelling and Clojure's semantics. Repeated — #_#_ a b c — discards that many following forms, and that falls out of the recursion rather than being counted: the discard reads *a form*, and the form it reads may itself begin with a discard, so the outer one throws away what the inner one already stepped past. It belongs to read_form rather than to the sequence readers, which is what makes it work in every position a form can appear — top level, inside a list or a vector or a map, before or after a quote. The two loops that look for a closer or for end of input skip it as well, because a discard is not an element and a file ending in one has read everything there is to read. A trailing #_ with nothing after it is an error, and it is the same error an unterminated form already gives.
This commit is contained in:
parent
0d549a6b6e
commit
c9e9d93a91
@ -13,6 +13,17 @@
|
||||
Lisp's, because [,] is already whitespace here (see [is_delimiter]) and
|
||||
every binding vector in the corpus relies on that.
|
||||
|
||||
[#_] discards the form after it, as in Clojure: it is read and thrown away,
|
||||
so commenting out a form does not mean counting its closing parens. Repeated
|
||||
([#_#_]) discards that many following forms, which falls out of the
|
||||
recursion rather than being counted — the discard reads *a form*, and the
|
||||
form it reads may itself begin with a discard.
|
||||
|
||||
It is a property of [read_form] rather than of the sequence readers, so it
|
||||
works in every position a form can appear: at the top level, inside a list
|
||||
or a vector or a map, and after a quote. A trailing [#_] with nothing after
|
||||
it is the one error, and it is the same error an unterminated form gives.
|
||||
|
||||
Not handled yet: metadata ([^:async]). It is rejected rather than read as a
|
||||
symbol, so it cannot rot into a silently-wrong name the way quote would
|
||||
have. *)
|
||||
@ -156,7 +167,7 @@ let wrap open_c items =
|
||||
| _ -> assert false
|
||||
|
||||
let rec read_form st =
|
||||
skip_trivia st;
|
||||
skip_ignorable st;
|
||||
let loc = here st in
|
||||
match peek st with
|
||||
| '\000' -> Loc.fail loc "unexpected end of input"
|
||||
@ -177,6 +188,24 @@ let rec read_form st =
|
||||
| ('-' | '+') when is_digit (peek2 st) -> read_number st
|
||||
| _ -> read_symbol_or_keyword st
|
||||
|
||||
(* Whitespace, comments, and forms that are read only to be thrown away.
|
||||
[#_] is handled here rather than in [read_form]'s match so that it is gone
|
||||
before *anything* looks at what comes next: a discard is not a form, so a
|
||||
sequence must not count it as an element and a top-level loop must not stop
|
||||
on it.
|
||||
|
||||
[#_#_ a b c] discards [a] and [b] with no counting. The outer discard reads
|
||||
one form; that read is [read_form], which sees the inner [#_], discards [a]
|
||||
and returns [b]; the outer discard then throws [b] away. What is left is
|
||||
[c]. *)
|
||||
and skip_ignorable st =
|
||||
skip_trivia st;
|
||||
if peek st = '#' && peek2 st = '_' then begin
|
||||
advance st; advance st;
|
||||
ignore (read_form st : Form.t);
|
||||
skip_ignorable st
|
||||
end
|
||||
|
||||
(* One sigil character, then the form it applies to, wrapped in a name. The
|
||||
name's location is the sigil's, so an error inside the wrapper points at the
|
||||
character the reader saw rather than at the form after it. *)
|
||||
@ -190,7 +219,10 @@ and read_seq st open_c loc =
|
||||
advance st;
|
||||
let want = closer open_c in
|
||||
let rec go acc =
|
||||
skip_trivia st;
|
||||
(* [skip_ignorable], not [skip_trivia]: a discard just before the closer —
|
||||
[(a #_b)] — has to be gone before the closer is looked for, or the
|
||||
sequence would try to read a form and find [)]. *)
|
||||
skip_ignorable st;
|
||||
if at_end st then
|
||||
Loc.fail loc "unclosed %C, expected %C" open_c want
|
||||
else
|
||||
@ -206,7 +238,9 @@ and read_seq st open_c loc =
|
||||
let read_all ~file src =
|
||||
let st = of_string ~file src in
|
||||
let rec go acc =
|
||||
skip_trivia st;
|
||||
(* Same reason as in [read_seq]: a file ending in [#_(defn …)] has read
|
||||
everything there is to read, and must not then be asked for a form. *)
|
||||
skip_ignorable st;
|
||||
if at_end st then List.rev acc else go (read_form st :: acc)
|
||||
in
|
||||
go []
|
||||
|
||||
@ -162,6 +162,35 @@ let () =
|
||||
reads "backtick ends a name" "(f a`b)" "(f a (quasiquote b))";
|
||||
reads "backtick in vec" "[`a ~b]" "[(quasiquote a) (unquote b)]";
|
||||
|
||||
(* ── Discard ───────────────────────────────────────────────────── *)
|
||||
(* [#_] reads the next form and throws it away, so commenting out a form does
|
||||
not mean counting its closing parens. Clojure's spelling and Clojure's
|
||||
semantics, including the repeated form. *)
|
||||
reads "discard in a call" "(f #_a b)" "(f b)";
|
||||
reads "discard the last" "(f a #_b)" "(f a)";
|
||||
reads "discard the first" "(#_f g a)" "(g a)";
|
||||
reads "discard a list" "(f #_(g x) b)" "(f b)";
|
||||
reads "discard in a vector" "[a #_b c]" "[a c]";
|
||||
reads "discard in a map" "{:a 1 #_:b #_2 :c 3}" "{:a 1 :c 3}";
|
||||
(* Two discards drop two forms, and that is the recursion rather than a
|
||||
count: the outer discard reads one form, and the form it reads is itself a
|
||||
discard that returns the one after. *)
|
||||
reads "two discards" "(f #_#_a b c)" "(f c)";
|
||||
reads "three discards" "(f #_#_#_a b c d)" "(f d)";
|
||||
(* Every position a form can appear in. *)
|
||||
reads "discard at top level" "#_(defn a [] 1) (defn b [] 2)" "(defn b [] 2)";
|
||||
reads "discard a whole file" "#_(defn a [] 1)" "";
|
||||
reads "discard before quote" "(f #_a 'b)" "(f (quote b))";
|
||||
reads "discard of a quote" "(f #_'a b)" "(f b)";
|
||||
(* Nested, which the recursive read gives for free. *)
|
||||
reads "discard inside a discarded form" "(f #_(g #_h i) j)" "(f j)";
|
||||
(* A name may still contain '#' — it is only a discard at the start of a
|
||||
form, after trivia. *)
|
||||
reads "hash inside a name" "(f a#_b)" "(f a#_b)";
|
||||
(* Nothing to discard is an error, not a silent nothing. *)
|
||||
rejects ~needle:"end of input" "discard at end of input" "(f a #_";
|
||||
rejects ~needle:"unbalanced" "discard of a closing paren" "(f a #_)";
|
||||
|
||||
(* The whole class: no reader-significant character may end up inside a name. *)
|
||||
let rec bad_names f =
|
||||
let open Form in
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user