#_ 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
|
Lisp's, because [,] is already whitespace here (see [is_delimiter]) and
|
||||||
every binding vector in the corpus relies on that.
|
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
|
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
|
symbol, so it cannot rot into a silently-wrong name the way quote would
|
||||||
have. *)
|
have. *)
|
||||||
@ -156,7 +167,7 @@ let wrap open_c items =
|
|||||||
| _ -> assert false
|
| _ -> assert false
|
||||||
|
|
||||||
let rec read_form st =
|
let rec read_form st =
|
||||||
skip_trivia st;
|
skip_ignorable st;
|
||||||
let loc = here st in
|
let loc = here st in
|
||||||
match peek st with
|
match peek st with
|
||||||
| '\000' -> Loc.fail loc "unexpected end of input"
|
| '\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
|
| ('-' | '+') when is_digit (peek2 st) -> read_number st
|
||||||
| _ -> read_symbol_or_keyword 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
|
(* 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
|
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. *)
|
character the reader saw rather than at the form after it. *)
|
||||||
@ -190,7 +219,10 @@ and read_seq st open_c loc =
|
|||||||
advance st;
|
advance st;
|
||||||
let want = closer open_c in
|
let want = closer open_c in
|
||||||
let rec go acc =
|
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
|
if at_end st then
|
||||||
Loc.fail loc "unclosed %C, expected %C" open_c want
|
Loc.fail loc "unclosed %C, expected %C" open_c want
|
||||||
else
|
else
|
||||||
@ -206,7 +238,9 @@ and read_seq st open_c loc =
|
|||||||
let read_all ~file src =
|
let read_all ~file src =
|
||||||
let st = of_string ~file src in
|
let st = of_string ~file src in
|
||||||
let rec go acc =
|
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)
|
if at_end st then List.rev acc else go (read_form st :: acc)
|
||||||
in
|
in
|
||||||
go []
|
go []
|
||||||
|
|||||||
@ -162,6 +162,35 @@ let () =
|
|||||||
reads "backtick ends a name" "(f a`b)" "(f a (quasiquote b))";
|
reads "backtick ends a name" "(f a`b)" "(f a (quasiquote b))";
|
||||||
reads "backtick in vec" "[`a ~b]" "[(quasiquote a) (unquote 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. *)
|
(* The whole class: no reader-significant character may end up inside a name. *)
|
||||||
let rec bad_names f =
|
let rec bad_names f =
|
||||||
let open Form in
|
let open Form in
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user