Skip to content

Commit 056c125

Browse files
committed
Support async for and async with constructs using macros
1 parent 7d1d925 commit 056c125

File tree

2 files changed

+52
-0
lines changed

2 files changed

+52
-0
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 `afor` and `awith` macros to support async Python interop (#1179, #1181)
10+
811
### Changed
912
* Single arity functions can be tagged with `^:allow-unsafe-names` to preserve their parameter names (#1212)
1013

src/basilisp/core.lpy

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4085,6 +4085,55 @@
40854085
(finally
40864086
(println (* 1000 (- (perf-counter) start#)) "msecs")))))
40874087

4088+
;;;;;;;;;;;;;;;;;;
4089+
;; Async Macros ;;
4090+
;;;;;;;;;;;;;;;;;;
4091+
4092+
(defmacro afor
4093+
"Repeatedly execute ``body`` while the binding name is repeatedly rebound to
4094+
successive values from the asynchronous iterable."
4095+
[binding & body]
4096+
(if (operator/ne 2 (count binding))
4097+
(throw
4098+
(ex-info "bindings take the form [name iter]"
4099+
{:bindings binding}))
4100+
nil)
4101+
(let [bound-name (first binding)
4102+
iter (second binding)]
4103+
`(let [it# (. ~iter ~'__aiter__)]
4104+
(loop [is-running?# true]
4105+
(when is-running?#
4106+
(try
4107+
(let [~bound-name (await (. it# ~'__anext__))]
4108+
~@body)
4109+
(catch python/StopAsyncIteration _
4110+
(recur false))))))))
4111+
4112+
(defmacro awith
4113+
"Evaluate ``body`` within a ``try`` / ``except`` expression, binding the named
4114+
expressions as per Python's async context manager protocol spec (Python's
4115+
``async with`` blocks)."
4116+
[bindings & body]
4117+
(let [binding (first bindings)
4118+
expr (second bindings)]
4119+
`(let [obj# ~expr
4120+
~binding (await (. obj# ~'__aenter__))
4121+
hit-except# (volatile! false)]
4122+
(try
4123+
(let [res# ~@(if (nthnext bindings 2)
4124+
[(concat
4125+
(list 'awith (vec (nthrest bindings 2)))
4126+
body)]
4127+
(list (concat '(do) body)))]
4128+
res#)
4129+
(catch python/Exception e#
4130+
(vreset! hit-except# true)
4131+
(when-not (await (. obj# (~'__aexit__ (python/type e#) e# (.- e# ~'__traceback__))))
4132+
(throw e#)))
4133+
(finally
4134+
(when-not @hit-except#
4135+
(await (. obj# (~'__aexit__ nil nil nil)))))))))
4136+
40884137
;;;;;;;;;;;;;;;;;;;;;;
40894138
;; Threading Macros ;;
40904139
;;;;;;;;;;;;;;;;;;;;;;

0 commit comments

Comments
 (0)