Repository: basilisp-lang/basilisp

Issue:
Ignore next form (`#_`) only works for the next form
In JVM/CLJS (others) clojure, we can use N `#_` in sequence to ignore the next N forms, while for basilisp I have an error (at least when using it from a `let` as the error seems to be related to the macroexpansion).

```clojure
;; This explodes.
(let [#_ #_ _ (bpy.ops.ed/undo_push)
      a 3])

;; This is fine
(let [#_ _ #_ (bpy.ops.ed/undo_push)
      a 3])
```

_Error_

```
 exception: <class 'basilisp.lang.exception.ExceptionInfo'> from <class 'basilisp.lang.compiler.exception.CompilerException'>
      phase: :macroexpansion
    message: error occurred during macroexpansion: Invalid destructuring argument type {:type :other}
       form: (let [(bpy.ops.ed/undo_push) a 3])
   location: /Users/pfeodrippe/dev/vybe/basilisp/src/vybe/basilisp/bake.lpy:54-55
    context:

 50   |       (set! bpy.context.view_layer.objects/active o)))
 51   | 
 52   | 
 53   | 
 54 > |   (let [#_ #_ _ (bpy.ops.ed/undo_push)
 55 > |         a 3])
 56   | 
```

Hints from issue comments:
@pfeodrippe: It also doesn't work for a map

```clojure
{#_ #_:a 3
 :b 5}
```

```
 exception: <class 'ValueError'> from <class 'basilisp.lang.reader.SyntaxError'>
    message: Unexpected char '}'; expected map value: not enough values to unpack (expected 2, got 1)
       line: 42:8
```
@chrisrink10: Happy to accept a PR fixing this.@pfeodrippe: I tried to do some fix, but it wasn't working for some reason (or maybe it wasn't as easy as I was thinking), but I can check it again soon \o Thanks@pfeodrippe: Ohh, thank you very much!! 

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index de9811e..089fb0d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
  * Single arity functions can be tagged with `^:allow-unsafe-names` to preserve their parameter names (#1212)
 
 ### Fixed
- * Fix an issue where the compiler would generate an illegal `return` statement for asynchronous generators (#1180) 
+ * Fix an issue where the compiler would generate an illegal `return` statement for asynchronous generators (#1180)
+ * Fix an issue where consecutive reader comment forms would not be ignored (#1207)
 
 ## [v0.3.7]
 ### Fixed 
diff --git a/src/basilisp/lang/reader.py b/src/basilisp/lang/reader.py
index 1f50fb0..d9a532d 100644
--- a/src/basilisp/lang/reader.py
+++ b/src/basilisp/lang/reader.py
@@ -1667,7 +1667,7 @@ def _read_reader_macro(ctx: ReaderContext) -> LispReaderForm:  # noqa: MC0001
         return _read_regex(ctx)
     elif char == "_":
         ctx.reader.advance()
-        _read_next(ctx)  # Ignore the entire next form
+        _read_next_consuming_comment(ctx)  # Ignore the entire next form
         return COMMENT
     elif char == "!":
         return _read_comment(ctx)


Test patch:
diff --git a/tests/basilisp/reader_test.py b/tests/basilisp/reader_test.py
index a089d2a..72d579f 100644
--- a/tests/basilisp/reader_test.py
+++ b/tests/basilisp/reader_test.py
@@ -1524,65 +1524,61 @@ class TestMetadata:
             read_str_first(s)
 
 
-def test_comment_reader_macro():
-    with pytest.raises(EOFError):
-        read_str_first("#_       (a list)", is_eof_error=True)
-
-    with pytest.raises(EOFError):
-        read_str_first("#_1", is_eof_error=True)
-
-    with pytest.raises(EOFError):
-        read_str_first('#_"string"', is_eof_error=True)
-
-    with pytest.raises(EOFError):
-        read_str_first("#_:keyword", is_eof_error=True)
-
-    with pytest.raises(EOFError):
-        read_str_first("#_symbol", is_eof_error=True)
-
-    with pytest.raises(EOFError):
-        read_str_first("#_[]", is_eof_error=True)
-
-    with pytest.raises(EOFError):
-        read_str_first("#_{}", is_eof_error=True)
-
-    with pytest.raises(EOFError):
-        read_str_first("#_()", is_eof_error=True)
-
-    with pytest.raises(EOFError):
-        read_str_first("#_#{}", is_eof_error=True)
-
-    assert kw.keyword("kw2") == read_str_first("#_:kw1 :kw2")
-
-    assert llist.EMPTY == read_str_first("(#_sym)")
-    assert llist.l(sym.symbol("inc"), 5) == read_str_first("(inc #_counter 5)")
-    assert llist.l(sym.symbol("dec"), 8) == read_str_first("(#_inc dec #_counter 8)")
-
-    assert vec.EMPTY == read_str_first("[#_m]")
-    assert vec.v(1) == read_str_first("[#_m 1]")
-    assert vec.v(1) == read_str_first("[#_m 1 #_2]")
-    assert vec.v(1, 2) == read_str_first("[#_m 1 2]")
-    assert vec.v(1, 4) == read_str_first("[#_m 1 #_2 4]")
-    assert vec.v(1, 4) == read_str_first("[#_m 1 #_2 4 #_5]")
-
-    assert lset.EMPTY == read_str_first("#{#_m}")
-    assert lset.s(1) == read_str_first("#{#_m 1}")
-    assert lset.s(1) == read_str_first("#{#_m 1 #_2}")
-    assert lset.s(1, 2) == read_str_first("#{#_m 1 2}")
-    assert lset.s(1, 4) == read_str_first("#{#_m 1 #_2 4}")
-    assert lset.s(1, 4) == read_str_first("#{#_m 1 #_2 4 #_5}")
-
-    assert lmap.EMPTY == read_str_first("{#_:key}")
-    assert lmap.EMPTY == read_str_first('{#_:key #_"value"}')
-    assert lmap.map({kw.keyword("key"): "value"}) == read_str_first(
-        '{:key #_"other" "value"}'
+class TestCommentReaderMacro:
+    @pytest.mark.parametrize(
+        "s",
+        [
+            "#_       (a list)",
+            "#_1",
+            '#_"string"',
+            "#_:keyword",
+            "#_symbol",
+            "#_[]",
+            "#_{}",
+            "#_()",
+            "#_#{}",
+            "#_ #_ {} {}",
+        ],
     )
-    assert lmap.map({kw.keyword("key"): "value"}) == read_str_first(
-        '{:key "value" #_"other"}'
+    def test_comment_suppresses_form_with_eof(self, s: str):
+        with pytest.raises(EOFError):
+            read_str_first(s, is_eof_error=True)
+
+    @pytest.mark.parametrize(
+        "s,v",
+        [
+            ("#_:kw1 :kw2", kw.keyword("kw2")),
+            ("#_ #_ :kw1 :kw2 :kw3", kw.keyword("kw3")),
+            ("(#_sym)", llist.EMPTY),
+            ("(#_ #_ sym 1)", llist.EMPTY),
+            ("(inc #_counter 5)", llist.l(sym.symbol("inc"), 5)),
+            ("(#_inc dec #_counter 8)", llist.l(sym.symbol("dec"), 8)),
+            ("[#_m]", vec.EMPTY),
+            ("[#_m 1]", vec.v(1)),
+            ("[#_m 1 #_2]", vec.v(1)),
+            ("[#_m 1 2]", vec.v(1, 2)),
+            ("[#_m 1 #_2 4]", vec.v(1, 4)),
+            ("[#_m 1 #_2 4 #_5]", vec.v(1, 4)),
+            ("#{#_m}", lset.EMPTY),
+            ("#{#_m 1}", lset.s(1)),
+            ("#{#_m 1 #_2}", lset.s(1)),
+            ("#{#_m 1 2}", lset.s(1, 2)),
+            ("#{#_m 1 #_2 4}", lset.s(1, 4)),
+            ("#{#_m 1 #_2 4 #_5}", lset.s(1, 4)),
+            ("{#_:key}", lmap.EMPTY),
+            ('{#_:key #_"value"}', lmap.EMPTY),
+            ('{#_ #_:key "value"}', lmap.EMPTY),
+            ('{:key #_"other" "value"}', lmap.map({kw.keyword("key"): "value"})),
+            ('{:key "value" #_"other"}', lmap.map({kw.keyword("key"): "value"})),
+            ("{#_ #_:a 3 :b 5}", lmap.map({kw.keyword("b"): 5})),
+        ],
     )
+    def test_comment_suppresses_form_in_other_form(self, s: str, v):
+        assert v == read_str_first(s)
 
-    with pytest.raises(reader.SyntaxError):
-        read_str_first('{:key #_"value"}')
+    def test_comment_creates_syntax_error(self):
+        with pytest.raises(reader.SyntaxError):
+            read_str_first('{:key #_"value"}')
 
 
 def test_comment_line():