+++ /dev/null
-# aio: async/await for Emacs Lisp
-
-`aio` is to Emacs Lisp as [`asyncio`][asyncio] is to Python. This
-package builds upon Emacs 25 generators to provide functions that
-pause while they wait on asynchronous events. They do not block any
-thread while paused.
-
-Introduction: [An Async / Await Library for Emacs Lisp][post]
-
-Installation is [available through MELPA][melpa]. Since it uses the
-`record` built-in, it requires Emacs 26 or later.
-
-## Usage
-
-An async function is defined using `aio-defun` or `aio-lambda`. The
-body of such functions can use `aio-await` to pause the function and
-wait on a given promise. The function continues with the promise's
-resolved value when it's ready. The package provides a number of
-functions that return promises, and every async function returns a
-promise representing its future return value.
-
-For example:
-
-```el
-(aio-defun foo (url)
- (aio-await (aio-sleep 3))
- (message "Done sleeping. Now fetching %s" url)
- (let* ((result (aio-await (aio-url-retrieve url)))
- (contents (with-current-buffer (cdr result)
- (prog1 (buffer-string)
- (kill-buffer)))))
- (message "Result: %s" contents)))
-```
-
-If an uncaught signal terminates an asynchronous function, that signal
-is captured by its return value promise and propagated into any
-function that awaits on that function.
-
-```el
-(aio-defun divide (a b)
- (aio-await (aio-sleep 1))
- (/ a b))
-
-(aio-defun divide-safe (a b)
- (condition-case error
- (aio-await (divide a b))
- (arith-error :arith-error)))
-
-(aio-wait-for (divide-safe 1.0 2.0))
-;; => 0.5
-
-(aio-wait-for (divide-safe 0 0))
-;; => :arith-error
-```
-
-To convert a callback-based function into an awaitable, async-friendly
-function, create a new promise object with `aio-promise`, then
-`aio-resolve` that promise in the callback. The helper function,
-`aio-make-callback`, makes this easy.
-
-## Utility macros and functions
-
-```el
-(aio-wait-for promise)
-;; Synchronously wait for PROMISE, blocking the current thread.
-
-(aio-cancel promise)
-;; Attempt to cancel PROMISE, returning non-nil if successful.
-
-(aio-with-promise promise &rest body) [macro]
-;; Evaluate BODY and resolve PROMISE with the result.
-
-(aio-with-async &rest body) [macro]
-;; Evaluate BODY asynchronously as if it was inside `aio-lambda'.
-
-(aio-make-callback &key tag once)
-;; Return a new callback function and its first promise.
-
-(aio-chain expr) [macro]
-;; `aio-await' on EXPR and replace place EXPR with the next promise.
-```
-
-The `aio-make-callback` function is especially useful for callbacks
-that are invoked repeatedly, such as process filters and sentinels.
-The `aio-chain` macro works in conjunction.
-
-## Awaitable functions
-
-Here are some useful promise-returning — i.e. awaitable — functions
-defined by this package.
-
-```el
-(aio-sleep seconds &optional result)
-;; Return a promise that is resolved after SECONDS with RESULT.
-
-(aio-idle seconds &optional result)
-;; Return a promise that is resolved after idle SECONDS with RESULT.
-
-(aio-url-retrieve url &optional silent inhibit-cookies)
-;; Wraps `url-retrieve' in a promise.
-
-(aio-all promises)
-;; Return a promise that resolves when all PROMISES are resolved."
-```
-
-## Select API
-
-This package includes a select()-like, level-triggered API for waiting
-on multiple promises at once. Create a "select" object, add promises
-to it, and await on it. Resolved and returned promises are
-automatically removed, and the "select" object can be reused.
-
-```el
-(aio-make-select &optional promises)
-;; Create a new `aio-select' object for waiting on multiple promises.
-
-(aio-select-add select promise)
-;; Add PROMISE to the set of promises in SELECT.
-
-(aio-select-remove select promise)
-;; Remove PROMISE form the set of promises in SELECT.
-
-(aio-select-promises select)
-;; Return a list of promises in SELECT.
-
-(aio-select select)
-;; Return a promise that resolves when any promise in SELECT resolves.
-```
-
-For example, here's an implementation of sleep sort:
-
-```el
-(aio-defun sleep-sort (values)
- (let* ((promises (mapcar (lambda (v) (aio-sleep v v)) values))
- (select (aio-make-select promises)))
- (cl-loop repeat (length promises)
- for next = (aio-await (aio-select select))
- collect (aio-await next))))
-```
-
-## Semaphore API
-
-Semaphores work just as they would as a thread synchronization
-primitive. There's an internal counter that cannot drop below zero,
-and `aio-sem-wait` is an awaitable function that may block the
-asynchronous function until another asynchronous function calls
-`aio-sem-post`. Blocked functions wait in a FIFO queue and are awoken
-in the same order that they awaited.
-
-```el
-(aio-sem init)
-;; Create a new semaphore with initial value INIT.
-
-(aio-sem-post sem)
-;; Increment the value of SEM.
-
-(aio-sem-wait sem)
-;; Decrement the value of SEM.
-```
-
-This can be used to create a work queue. For example, here's a
-configurable download queue for `url-retrieve`:
-
-```el
-(defun fetch (url-list max-parallel callback)
- (let ((sem (aio-sem max-parallel)))
- (dolist (url url-list)
- (aio-with-async
- (aio-await (aio-sem-wait sem))
- (cl-destructuring-bind (status . buffer)
- (aio-await (aio-url-retrieve url))
- (aio-sem-post sem)
- (funcall callback
- (with-current-buffer buffer
- (prog1 (buffer-string)
- (kill-buffer)))))))))
-```
-
-
-[asyncio]: https://docs.python.org/3/library/asyncio.html
-[melpa]: https://melpa.org/#/aio
-[post]: https://nullprogram.com/blog/2019/03/10/
+++ /dev/null
-This is free and unencumbered software released into the public domain.
-
-Anyone is free to copy, modify, publish, use, compile, sell, or
-distribute this software, either in source code form or as a compiled
-binary, for any purpose, commercial or non-commercial, and by any
-means.
-
-In jurisdictions that recognize copyright laws, the author or authors
-of this software dedicate any and all copyright interest in the
-software to the public domain. We make this dedication for the benefit
-of the public at large and to the detriment of our heirs and
-successors. We intend this dedication to be an overt act of
-relinquishment in perpetuity of all present and future rights to this
-software under copyright law.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
-ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-OTHER DEALINGS IN THE SOFTWARE.
-
-For more information, please refer to <http://unlicense.org/>
+++ /dev/null
-;;; aio-autoloads.el --- automatically extracted autoloads (do not edit) -*- lexical-binding: t -*-
-;; Generated by the `loaddefs-generate' function.
-
-;; This file is part of GNU Emacs.
-
-;;; Code:
-
-(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path)))
-
-
-\f
-;;; Generated autoloads from aio.el
-
-(register-definition-prefixes "aio" '("aio-"))
-\f
-;;; End of scraped data
-
-(provide 'aio-autoloads)
-
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; no-native-compile: t
-;; coding: utf-8-emacs-unix
-;; End:
-
-;;; aio-autoloads.el ends here
+++ /dev/null
-;; -*- no-byte-compile: t; lexical-binding: nil -*-
-(define-package "aio" "20251117.644"
- "Async/await for Emacs Lisp."
- '((emacs "26.1"))
- :url "https://github.com/skeeto/emacs-aio"
- :commit "58157e51e7eb7a4b954894ee4182564c507a2f01"
- :revdesc "58157e51e7eb"
- :authors '(("Christopher Wellons" . "wellonsNOSPAMnullprogram.com"))
- :maintainers '(("Christopher Wellons" . "wellonsNOSPAMnullprogram.com")))
+++ /dev/null
-;;; aio.el --- async/await for Emacs Lisp -*- lexical-binding: t; -*-
-
-;; This is free and unencumbered software released into the public domain.
-
-;; Author: Christopher Wellons <wellonsNOSPAMnullprogram.com>
-;; URL: https://github.com/skeeto/emacs-aio
-;; Package-Version: 20251117.644
-;; Package-Revision: 58157e51e7eb
-;; Package-Requires: ((emacs "26.1"))
-
-;;; Commentary:
-
-;; `aio` is to Emacs Lisp as [`asyncio`][asyncio] is to Python. This
-;; package builds upon Emacs 25 generators to provide functions that
-;; pause while they wait on asynchronous events. They do not block any
-;; thread while paused.
-
-;; The main components of this package are `aio-defun' / `aio-lambda'
-;; to define async function, and `aio-await' to pause these functions
-;; while they wait on asynchronous events. When an asynchronous
-;; function is paused, the main thread is not blocked. It is no more
-;; or less powerful than callbacks, but is nicer to use.
-
-;; This is implementation is based on Emacs 25 generators, and
-;; asynchronous functions are actually iterators in disguise, operated
-;; as stackless, asymmetric coroutines.
-
-;;; Code:
-
-(require 'cl-lib)
-(require 'font-lock)
-(require 'generator)
-(require 'macroexp)
-(require 'rx)
-(require 'iter2 nil t)
-
-;; Register new error types
-(define-error 'aio-cancel "Promise was canceled")
-(define-error 'aio-timeout "Timeout was reached")
-
-(cl-defstruct (aio-promise (:constructor aio-promise))
- "A promise object."
- result
- callbacks)
-
-(defsubst aio-result (promise)
- "Return the result of PROMISE, or nil if it is unresolved.
-
-Promise results are wrapped in a function. The result must be
-called (e.g. `funcall') in order to retrieve the value."
- (aio-promise-result promise))
-
-(defun aio-listen (promise callback)
- "Add CALLBACK to PROMISE.
-
-If the promise has already been resolved, the callback will be
-scheduled for the next event loop turn."
- (let ((result (aio-result promise)))
- (if result
- (run-at-time 0 nil callback result)
- (push callback (aio-promise-callbacks promise)))))
-
-(defun aio-resolve (promise value-function)
- "Resolve this PROMISE with VALUE-FUNCTION.
-
-A promise can only be resolved once, and any further calls to
-`aio-resolve' are silently ignored. The VALUE-FUNCTION must be a
-function that takes no arguments and either returns the result
-value or rethrows a signal."
- (cl-check-type value-function function)
- (unless (aio-result promise)
- (let ((callbacks (nreverse (aio-promise-callbacks promise))))
- (setf (aio-promise-result promise) value-function
- (aio-promise-callbacks promise) ())
- (dolist (callback callbacks)
- (run-at-time 0 nil callback value-function)))))
-
-(defun aio--step (iter promise yield-result)
- "Advance ITER to the next promise.
-
-PROMISE is the return promise of the iterator, which was returned
-by the originating async function. YIELD-RESULT is the value
-function result directly from the previously yielded promise."
- (condition-case _
- (cl-loop for result = (iter-next iter yield-result)
- then (iter-next iter (lambda () result))
- until (aio-promise-p result)
- finally (aio-listen result
- (lambda (value)
- (aio--step iter promise value))))
- (iter-end-of-sequence)))
-
-(defmacro aio-with-promise (promise &rest body)
- "Evaluate BODY and resolve PROMISE with the result.
-
-If the body signals an error, this error will be stored in the
-promise and rethrown in the promise's listeners."
- (declare (indent defun)
- (debug (form body)))
- (cl-assert (eq lexical-binding t))
- `(aio-resolve ,promise
- (condition-case error
- (let ((result ,(macroexp-progn body)))
- (lambda () result))
- (error (lambda ()
- (signal (car error) (cdr error)))))))
-
-(defmacro aio-await (expr)
- "If EXPR evaluates to a promise, pause until the promise is resolved.
-
-Pausing an async function does not block Emacs' main thread. If
-EXPR doesn't evaluate to a promise, the value is returned
-immediately and the function is not paused. Since async functions
-return promises, async functions can await directly on other
-async functions using this macro.
-
-This macro can only be used inside an async function, either
-`aio-lambda' or `aio-defun'."
- `(funcall (iter-yield ,expr)))
-
-(defmacro aio-lambda (arglist &rest body)
- "Like `lambda', but defines an async function.
-
-The body of this function may use `aio-await' to wait on
-promises. When an async function is called, it immediately
-returns a promise that will resolve to the function's return
-value, or any uncaught error signal.
-
-See Info node ‘(elisp)Lambda Components’ for a description of
-ARGLIST and BODY.
-
-\(fn ARGLIST &optional DOCSTRING INTERACTIVE &rest BODY)"
- (declare (indent defun)
- (doc-string 3)
- (debug (&define lambda-list lambda-doc
- [&optional ("interactive" interactive)]
- &rest sexp)))
- (let ((args (make-symbol "args"))
- (promise (make-symbol "promise"))
- (split-body (macroexp-parse-body body)))
- (if (fboundp 'iter2-lambda)
- `(lambda (&rest ,args)
- ,@(car split-body)
- (let* ((,promise (aio-promise))
- (iter (apply (iter2-lambda ,arglist
- (aio-with-promise ,promise
- ,@(cdr split-body)))
- ,args)))
- (prog1 ,promise
- (aio--step iter ,promise nil))))
- `(lambda (&rest ,args)
- ,@(car split-body)
- (let* ((,promise (aio-promise))
- (iter (apply (iter-lambda ,arglist
- (aio-with-promise ,promise
- ,@(cdr split-body)))
- ,args)))
- (prog1 ,promise
- (aio--step iter ,promise nil)))))))
-
-(defmacro aio-defun (name arglist &rest body)
- "Like `aio-lambda' but gives the function a NAME like `defun'.
-
-See Info node ‘(elisp)Defining Functions’ for a description of
-ARGLIST and BODY."
- (declare (indent defun)
- (doc-string 3)
- (debug (&define name lambda-list &rest sexp)))
- (or name (error "Cannot define `%s' as a function" name))
- (let* ((split-body (macroexp-parse-body body))
- (declarations (car split-body))
- (body (cdr split-body))
- (docstring (and (stringp (car declarations)) (pop declarations)))
- (declares (and (eq (car-safe (car declarations)) #'declare)
- (cdr (pop declarations)))))
- ;; Other declarations (e.g., ‘interactive’ forms) are left as-is.
- `(progn
- (defalias ',name (aio-lambda ,arglist ,docstring ,@declarations ,@body))
- (function-put ',name 'aio-defun-p t)
- ,@(mapcar
- (lambda (declare)
- (let* ((prop (car declare))
- (args (cdr declare))
- (fun (assq prop defun-declarations-alist)))
- (or fun (error "Unknown ‘defun’ declaration property %s" prop))
- (apply (cadr fun) name arglist args)))
- declares)
- (set-advertised-calling-convention (indirect-function ',name) ',arglist nil)
- ',name)))
-
-(defun aio-wait-for (promise)
- "Synchronously wait for PROMISE, blocking the current thread."
- (while (null (aio-result promise))
- (accept-process-output))
- (funcall (aio-result promise)))
-
-(defun aio-cancel (promise &optional reason)
- "Attempt to cancel PROMISE, returning non-nil if successful.
-
-All awaiters will receive an `aio-cancel' signal. The actual
-underlying asynchronous operation will not actually be canceled.
-Optional argument REASON is used as error data for the signal."
- (unless (aio-result promise)
- (aio-resolve promise (lambda () (signal 'aio-cancel reason)))))
-
-(defmacro aio-with-async (&rest body)
- "Evaluate BODY asynchronously as if it was inside `aio-lambda'.
-
-Since BODY is evalued inside an asynchronous lambda, `aio-await'
-is available here. This macro evaluates to a promise for BODY's
-eventual result.
-
-Beware: Dynamic bindings that are lexically outside
-‘aio-with-async’ blocks have no effect. For example,
-
- (defvar dynamic-var nil)
- (defun my-func ()
- (let ((dynamic-var 123))
- (aio-with-async dynamic-var)))
- (let ((dynamic-var 456))
- (aio-wait-for (my-func)))
- ⇒ 456
-
-Other global state such as the current buffer behaves likewise."
- (declare (indent 0)
- (debug (&rest sexp)))
- `(let ((promise (funcall (aio-lambda ()
- (aio-await (aio-sleep 0))
- ,@body))))
- (prog1 promise
- ;; The is the main feature: Force the final result to be
- ;; realized so that errors are reported.
- (aio-listen promise #'funcall))))
-
-(defmacro aio-chain (expr)
- "`aio-await' on EXPR and replace place EXPR with the next promise.
-
-EXPR must be setf-able. Returns (cdr result). This macro is
-intended to be used with `aio-make-callback' in order to follow
-a chain of promise-yielding promises."
- (let ((result (make-symbol "result")))
- `(let ((,result (aio-await ,expr)))
- (setf ,expr (car ,result))
- (cdr ,result))))
-
-;; Useful promise-returning functions:
-
-(defmacro aio-all (promises)
- "Return a promise that resolves when all PROMISES are resolved."
- `(let ((promises ,promises))
- (while-let ((promise (pop promises)))
- (aio-await promise))))
-
-(defun aio-catch (promise)
- "Return a new promise that wraps PROMISE but will never signal.
-
-The promise value is a cons where the car is either :success or
-:error. For :success, the cdr will be the result value. For
-:error, the cdr will be the error data."
- (let ((result (aio-promise)))
- (cl-flet ((callback (value)
- (aio-resolve result
- (lambda ()
- (condition-case error
- (cons :success (funcall value))
- (error (cons :error error)))))))
- (prog1 result
- (aio-listen promise #'callback)))))
-
-(defun aio-sleep (seconds &optional result)
- "Create a promise that is resolved after SECONDS with RESULT.
-
-The result is a value, not a value function, and it will be
-automatically wrapped with a value function (see `aio-resolve')."
- (let ((promise (aio-promise)))
- (prog1 promise
- (run-at-time seconds nil
- #'aio-resolve promise (lambda () result)))))
-
-(defun aio-idle (seconds &optional result)
- "Create a promise that is resolved after idle SECONDS with RESULT.
-
-The result is a value, not a value function, and it will be
-automatically wrapped with a value function (see `aio-resolve')."
- (let ((promise (aio-promise)))
- (prog1 promise
- (run-with-idle-timer seconds nil
- #'aio-resolve promise (lambda () result)))))
-
-(defun aio-timeout (seconds)
- "Create a promise with a timeout error after SECONDS."
- (let ((promise (aio-promise)))
- (prog1 promise
- (run-at-time seconds nil
- #'aio-resolve promise (lambda () (signal 'aio-timeout seconds))))))
-
-(defun aio-url-retrieve (url &optional silent inhibit-cookies)
- "Wraps `url-retrieve' in a promise.
-
-This function will never directly signal an error. Instead any
-errors will be delivered via the returned promise. The promise
-result is a cons of (status . buffer). This buffer is a clone of
-the buffer created by `url-retrieve' and should be killed by the
-caller.
-
-Arguments URL, SILENT, and INHIBIT-COOKIES are passed on to
-`url-retrieve', which see. Also see Info node ‘(url)Retrieving
-URLs’ for details."
- (require 'url)
- (let ((promise (aio-promise)))
- (prog1 promise
- (condition-case error
- (url-retrieve url (lambda (status)
- (let ((value (cons status (clone-buffer))))
- (aio-resolve promise (lambda () value))))
- silent inhibit-cookies)
- (error (aio-resolve promise
- (lambda ()
- (signal (car error) (cdr error)))))))))
-
-(cl-defun aio-make-callback (&key tag once)
- "Return a new callback function and its first promise.
-
-Returns a cons (callback . promise) where callback is function
-suitable for repeated invocation. This makes it useful for
-process filters and sentinels. The promise is the first promise
-to be resolved by the callback.
-
-The promise resolves to:
- (next-promise . callback-args)
-Or when TAG is supplied:
- (next-promise TAG . callback-args)
-Or if ONCE is non-nil:
- callback-args
-
-The callback resolves next-promise on the next invocation. This
-creates a chain of promises representing the sequence of calls.
-Note: To avoid keeping lots of garbage in memory, avoid holding
-onto the first promise (i.e. capturing it in a closure).
-
-The `aio-chain' macro makes it easier to use these promises."
- (let* ((promise (aio-promise))
- (callback
- (if once
- (lambda (&rest args)
- (let ((result (if tag
- (cons tag args)
- args)))
- (aio-resolve promise (lambda () result))))
- (lambda (&rest args)
- (let* ((next-promise (aio-promise))
- (result (if tag
- (cons next-promise (cons tag args))
- (cons next-promise args))))
- (aio-resolve promise (lambda () result))
- (setf promise next-promise))))))
- (cons callback promise)))
-
-;; A simple little queue
-
-(defsubst aio--queue-empty-p (queue)
- "Return non-nil if QUEUE is empty.
-An empty queue is (nil . nil)."
- (null (caar queue)))
-
-(defsubst aio--queue-get (queue)
- "Get the next item from QUEUE, or nil for empty."
- (let ((head (car queue)))
- (cond ((null head)
- nil)
- ((eq head (cdr queue))
- (prog1 (car head)
- (setf (car queue) nil
- (cdr queue) nil)))
- ((prog1 (car head)
- (setf (car queue) (cdr head)))))))
-
-(defsubst aio--queue-put (queue element)
- "Append ELEMENT to QUEUE, returning ELEMENT."
- (let ((new (list element)))
- (prog1 element
- (if (null (car queue))
- (setf (car queue) new
- (cdr queue) new)
- (setf (cdr (cdr queue)) new
- (cdr queue) new)))))
-
-;; An efficient select()-like interface for promises
-(cl-defstruct (aio-select (:constructor aio--make-select))
- "A select() object for waiting on multiple promises."
- ;; Membership table
- (members (make-hash-table :test 'eq))
- ;; "Seen" table (avoid adding multiple callback)
- (seen (make-hash-table :test 'eq :weakness 'key))
- ;; Queue of pending resolved promises
- (queue (cons nil nil))
- ;; Callback to resolve select's own promise
- callback)
-
-(defun aio-make-select (&optional promises)
- "Create a new `aio-select' object for waiting on multiple PROMISES."
- (let ((select (aio--make-select)))
- (prog1 select
- (dolist (promise promises)
- (aio-select-add select promise)))))
-
-(defun aio-select-add (select promise)
- "Add PROMISE to the set of promises in SELECT.
-
-SELECT is created with `aio-make-select'. It is valid to add a
-promise that was previously removed."
- (let ((members (aio-select-members select))
- (seen (aio-select-seen select)))
- (prog1 promise
- (unless (gethash promise seen)
- (setf (gethash promise seen) t
- (gethash promise members) t)
- (aio-listen promise
- (lambda (_)
- (when (gethash promise members)
- (aio--queue-put (aio-select-queue select) promise)
- (remhash promise members)
- (let ((callback (aio-select-callback select)))
- (when callback
- (setf (aio-select-callback select) nil)
- (funcall callback))))))))))
-
-(defun aio-select-remove (select promise)
- "Remove PROMISE form the set of promises in SELECT.
-
-SELECT is created with `aio-make-select'."
- (remhash promise (aio-select-members select)))
-
-(defun aio-select-promises (select)
- "Return a list of promises in SELECT.
-
-SELECT is created with `aio-make-select'."
- (cl-loop for key being the hash-keys of (aio-select-members select)
- collect key))
-
-(defun aio-select (select)
- "Return a promise that resolves when any promise in SELECT resolves.
-
-SELECT is created with `aio-make-select'. This function is
-level-triggered: if a promise in SELECT is already resolved, it
-returns immediately with that promise. Promises returned by
-`aio-select' are automatically removed from SELECT. Use this
-function to repeatedly wait on a set of promises.
-
-Note: The promise returned by this function resolves to another
-promise, not that promise's result. You will need to `aio-await'
-on it, or use `aio-result'."
- (let* ((result (aio-promise))
- (callback (lambda ()
- (let ((promise (aio--queue-get (aio-select-queue select))))
- (aio-resolve result (lambda () promise))))))
- (prog1 result
- (if (aio--queue-empty-p (aio-select-queue select))
- (setf (aio-select-callback select) callback)
- (funcall callback)))))
-
-;; Semaphores
-(cl-defstruct (aio-sem (:constructor aio--make-sem))
- "A semaphore object."
- ;; Semaphore value
- (value 0)
- ;; Queue of waiting async functions
- (queue (cons nil nil)))
-
-(defun aio-sem (init)
- "Create a new semaphore with initial value INIT."
- (aio--make-sem :value init))
-
-(defun aio-sem-post (sem)
- "Increment the value of SEM.
-
-If asynchronous functions are awaiting on SEM, then one will be
-woken up. This function is not awaitable."
- (when (<= (cl-incf (aio-sem-value sem)) 0)
- (let ((waiting (aio--queue-get (aio-sem-queue sem))))
- (when waiting
- (aio-resolve waiting (lambda () nil))))))
-
-(defun aio-sem-wait (sem)
- "Decrement the value of SEM.
-
-If SEM is at zero, returns a promise that will resolve when
-another asynchronous function uses `aio-sem-post'."
- (when (< (cl-decf (aio-sem-value sem)) 0)
- (aio--queue-put (aio-sem-queue sem) (aio-promise))))
-
-;; `emacs-lisp-mode' font lock
-
-(font-lock-add-keywords
- 'emacs-lisp-mode
- `((,(rx "(aio-defun" (+ blank)
- (group (+ (or (syntax word) (syntax symbol)))))
- 1 'font-lock-function-name-face)))
-
-(add-hook 'help-fns-describe-function-functions #'aio-describe-function)
-
-(defun aio-describe-function (function)
- "Insert whether FUNCTION is an asynchronous function.
-This function is added to ‘help-fns-describe-function-functions’."
- (when (function-get function 'aio-defun-p)
- (insert " This function is asynchronous; it returns "
- "an ‘aio-promise’ object.\n")))
-
-(add-to-list 'lisp-imenu-generic-expression
- (list nil (concat "^\\s-*(aio-defun\\s-+\\(" lisp-mode-symbol-regexp "\\)") 1))
-
-(provide 'aio)
-
-;;; aio.el ends here
--- /dev/null
+`aio` is to Emacs Lisp as [`asyncio`][asyncio] is to Python. This
+package builds upon Emacs 25 generators to provide functions that
+pause while they wait on asynchronous events. They do not block any
+thread while paused.
+
+The main components of this package are `aio-defun' / `aio-lambda'
+to define async function, and `aio-await' to pause these functions
+while they wait on asynchronous events. When an asynchronous
+function is paused, the main thread is not blocked. It is no more
+or less powerful than callbacks, but is nicer to use.
+
+This is implementation is based on Emacs 25 generators, and
+asynchronous functions are actually iterators in disguise, operated
+as stackless, asymmetric coroutines.
--- /dev/null
+# aio: async/await for Emacs Lisp
+
+`aio` is to Emacs Lisp as [`asyncio`][asyncio] is to Python. This
+package builds upon Emacs 25 generators to provide functions that
+pause while they wait on asynchronous events. They do not block any
+thread while paused.
+
+Introduction: [An Async / Await Library for Emacs Lisp][post]
+
+Installation is [available through MELPA][melpa]. Since it uses the
+`record` built-in, it requires Emacs 26 or later.
+
+## Usage
+
+An async function is defined using `aio-defun` or `aio-lambda`. The
+body of such functions can use `aio-await` to pause the function and
+wait on a given promise. The function continues with the promise's
+resolved value when it's ready. The package provides a number of
+functions that return promises, and every async function returns a
+promise representing its future return value.
+
+For example:
+
+```el
+(aio-defun foo (url)
+ (aio-await (aio-sleep 3))
+ (message "Done sleeping. Now fetching %s" url)
+ (let* ((result (aio-await (aio-url-retrieve url)))
+ (contents (with-current-buffer (cdr result)
+ (prog1 (buffer-string)
+ (kill-buffer)))))
+ (message "Result: %s" contents)))
+```
+
+If an uncaught signal terminates an asynchronous function, that signal
+is captured by its return value promise and propagated into any
+function that awaits on that function.
+
+```el
+(aio-defun divide (a b)
+ (aio-await (aio-sleep 1))
+ (/ a b))
+
+(aio-defun divide-safe (a b)
+ (condition-case error
+ (aio-await (divide a b))
+ (arith-error :arith-error)))
+
+(aio-wait-for (divide-safe 1.0 2.0))
+;; => 0.5
+
+(aio-wait-for (divide-safe 0 0))
+;; => :arith-error
+```
+
+To convert a callback-based function into an awaitable, async-friendly
+function, create a new promise object with `aio-promise`, then
+`aio-resolve` that promise in the callback. The helper function,
+`aio-make-callback`, makes this easy.
+
+## Utility macros and functions
+
+```el
+(aio-wait-for promise)
+;; Synchronously wait for PROMISE, blocking the current thread.
+
+(aio-cancel promise)
+;; Attempt to cancel PROMISE, returning non-nil if successful.
+
+(aio-with-promise promise &rest body) [macro]
+;; Evaluate BODY and resolve PROMISE with the result.
+
+(aio-with-async &rest body) [macro]
+;; Evaluate BODY asynchronously as if it was inside `aio-lambda'.
+
+(aio-make-callback &key tag once)
+;; Return a new callback function and its first promise.
+
+(aio-chain expr) [macro]
+;; `aio-await' on EXPR and replace place EXPR with the next promise.
+```
+
+The `aio-make-callback` function is especially useful for callbacks
+that are invoked repeatedly, such as process filters and sentinels.
+The `aio-chain` macro works in conjunction.
+
+## Awaitable functions
+
+Here are some useful promise-returning — i.e. awaitable — functions
+defined by this package.
+
+```el
+(aio-sleep seconds &optional result)
+;; Return a promise that is resolved after SECONDS with RESULT.
+
+(aio-idle seconds &optional result)
+;; Return a promise that is resolved after idle SECONDS with RESULT.
+
+(aio-url-retrieve url &optional silent inhibit-cookies)
+;; Wraps `url-retrieve' in a promise.
+
+(aio-all promises)
+;; Return a promise that resolves when all PROMISES are resolved."
+```
+
+## Select API
+
+This package includes a select()-like, level-triggered API for waiting
+on multiple promises at once. Create a "select" object, add promises
+to it, and await on it. Resolved and returned promises are
+automatically removed, and the "select" object can be reused.
+
+```el
+(aio-make-select &optional promises)
+;; Create a new `aio-select' object for waiting on multiple promises.
+
+(aio-select-add select promise)
+;; Add PROMISE to the set of promises in SELECT.
+
+(aio-select-remove select promise)
+;; Remove PROMISE form the set of promises in SELECT.
+
+(aio-select-promises select)
+;; Return a list of promises in SELECT.
+
+(aio-select select)
+;; Return a promise that resolves when any promise in SELECT resolves.
+```
+
+For example, here's an implementation of sleep sort:
+
+```el
+(aio-defun sleep-sort (values)
+ (let* ((promises (mapcar (lambda (v) (aio-sleep v v)) values))
+ (select (aio-make-select promises)))
+ (cl-loop repeat (length promises)
+ for next = (aio-await (aio-select select))
+ collect (aio-await next))))
+```
+
+## Semaphore API
+
+Semaphores work just as they would as a thread synchronization
+primitive. There's an internal counter that cannot drop below zero,
+and `aio-sem-wait` is an awaitable function that may block the
+asynchronous function until another asynchronous function calls
+`aio-sem-post`. Blocked functions wait in a FIFO queue and are awoken
+in the same order that they awaited.
+
+```el
+(aio-sem init)
+;; Create a new semaphore with initial value INIT.
+
+(aio-sem-post sem)
+;; Increment the value of SEM.
+
+(aio-sem-wait sem)
+;; Decrement the value of SEM.
+```
+
+This can be used to create a work queue. For example, here's a
+configurable download queue for `url-retrieve`:
+
+```el
+(defun fetch (url-list max-parallel callback)
+ (let ((sem (aio-sem max-parallel)))
+ (dolist (url url-list)
+ (aio-with-async
+ (aio-await (aio-sem-wait sem))
+ (cl-destructuring-bind (status . buffer)
+ (aio-await (aio-url-retrieve url))
+ (aio-sem-post sem)
+ (funcall callback
+ (with-current-buffer buffer
+ (prog1 (buffer-string)
+ (kill-buffer)))))))))
+```
+
+
+[asyncio]: https://docs.python.org/3/library/asyncio.html
+[melpa]: https://melpa.org/#/aio
+[post]: https://nullprogram.com/blog/2019/03/10/
--- /dev/null
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org/>
--- /dev/null
+;;; aio-autoloads.el --- automatically extracted autoloads (do not edit) -*- lexical-binding: t -*-
+;; Generated by the `loaddefs-generate' function.
+
+;; This file is part of GNU Emacs.
+
+;;; Code:
+
+(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path)))
+
+
+\f
+;;; Generated autoloads from aio.el
+
+(register-definition-prefixes "aio" '("aio-"))
+\f
+;;; End of scraped data
+
+(provide 'aio-autoloads)
+
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; no-native-compile: t
+;; coding: utf-8-emacs-unix
+;; End:
+
+;;; aio-autoloads.el ends here
--- /dev/null
+;; -*- no-byte-compile: t; lexical-binding: nil -*-
+(define-package "aio" "20260214.1529"
+ "Async/await for Emacs Lisp."
+ '((emacs "26.1"))
+ :url "https://github.com/skeeto/emacs-aio"
+ :commit "0e94a06bb035953cbbb4242568b38ca15443ad4c"
+ :revdesc "0e94a06bb035"
+ :authors '(("Christopher Wellons" . "wellonsNOSPAMnullprogram.com"))
+ :maintainers '(("Christopher Wellons" . "wellonsNOSPAMnullprogram.com")))
--- /dev/null
+;;; aio.el --- async/await for Emacs Lisp -*- lexical-binding: t; -*-
+
+;; This is free and unencumbered software released into the public domain.
+
+;; Author: Christopher Wellons <wellonsNOSPAMnullprogram.com>
+;; URL: https://github.com/skeeto/emacs-aio
+;; Package-Version: 20260214.1529
+;; Package-Revision: 0e94a06bb035
+;; Package-Requires: ((emacs "26.1"))
+
+;;; Commentary:
+
+;; `aio` is to Emacs Lisp as [`asyncio`][asyncio] is to Python. This
+;; package builds upon Emacs 25 generators to provide functions that
+;; pause while they wait on asynchronous events. They do not block any
+;; thread while paused.
+
+;; The main components of this package are `aio-defun' / `aio-lambda'
+;; to define async function, and `aio-await' to pause these functions
+;; while they wait on asynchronous events. When an asynchronous
+;; function is paused, the main thread is not blocked. It is no more
+;; or less powerful than callbacks, but is nicer to use.
+
+;; This is implementation is based on Emacs 25 generators, and
+;; asynchronous functions are actually iterators in disguise, operated
+;; as stackless, asymmetric coroutines.
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'font-lock)
+(require 'generator)
+(require 'macroexp)
+(require 'rx)
+(require 'iter2 nil t)
+
+;; Register new error types
+(define-error 'aio-cancel "Promise was canceled")
+(define-error 'aio-timeout "Timeout was reached")
+
+(cl-defstruct (aio-promise (:constructor aio-promise))
+ "A promise object."
+ result
+ callbacks)
+
+(defsubst aio-result (promise)
+ "Return the result of PROMISE, or nil if it is unresolved.
+
+Promise results are wrapped in a function. The result must be
+called (e.g. `funcall') in order to retrieve the value."
+ (aio-promise-result promise))
+
+(defun aio-listen (promise callback)
+ "Add CALLBACK to PROMISE.
+
+If the promise has already been resolved, the callback will be
+scheduled for the next event loop turn."
+ (let ((result (aio-result promise)))
+ (if result
+ (run-at-time 0 nil callback result)
+ (push callback (aio-promise-callbacks promise)))))
+
+(defun aio-resolve (promise value-function)
+ "Resolve this PROMISE with VALUE-FUNCTION.
+
+A promise can only be resolved once, and any further calls to
+`aio-resolve' are silently ignored. The VALUE-FUNCTION must be a
+function that takes no arguments and either returns the result
+value or rethrows a signal."
+ (cl-check-type value-function function)
+ (unless (aio-result promise)
+ (let ((callbacks (nreverse (aio-promise-callbacks promise))))
+ (setf (aio-promise-result promise) value-function
+ (aio-promise-callbacks promise) ())
+ (dolist (callback callbacks)
+ (run-at-time 0 nil callback value-function)))))
+
+(defun aio--step (iter promise yield-result)
+ "Advance ITER to the next promise.
+
+PROMISE is the return promise of the iterator, which was returned
+by the originating async function. YIELD-RESULT is the value
+function result directly from the previously yielded promise."
+ (condition-case _
+ (cl-loop for result = (iter-next iter yield-result)
+ then (iter-next iter (lambda () result))
+ until (aio-promise-p result)
+ finally (aio-listen result
+ (lambda (value)
+ (aio--step iter promise value))))
+ (iter-end-of-sequence)))
+
+(defmacro aio-with-promise (promise &rest body)
+ "Evaluate BODY and resolve PROMISE with the result.
+
+If the body signals an error, this error will be stored in the
+promise and rethrown in the promise's listeners."
+ (declare (indent defun)
+ (debug (form body)))
+ (cl-assert (eq lexical-binding t))
+ `(aio-resolve ,promise
+ (condition-case error
+ (let ((result ,(macroexp-progn body)))
+ (lambda () result))
+ (error (lambda ()
+ (signal (car error) (cdr error)))))))
+
+(defmacro aio-await (expr)
+ "If EXPR evaluates to a promise, pause until the promise is resolved.
+
+Pausing an async function does not block Emacs' main thread. If
+EXPR doesn't evaluate to a promise, the value is returned
+immediately and the function is not paused. Since async functions
+return promises, async functions can await directly on other
+async functions using this macro.
+
+This macro can only be used inside an async function, either
+`aio-lambda' or `aio-defun'."
+ `(funcall (iter-yield ,expr)))
+
+(defmacro aio-lambda (arglist &rest body)
+ "Like `lambda', but defines an async function.
+
+The body of this function may use `aio-await' to wait on
+promises. When an async function is called, it immediately
+returns a promise that will resolve to the function's return
+value, or any uncaught error signal.
+
+See Info node ‘(elisp)Lambda Components’ for a description of
+ARGLIST and BODY.
+
+\(fn ARGLIST &optional DOCSTRING INTERACTIVE &rest BODY)"
+ (declare (indent defun)
+ (doc-string 3)
+ (debug (&define lambda-list lambda-doc
+ [&optional ("interactive" interactive)]
+ &rest sexp)))
+ (let ((args (make-symbol "args"))
+ (promise (make-symbol "promise"))
+ (split-body (macroexp-parse-body body)))
+ (if (fboundp 'iter2-lambda)
+ `(lambda (&rest ,args)
+ ,@(car split-body)
+ (let* ((,promise (aio-promise))
+ (iter (apply (iter2-lambda ,arglist
+ (aio-with-promise ,promise
+ ,@(cdr split-body)))
+ ,args)))
+ (prog1 ,promise
+ (aio--step iter ,promise nil))))
+ `(lambda (&rest ,args)
+ ,@(car split-body)
+ (let* ((,promise (aio-promise))
+ (iter (apply (iter-lambda ,arglist
+ (aio-with-promise ,promise
+ ,@(cdr split-body)))
+ ,args)))
+ (prog1 ,promise
+ (aio--step iter ,promise nil)))))))
+
+(defmacro aio-defun (name arglist &rest body)
+ "Like `aio-lambda' but gives the function a NAME like `defun'.
+
+See Info node ‘(elisp)Defining Functions’ for a description of
+ARGLIST and BODY."
+ (declare (indent defun)
+ (doc-string 3)
+ (debug (&define name lambda-list &rest sexp)))
+ (or name (error "Cannot define `%s' as a function" name))
+ (let* ((split-body (macroexp-parse-body body))
+ (declarations (car split-body))
+ (body (cdr split-body))
+ (docstring (and (stringp (car declarations)) (pop declarations)))
+ (declares (and (eq (car-safe (car declarations)) #'declare)
+ (cdr (pop declarations)))))
+ ;; Other declarations (e.g., ‘interactive’ forms) are left as-is.
+ `(progn
+ (defalias ',name (aio-lambda ,arglist ,docstring ,@declarations ,@body))
+ (function-put ',name 'aio-defun-p t)
+ ,@(mapcar
+ (lambda (declare)
+ (let* ((prop (car declare))
+ (args (cdr declare))
+ (fun (assq prop defun-declarations-alist)))
+ (or fun (error "Unknown ‘defun’ declaration property %s" prop))
+ (apply (cadr fun) name arglist args)))
+ declares)
+ (set-advertised-calling-convention (indirect-function ',name) ',arglist nil)
+ ',name)))
+
+(defun aio-wait-for (promise)
+ "Synchronously wait for PROMISE, blocking the current thread."
+ (while (null (aio-result promise))
+ (accept-process-output))
+ (funcall (aio-result promise)))
+
+(defun aio-cancel (promise &optional reason)
+ "Attempt to cancel PROMISE, returning non-nil if successful.
+
+All awaiters will receive an `aio-cancel' signal. The actual
+underlying asynchronous operation will not actually be canceled.
+Optional argument REASON is used as error data for the signal."
+ (unless (aio-result promise)
+ (aio-resolve promise (lambda () (signal 'aio-cancel reason)))))
+
+(defmacro aio-with-async (&rest body)
+ "Evaluate BODY asynchronously as if it was inside `aio-lambda'.
+
+Since BODY is evalued inside an asynchronous lambda, `aio-await'
+is available here. This macro evaluates to a promise for BODY's
+eventual result.
+
+Beware: Dynamic bindings that are lexically outside
+‘aio-with-async’ blocks have no effect. For example,
+
+ (defvar dynamic-var nil)
+ (defun my-func ()
+ (let ((dynamic-var 123))
+ (aio-with-async dynamic-var)))
+ (let ((dynamic-var 456))
+ (aio-wait-for (my-func)))
+ ⇒ 456
+
+Other global state such as the current buffer behaves likewise."
+ (declare (indent 0)
+ (debug (&rest sexp)))
+ `(let ((promise (funcall (aio-lambda ()
+ (aio-await (aio-sleep 0))
+ ,@body))))
+ (prog1 promise
+ ;; The is the main feature: Force the final result to be
+ ;; realized so that errors are reported.
+ (aio-listen promise #'funcall))))
+
+(defmacro aio-chain (expr)
+ "`aio-await' on EXPR and replace place EXPR with the next promise.
+
+EXPR must be setf-able. Returns (cdr result). This macro is
+intended to be used with `aio-make-callback' in order to follow
+a chain of promise-yielding promises."
+ (let ((result (make-symbol "result")))
+ `(let ((,result (aio-await ,expr)))
+ (setf ,expr (car ,result))
+ (cdr ,result))))
+
+;; Useful promise-returning functions:
+
+(defmacro aio-all (promises)
+ "Return a promise that resolves when all PROMISES are resolved."
+ `(let ((promises ,promises))
+ (while promises
+ (aio-await (pop promises)))))
+
+(defun aio-catch (promise)
+ "Return a new promise that wraps PROMISE but will never signal.
+
+The promise value is a cons where the car is either :success or
+:error. For :success, the cdr will be the result value. For
+:error, the cdr will be the error data."
+ (let ((result (aio-promise)))
+ (cl-flet ((callback (value)
+ (aio-resolve result
+ (lambda ()
+ (condition-case error
+ (cons :success (funcall value))
+ (error (cons :error error)))))))
+ (prog1 result
+ (aio-listen promise #'callback)))))
+
+(defun aio-sleep (seconds &optional result)
+ "Create a promise that is resolved after SECONDS with RESULT.
+
+The result is a value, not a value function, and it will be
+automatically wrapped with a value function (see `aio-resolve')."
+ (let ((promise (aio-promise)))
+ (prog1 promise
+ (run-at-time seconds nil
+ #'aio-resolve promise (lambda () result)))))
+
+(defun aio-idle (seconds &optional result)
+ "Create a promise that is resolved after idle SECONDS with RESULT.
+
+The result is a value, not a value function, and it will be
+automatically wrapped with a value function (see `aio-resolve')."
+ (let ((promise (aio-promise)))
+ (prog1 promise
+ (run-with-idle-timer seconds nil
+ #'aio-resolve promise (lambda () result)))))
+
+(defun aio-timeout (seconds)
+ "Create a promise with a timeout error after SECONDS."
+ (let ((promise (aio-promise)))
+ (prog1 promise
+ (run-at-time seconds nil
+ #'aio-resolve promise (lambda () (signal 'aio-timeout seconds))))))
+
+(defun aio-url-retrieve (url &optional silent inhibit-cookies)
+ "Wraps `url-retrieve' in a promise.
+
+This function will never directly signal an error. Instead any
+errors will be delivered via the returned promise. The promise
+result is a cons of (status . buffer). This buffer is a clone of
+the buffer created by `url-retrieve' and should be killed by the
+caller.
+
+Arguments URL, SILENT, and INHIBIT-COOKIES are passed on to
+`url-retrieve', which see. Also see Info node ‘(url)Retrieving
+URLs’ for details."
+ (require 'url)
+ (let ((promise (aio-promise)))
+ (prog1 promise
+ (condition-case error
+ (url-retrieve url (lambda (status)
+ (let ((value (cons status (clone-buffer))))
+ (aio-resolve promise (lambda () value))))
+ silent inhibit-cookies)
+ (error (aio-resolve promise
+ (lambda ()
+ (signal (car error) (cdr error)))))))))
+
+(cl-defun aio-make-callback (&key tag once)
+ "Return a new callback function and its first promise.
+
+Returns a cons (callback . promise) where callback is function
+suitable for repeated invocation. This makes it useful for
+process filters and sentinels. The promise is the first promise
+to be resolved by the callback.
+
+The promise resolves to:
+ (next-promise . callback-args)
+Or when TAG is supplied:
+ (next-promise TAG . callback-args)
+Or if ONCE is non-nil:
+ callback-args
+
+The callback resolves next-promise on the next invocation. This
+creates a chain of promises representing the sequence of calls.
+Note: To avoid keeping lots of garbage in memory, avoid holding
+onto the first promise (i.e. capturing it in a closure).
+
+The `aio-chain' macro makes it easier to use these promises."
+ (let* ((promise (aio-promise))
+ (callback
+ (if once
+ (lambda (&rest args)
+ (let ((result (if tag
+ (cons tag args)
+ args)))
+ (aio-resolve promise (lambda () result))))
+ (lambda (&rest args)
+ (let* ((next-promise (aio-promise))
+ (result (if tag
+ (cons next-promise (cons tag args))
+ (cons next-promise args))))
+ (aio-resolve promise (lambda () result))
+ (setf promise next-promise))))))
+ (cons callback promise)))
+
+;; A simple little queue
+
+(defsubst aio--queue-empty-p (queue)
+ "Return non-nil if QUEUE is empty.
+An empty queue is (nil . nil)."
+ (null (caar queue)))
+
+(defsubst aio--queue-get (queue)
+ "Get the next item from QUEUE, or nil for empty."
+ (let ((head (car queue)))
+ (cond ((null head)
+ nil)
+ ((eq head (cdr queue))
+ (prog1 (car head)
+ (setf (car queue) nil
+ (cdr queue) nil)))
+ ((prog1 (car head)
+ (setf (car queue) (cdr head)))))))
+
+(defsubst aio--queue-put (queue element)
+ "Append ELEMENT to QUEUE, returning ELEMENT."
+ (let ((new (list element)))
+ (prog1 element
+ (if (null (car queue))
+ (setf (car queue) new
+ (cdr queue) new)
+ (setf (cdr (cdr queue)) new
+ (cdr queue) new)))))
+
+;; An efficient select()-like interface for promises
+(cl-defstruct (aio-select (:constructor aio--make-select))
+ "A select() object for waiting on multiple promises."
+ ;; Membership table
+ (members (make-hash-table :test 'eq))
+ ;; "Seen" table (avoid adding multiple callback)
+ (seen (make-hash-table :test 'eq :weakness 'key))
+ ;; Queue of pending resolved promises
+ (queue (cons nil nil))
+ ;; Callback to resolve select's own promise
+ callback)
+
+(defun aio-make-select (&optional promises)
+ "Create a new `aio-select' object for waiting on multiple PROMISES."
+ (let ((select (aio--make-select)))
+ (prog1 select
+ (dolist (promise promises)
+ (aio-select-add select promise)))))
+
+(defun aio-select-add (select promise)
+ "Add PROMISE to the set of promises in SELECT.
+
+SELECT is created with `aio-make-select'. It is valid to add a
+promise that was previously removed."
+ (let ((members (aio-select-members select))
+ (seen (aio-select-seen select)))
+ (prog1 promise
+ (unless (gethash promise seen)
+ (setf (gethash promise seen) t
+ (gethash promise members) t)
+ (aio-listen promise
+ (lambda (_)
+ (when (gethash promise members)
+ (aio--queue-put (aio-select-queue select) promise)
+ (remhash promise members)
+ (let ((callback (aio-select-callback select)))
+ (when callback
+ (setf (aio-select-callback select) nil)
+ (funcall callback))))))))))
+
+(defun aio-select-remove (select promise)
+ "Remove PROMISE form the set of promises in SELECT.
+
+SELECT is created with `aio-make-select'."
+ (remhash promise (aio-select-members select)))
+
+(defun aio-select-promises (select)
+ "Return a list of promises in SELECT.
+
+SELECT is created with `aio-make-select'."
+ (cl-loop for key being the hash-keys of (aio-select-members select)
+ collect key))
+
+(defun aio-select (select)
+ "Return a promise that resolves when any promise in SELECT resolves.
+
+SELECT is created with `aio-make-select'. This function is
+level-triggered: if a promise in SELECT is already resolved, it
+returns immediately with that promise. Promises returned by
+`aio-select' are automatically removed from SELECT. Use this
+function to repeatedly wait on a set of promises.
+
+Note: The promise returned by this function resolves to another
+promise, not that promise's result. You will need to `aio-await'
+on it, or use `aio-result'."
+ (let* ((result (aio-promise))
+ (callback (lambda ()
+ (let ((promise (aio--queue-get (aio-select-queue select))))
+ (aio-resolve result (lambda () promise))))))
+ (prog1 result
+ (if (aio--queue-empty-p (aio-select-queue select))
+ (setf (aio-select-callback select) callback)
+ (funcall callback)))))
+
+;; Semaphores
+(cl-defstruct (aio-sem (:constructor aio--make-sem))
+ "A semaphore object."
+ ;; Semaphore value
+ (value 0)
+ ;; Queue of waiting async functions
+ (queue (cons nil nil)))
+
+(defun aio-sem (init)
+ "Create a new semaphore with initial value INIT."
+ (aio--make-sem :value init))
+
+(defun aio-sem-post (sem)
+ "Increment the value of SEM.
+
+If asynchronous functions are awaiting on SEM, then one will be
+woken up. This function is not awaitable."
+ (when (<= (cl-incf (aio-sem-value sem)) 0)
+ (let ((waiting (aio--queue-get (aio-sem-queue sem))))
+ (when waiting
+ (aio-resolve waiting (lambda () nil))))))
+
+(defun aio-sem-wait (sem)
+ "Decrement the value of SEM.
+
+If SEM is at zero, returns a promise that will resolve when
+another asynchronous function uses `aio-sem-post'."
+ (when (< (cl-decf (aio-sem-value sem)) 0)
+ (aio--queue-put (aio-sem-queue sem) (aio-promise))))
+
+;; `emacs-lisp-mode' font lock
+
+(font-lock-add-keywords
+ 'emacs-lisp-mode
+ `((,(rx "(aio-defun" (+ blank)
+ (group (+ (or (syntax word) (syntax symbol)))))
+ 1 'font-lock-function-name-face)))
+
+(add-hook 'help-fns-describe-function-functions #'aio-describe-function)
+
+(defun aio-describe-function (function)
+ "Insert whether FUNCTION is an asynchronous function.
+This function is added to ‘help-fns-describe-function-functions’."
+ (when (function-get function 'aio-defun-p)
+ (insert " This function is asynchronous; it returns "
+ "an ‘aio-promise’ object.\n")))
+
+(add-to-list 'lisp-imenu-generic-expression
+ (list nil (concat "^\\s-*(aio-defun\\s-+\\(" lisp-mode-symbol-regexp "\\)") 1))
+
+(provide 'aio)
+
+;;; aio.el ends here