Skip to content

Commit 0a350bc

Browse files
authored
Throw a Syntax Error when a splicing reader conditional is outside a valid (#470)
1 parent 75a01d3 commit 0a350bc

File tree

4 files changed

+41
-7
lines changed

4 files changed

+41
-7
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
### Changed
1212
* Change the default user namespace to `basilisp.user` (#466)
1313

14+
### Fixed
15+
* Fixed a reader bug where no exception was being thrown splicing reader conditional forms appeared outside of valid splicing contexts (#470)
16+
1417
## [v0.1.dev12] - 2020-01-26
1518
### Added
1619
* Added new control structures: `dotimes`, `while`, `dorun`, `doall`, `case`, `for`, `doseq`, `..`, `with`, `doto` (#431)

docs/reader.rst

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,4 +360,32 @@ Syntax quoting is a facility primarily used for writing macros in Basilisp.
360360
Reader Conditionals
361361
-------------------
362362

363-
Reader conditionals are **not** supported at the moment by Basilisp's reader, though support is planned.
363+
Reader conditionals are a powerful reader feature which allow Basilisp to read code written for other Clojure-like platforms (such as Clojure JVM or ClojureScript) without experiencing catastrophic errors.
364+
Platform-specific Clojure code can be wrapped in reader conditionals and the reader will match only forms identified by supported reader "features".
365+
Features are just standard :ref:`keywords`.
366+
By default, Basilisp supports the ``:lpy`` feature.
367+
368+
Reader conditionals appear as Basilisp lists prefixed with the ``#?`` characters.
369+
Like maps, reader conditionals should always contain an even number of forms.
370+
Each pair should consist of the keyword used to identify the platform feature (such as ``:lpy`` for Basilisp) and the intended form for that feature.
371+
The reader may emit no forms (much like with the :ref:`reader_macros` ``#_``) if there are no supported features in the reader conditional form.
372+
373+
::
374+
375+
basilisp.user=> #?(:clj 1 :lpy 2)
376+
2
377+
basilisp.user=> #?(:clj 1)
378+
basilisp.user=>
379+
basilisp.user=> [#?@(:lpy [1 2 3])]
380+
[1 2 3]
381+
382+
For advanced use cases, reader conditionals may also be written to splice their contents into surrounding forms.
383+
Splicing reader conditionals are subject to the same rules as splicing unquote in a syntax quoting context.
384+
Splicing reader conditionals may only appear within other collection literal forms (such as lists, maps, sets, and vectors).
385+
386+
::
387+
388+
basilisp.user=> [#?@(:lpy [1 2 3])]
389+
[1 2 3]
390+
basilisp.user=> #?@(:lpy [1 2 3])
391+
basilisp.lang.reader.SyntaxError: Unexpected reader conditional

src/basilisp/lang/reader.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ def __read_map_elems(ctx: ReaderContext) -> Iterable[RawReaderForm]:
567567
if v is COMMENT or isinstance(v, Comment):
568568
continue
569569
elif v is ctx.eof:
570-
raise SyntaxError(f"Unexpected EOF in map")
570+
raise SyntaxError("Unexpected EOF in map")
571571
elif _should_splice_reader_conditional(ctx, v):
572572
assert isinstance(v, ReaderConditional)
573573
selected_feature = v.select_feature(ctx.reader_features)
@@ -736,7 +736,7 @@ def _read_str(ctx: ReaderContext, allow_arbitrary_escapes: bool = False) -> str:
736736
if allow_arbitrary_escapes:
737737
s.append("\\")
738738
else:
739-
raise SyntaxError("Unknown escape sequence: \\{token}")
739+
raise SyntaxError(f"Unknown escape sequence: \\{token}")
740740
if token == '"':
741741
reader.next_token()
742742
return "".join(s)
@@ -1358,10 +1358,11 @@ def read( # pylint: disable=too-many-arguments
13581358
return
13591359
if expr is COMMENT or isinstance(expr, Comment):
13601360
continue
1361-
assert (
1362-
not isinstance(expr, ReaderConditional)
1363-
or not ctx.should_process_reader_cond
1364-
), "Reader conditionals must be processed if specified"
1361+
if isinstance(expr, ReaderConditional) and ctx.should_process_reader_cond:
1362+
raise SyntaxError(
1363+
f"Unexpected reader conditional '{repr(expr)})'; "
1364+
"reader is configured to process reader conditionals"
1365+
)
13651366
yield expr
13661367

13671368

tests/basilisp/reader_test.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,6 +892,8 @@ def test_basic_form_preserving(self):
892892
@pytest.mark.parametrize(
893893
"v",
894894
[
895+
# No splice context
896+
"#?@(:clj [1 2 3] :lpy [4 5 6] :default [7 8 9])",
895897
# Invalid splice collection
896898
"(#?@(:clj (1 2) :lpy (3 4)))",
897899
"(#?@(:clj #{1 2} :lpy #{3 4}))",

0 commit comments

Comments
 (0)