Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
* Added support for constructing a data representation of reader conditionals (#1125)

### Fixed
* Fix a bug where `basilisp test` command fails due to an invalid `argparse` configuration (#1119)
* Fix a bug where `basilisp.walk/walk` (and any functions that depend on it) did not preserve collection metadata (#1123)
Expand Down
6 changes: 5 additions & 1 deletion docs/reader.rst
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ In nearly all cases, this will be the return value from a macro function, which

:ref:`macros`

.. _reader_conditions:
.. _reader_conditionals:

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

.. seealso::

:lpy:fn:`reader-conditional`, :lpy:fn:`reader-conditional?`

.. _python_version_reader_features:

Python Version Reader Features
Expand Down
17 changes: 14 additions & 3 deletions src/basilisp/core.lpy
Original file line number Diff line number Diff line change
Expand Up @@ -547,14 +547,25 @@
v
(.-name v)))

(defn ^:inline reader-conditional
"Construct a data representation of a :ref:`reader conditional <reader_conditionals>`.

The form must contain balanced key-value pairs."
[form is-splicing?]
(basilisp.lang.reader/ReaderConditional form is-splicing?))

(defn ^:inline reader-conditional?
"Return true if the value is the data representation of a :ref:`reader conditional <reader_conditionals>`."
[o]
(instance? basilisp.lang.reader/ReaderConditional o))

(defn ^:inline tagged-literal
"Construct a data representation of a tagged literal from a
tag symbol and a form."
"Construct a data representation of a tagged literal from a tag symbol and a form."
[tag form]
(basilisp.lang.tagged/tagged-literal tag form))

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

Expand Down
14 changes: 11 additions & 3 deletions src/basilisp/lang/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@
fn_macro_args = re.compile("(%)(&|[0-9])?")
unicode_char = re.compile(r"u(\w+)")

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


class ReaderContext:
_DATA_READERS = lmap.map(
_DATA_READERS: DataReaders = lmap.map(
{
sym.symbol("inst"): _inst_from_str,
sym.symbol("py"): _py_from_lisp,
Expand Down Expand Up @@ -394,7 +395,7 @@ def __init__( # pylint: disable=too-many-arguments
self._eof = eof

@property
def data_readers(self) -> lmap.PersistentMap:
def data_readers(self) -> DataReaders:
return self._data_readers

@property
Expand Down Expand Up @@ -487,6 +488,13 @@ def __init__(
self._feature_vec = self._compile_feature_vec(form)
self._is_splicing = is_splicing

def __eq__(self, other):
if self is other:
return True
if not isinstance(other, ReaderConditional):
return NotImplemented
return self._form == other._form and self._is_splicing == other._is_splicing

@staticmethod
def _compile_feature_vec(form: IPersistentList[tuple[kw.Keyword, ReaderForm]]):
found_features: set[kw.Keyword] = set()
Expand Down
2 changes: 1 addition & 1 deletion src/basilisp/lang/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -2434,7 +2434,7 @@ def in_ns(s: sym.Symbol):
{
_DOC_META_KEY: (
"The set of all currently supported "
":ref:`reader features <reader_conditions>`."
":ref:`reader features <reader_conditionals>`."
)
}
),
Expand Down
5 changes: 1 addition & 4 deletions src/basilisp/lang/tagged.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@
_FORM_KW = keyword("form")


class TaggedLiteral(
ILispObject,
ILookup[K, T],
):
class TaggedLiteral(ILispObject, ILookup[K, T]):
"""Basilisp TaggedLiteral. https://clojure.org/reference/reader#tagged_literals"""

__slots__ = ("_tag", "_form", "_hash")
Expand Down
2 changes: 0 additions & 2 deletions tests/basilisp/test_core_fns.lpy
Original file line number Diff line number Diff line change
Expand Up @@ -461,8 +461,6 @@
(is (= 1 (rand-nth [1])))
(is (#{1 2} (rand-nth [1 2]))))



(deftest subvec-test
(is (= [] (subvec [] 0)))
(is (thrown? python/IndexError (subvec [] 3)))
Expand Down
68 changes: 68 additions & 0 deletions tests/basilisp/test_core_reader_utility_fns.lpy
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
(ns tests.basilisp.test-core-reader-utility-fns
(:require
[basilisp.test :refer [deftest is testing]]))

(deftest reader-conditional-test
(let [form '()
is-splicing? true
reader-cond (reader-conditional form is-splicing?)]
(testing "equality"
(is (= reader-cond reader-cond))
(is (= reader-cond (reader-conditional form is-splicing?)))
(is (not= reader-cond (reader-conditional '(:clj [] :lpy [true]) is-splicing?)))
(is (not= reader-cond (reader-conditional form false))))

(testing "accessors"
(is (= form (:form reader-cond)))
(is (= is-splicing? (:splicing? reader-cond)))
(is (nil? (:key reader-cond)))
(is (= ::default (:key reader-cond ::default))))

(testing "predicate"
(is (true? (reader-conditional? reader-cond)))
(is (false? (reader-conditional? nil)))
(is (false? (reader-conditional? 0)))
(is (false? (reader-conditional? ::foo))))

(testing "printing"
(is (= "#?@()" (pr-str reader-cond)))
(is (= "#?()" (pr-str (reader-conditional '() false))))
(is (= "#?@(:clj [] :lpy [true])" (pr-str (reader-conditional '(:clj [] :lpy [true]) true)))))

(testing "validation"
(is (thrown? basilisp.lang.reader/SyntaxError
(reader-conditional '(:clj) true)))
(is (thrown? basilisp.lang.reader/SyntaxError
(reader-conditional '(:clj [] :lpy) true)))
(is (thrown? basilisp.lang.reader/SyntaxError
(reader-conditional '('lpy [] :clj [true]) true))))))

(deftest tagged-literal-test
(let [tag 'tag
form 1
tagged (tagged-literal tag form)]
(testing "equality"
(is (= tagged tagged))
(is (= tagged (tagged-literal tag form)))
(is (not= tagged (tagged-literal 'foo form)))
(is (not= tagged (tagged-literal tag 2))))

(testing "accessors"
(is (= tag (:tag tagged)))
(is (= form (:form tagged)))
(is (nil? (:key tagged)))
(is (= ::default (:key tagged ::default))))

(testing "predicate"
(is (true? (tagged-literal? tagged)))
(is (false? (tagged-literal? nil)))
(is (false? (tagged-literal? 0)))
(is (false? (tagged-literal? ::foo))))

(testing "printing"
(is (= "#tag 1" (pr-str tagged)))
(is (= "#js []" (pr-str (tagged-literal 'js []))))
(is (= "#js {}" (pr-str (tagged-literal 'js {})))))

(testing "validation"
(is (thrown? TypeError (tagged-literal 1 1))))))
33 changes: 0 additions & 33 deletions tests/basilisp/test_tagged.lpy

This file was deleted.