Skip to content

Commit 31405ef

Browse files
authored
Add support for creating reader conditionals (#1126)
Fixes #1125
1 parent 2fe510b commit 31405ef

File tree

9 files changed

+103
-47
lines changed

9 files changed

+103
-47
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Added
9+
* Added support for constructing a data representation of reader conditionals (#1125)
10+
811
### Fixed
912
* Fix a bug where `basilisp test` command fails due to an invalid `argparse` configuration (#1119)
1013
* Fix a bug where `basilisp.walk/walk` (and any functions that depend on it) did not preserve collection metadata (#1123)

docs/reader.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,7 @@ In nearly all cases, this will be the return value from a macro function, which
537537

538538
:ref:`macros`
539539

540-
.. _reader_conditions:
540+
.. _reader_conditionals:
541541

542542
Reader Conditionals
543543
-------------------
@@ -572,6 +572,10 @@ Splicing reader conditionals may only appear within other collection literal for
572572
basilisp.user=> #?@(:lpy [1 2 3])
573573
basilisp.lang.reader.SyntaxError: Unexpected reader conditional
574574

575+
.. seealso::
576+
577+
:lpy:fn:`reader-conditional`, :lpy:fn:`reader-conditional?`
578+
575579
.. _python_version_reader_features:
576580

577581
Python Version Reader Features

src/basilisp/core.lpy

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -547,14 +547,25 @@
547547
v
548548
(.-name v)))
549549

550+
(defn ^:inline reader-conditional
551+
"Construct a data representation of a :ref:`reader conditional <reader_conditionals>`.
552+
553+
The form must contain balanced key-value pairs."
554+
[form is-splicing?]
555+
(basilisp.lang.reader/ReaderConditional form is-splicing?))
556+
557+
(defn ^:inline reader-conditional?
558+
"Return true if the value is the data representation of a :ref:`reader conditional <reader_conditionals>`."
559+
[o]
560+
(instance? basilisp.lang.reader/ReaderConditional o))
561+
550562
(defn ^:inline tagged-literal
551-
"Construct a data representation of a tagged literal from a
552-
tag symbol and a form."
563+
"Construct a data representation of a tagged literal from a tag symbol and a form."
553564
[tag form]
554565
(basilisp.lang.tagged/tagged-literal tag form))
555566

556567
(defn ^:inline tagged-literal?
557-
"Return true if the value is the data representation of a tagged literal"
568+
"Return true if the value is the data representation of a tagged literal."
558569
[o]
559570
(instance? basilisp.lang.tagged/TaggedLiteral o))
560571

src/basilisp/lang/reader.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@
7272
fn_macro_args = re.compile("(%)(&|[0-9])?")
7373
unicode_char = re.compile(r"u(\w+)")
7474

75-
DataReaders = Optional[lmap.PersistentMap]
75+
DataReaderFn = Callable[[Any], Any]
76+
DataReaders = lmap.PersistentMap[sym.Symbol, DataReaderFn]
7677
GenSymEnvironment = MutableMapping[str, sym.Symbol]
7778
Resolver = Callable[[sym.Symbol], sym.Symbol]
7879
LispReaderFn = Callable[["ReaderContext"], LispForm]
@@ -348,7 +349,7 @@ def _raise_unknown_tag(s: sym.Symbol, v: LispReaderForm) -> NoReturn:
348349

349350

350351
class ReaderContext:
351-
_DATA_READERS = lmap.map(
352+
_DATA_READERS: DataReaders = lmap.map(
352353
{
353354
sym.symbol("inst"): _inst_from_str,
354355
sym.symbol("py"): _py_from_lisp,
@@ -394,7 +395,7 @@ def __init__( # pylint: disable=too-many-arguments
394395
self._eof = eof
395396

396397
@property
397-
def data_readers(self) -> lmap.PersistentMap:
398+
def data_readers(self) -> DataReaders:
398399
return self._data_readers
399400

400401
@property
@@ -487,6 +488,13 @@ def __init__(
487488
self._feature_vec = self._compile_feature_vec(form)
488489
self._is_splicing = is_splicing
489490

491+
def __eq__(self, other):
492+
if self is other:
493+
return True
494+
if not isinstance(other, ReaderConditional):
495+
return NotImplemented
496+
return self._form == other._form and self._is_splicing == other._is_splicing
497+
490498
@staticmethod
491499
def _compile_feature_vec(form: IPersistentList[tuple[kw.Keyword, ReaderForm]]):
492500
found_features: set[kw.Keyword] = set()

src/basilisp/lang/runtime.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2434,7 +2434,7 @@ def in_ns(s: sym.Symbol):
24342434
{
24352435
_DOC_META_KEY: (
24362436
"The set of all currently supported "
2437-
":ref:`reader features <reader_conditions>`."
2437+
":ref:`reader features <reader_conditionals>`."
24382438
)
24392439
}
24402440
),

src/basilisp/lang/tagged.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@
1515
_FORM_KW = keyword("form")
1616

1717

18-
class TaggedLiteral(
19-
ILispObject,
20-
ILookup[K, T],
21-
):
18+
class TaggedLiteral(ILispObject, ILookup[K, T]):
2219
"""Basilisp TaggedLiteral. https://clojure.org/reference/reader#tagged_literals"""
2320

2421
__slots__ = ("_tag", "_form", "_hash")

tests/basilisp/test_core_fns.lpy

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -461,8 +461,6 @@
461461
(is (= 1 (rand-nth [1])))
462462
(is (#{1 2} (rand-nth [1 2]))))
463463

464-
465-
466464
(deftest subvec-test
467465
(is (= [] (subvec [] 0)))
468466
(is (thrown? python/IndexError (subvec [] 3)))
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
(ns tests.basilisp.test-core-reader-utility-fns
2+
(:require
3+
[basilisp.test :refer [deftest is testing]]))
4+
5+
(deftest reader-conditional-test
6+
(let [form '()
7+
is-splicing? true
8+
reader-cond (reader-conditional form is-splicing?)]
9+
(testing "equality"
10+
(is (= reader-cond reader-cond))
11+
(is (= reader-cond (reader-conditional form is-splicing?)))
12+
(is (not= reader-cond (reader-conditional '(:clj [] :lpy [true]) is-splicing?)))
13+
(is (not= reader-cond (reader-conditional form false))))
14+
15+
(testing "accessors"
16+
(is (= form (:form reader-cond)))
17+
(is (= is-splicing? (:splicing? reader-cond)))
18+
(is (nil? (:key reader-cond)))
19+
(is (= ::default (:key reader-cond ::default))))
20+
21+
(testing "predicate"
22+
(is (true? (reader-conditional? reader-cond)))
23+
(is (false? (reader-conditional? nil)))
24+
(is (false? (reader-conditional? 0)))
25+
(is (false? (reader-conditional? ::foo))))
26+
27+
(testing "printing"
28+
(is (= "#?@()" (pr-str reader-cond)))
29+
(is (= "#?()" (pr-str (reader-conditional '() false))))
30+
(is (= "#?@(:clj [] :lpy [true])" (pr-str (reader-conditional '(:clj [] :lpy [true]) true)))))
31+
32+
(testing "validation"
33+
(is (thrown? basilisp.lang.reader/SyntaxError
34+
(reader-conditional '(:clj) true)))
35+
(is (thrown? basilisp.lang.reader/SyntaxError
36+
(reader-conditional '(:clj [] :lpy) true)))
37+
(is (thrown? basilisp.lang.reader/SyntaxError
38+
(reader-conditional '('lpy [] :clj [true]) true))))))
39+
40+
(deftest tagged-literal-test
41+
(let [tag 'tag
42+
form 1
43+
tagged (tagged-literal tag form)]
44+
(testing "equality"
45+
(is (= tagged tagged))
46+
(is (= tagged (tagged-literal tag form)))
47+
(is (not= tagged (tagged-literal 'foo form)))
48+
(is (not= tagged (tagged-literal tag 2))))
49+
50+
(testing "accessors"
51+
(is (= tag (:tag tagged)))
52+
(is (= form (:form tagged)))
53+
(is (nil? (:key tagged)))
54+
(is (= ::default (:key tagged ::default))))
55+
56+
(testing "predicate"
57+
(is (true? (tagged-literal? tagged)))
58+
(is (false? (tagged-literal? nil)))
59+
(is (false? (tagged-literal? 0)))
60+
(is (false? (tagged-literal? ::foo))))
61+
62+
(testing "printing"
63+
(is (= "#tag 1" (pr-str tagged)))
64+
(is (= "#js []" (pr-str (tagged-literal 'js []))))
65+
(is (= "#js {}" (pr-str (tagged-literal 'js {})))))
66+
67+
(testing "validation"
68+
(is (thrown? TypeError (tagged-literal 1 1))))))

tests/basilisp/test_tagged.lpy

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)