diff --git a/CHANGELOG.md b/CHANGELOG.md index a03ccd9b..ac4bc393 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added support for proxies (#425) * Added a `:slots` meta flag for `deftype` to disable creation of `__slots__` on created types (#1241) * Added support for f-strings (#922) + * Added the `aslice` macro to facilitate the use of Python style `array[start:stop:step]` slicing in Basilisp (#1248) ### Changed * Removed implicit support for single-use iterables in sequences, and introduced `iterator-seq` to expliciltly handle them (#1192) diff --git a/docs/pyinterop.rst b/docs/pyinterop.rst index dafc47ef..c291251d 100644 --- a/docs/pyinterop.rst +++ b/docs/pyinterop.rst @@ -288,7 +288,7 @@ Type hints may be applied to :lpy:form:`def` names, function arguments and retur Python Slicing -------------- -Python slicing lets you extract parts of a sequence (like a list or string) using the syntax ``sequence[start:end:step]``: +Python slicing lets you extract parts of a sequence (like a list or string) using the syntax ``sequence[start:stop:step]``: .. code-block:: python @@ -296,7 +296,22 @@ Python slicing lets you extract parts of a sequence (like a list or string) usin coll[1:5:2] # => [-2, 0] -Basilisp does not support slicing syntax directly, but it can be achieved using the :external:py:obj:`slice` operator combined with the :lpy:fn:`basilisp.core/aget` function: +Basilisp provides the :lpy:fn:`basilisp.core/aslice` macro to facilitate this syntax: + +.. code-block:: clojure + + (def coll #py [-3 -2 -1 0 1 2 3]) + (aslice coll 3) + ;; => #py [-3 -2 -1] + + (aslice coll nil -3) + ;; => #py [-3 -2 -1 0] + + (aslice coll 1 5 2) + ;; => #py [-2 0] + + +This macro is just a wrapper around Python's :external:py:obj:`slice` operator combined with the :lpy:fn:`basilisp.core/aget` function: .. code-block:: clojure diff --git a/src/basilisp/core.lpy b/src/basilisp/core.lpy index e119da98..1df1cd89 100644 --- a/src/basilisp/core.lpy +++ b/src/basilisp/core.lpy @@ -1949,7 +1949,7 @@ (python/len array)) (defmacro amap - "Map ``expr`` over the Python list `array`, returning a new Python list with the + "Map ``expr`` over the Python list ``array``, returning a new Python list with the result. This macro initially binds the symbol named by ``ret`` to a clone of ``array``\\. On @@ -1977,6 +1977,17 @@ (recur ~expr (inc ~idx)) ~ret)))) +(defmacro aslice + "Get a portion of the Python list ``array`` using a + Python :external:py:obj:`slice` instance with a single ``stop`` + value, or a combination of ``start`` and ``stop`` and optional + ``step`` values. + + Equivalent to Python's ``array[start:stop:step]`` extended indexing + syntax. Use ``nil`` when omitting any of these fields." + [array & stop-or-start-stop-step] + `(aget ~array (python/slice ~@stop-or-start-stop-step) )) + (defn ^:inline booleans "Dummy cast to a Python list of booleans. diff --git a/tests/basilisp/test_core_macros.lpy b/tests/basilisp/test_core_macros.lpy index de26274f..2cd962df 100644 --- a/tests/basilisp/test_core_macros.lpy +++ b/tests/basilisp/test_core_macros.lpy @@ -385,6 +385,19 @@ (let [l #py [1 2 3]] (is (= 6 (areduce l idx ret 0 (+ ret (aget l idx))))))) +(deftest aslice-test + (let [l #py [-3 -2 -1 0 1 2 3]] + (is (= #py [-3 -2 -1 0] (aslice l 4))) + (is (= #py [-3 -2 -1 0 1 2 3] (aslice l nil))) + (is (= #py [-3 -2 -1 0 1] (aslice l -2))) + (is (= #py [-1 0] (aslice l 2 4))) + (is (= #py [-3 -2 -1 0] (aslice l nil 4))) + (is (= #py [1 2 3] (aslice l 4 nil))) + (is (= #py [1 2] (aslice l 4 -1))) + (is (= #py [-2 0 2] (aslice l 1 6 2))) + (is (= #py [3 1 -1] (aslice l 6 1 -2))) + (is (= #py [3 1 -1 -3] (aslice l nil nil -2))))) + (defmacro case-with-seq [] `(case :val