]> git.bundesbrandschatzamt.de Git - emacsconfig.git/commitdiff
update aio
authorAndreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
Sun, 26 Apr 2026 16:33:29 +0000 (18:33 +0200)
committerAndreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
Sun, 26 Apr 2026 16:33:29 +0000 (18:33 +0200)
elpa/aio-20251117.644/README.md [deleted file]
elpa/aio-20251117.644/UNLICENSE [deleted file]
elpa/aio-20251117.644/aio-autoloads.el [deleted file]
elpa/aio-20251117.644/aio-pkg.el [deleted file]
elpa/aio-20251117.644/aio.el [deleted file]
elpa/aio-20260214.1529/README-elpa [new file with mode: 0644]
elpa/aio-20260214.1529/README.md [new file with mode: 0644]
elpa/aio-20260214.1529/UNLICENSE [new file with mode: 0644]
elpa/aio-20260214.1529/aio-autoloads.el [new file with mode: 0644]
elpa/aio-20260214.1529/aio-pkg.el [new file with mode: 0644]
elpa/aio-20260214.1529/aio.el [new file with mode: 0644]

diff --git a/elpa/aio-20251117.644/README.md b/elpa/aio-20251117.644/README.md
deleted file mode 100644 (file)
index 8267bec..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-# 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/
diff --git a/elpa/aio-20251117.644/UNLICENSE b/elpa/aio-20251117.644/UNLICENSE
deleted file mode 100644 (file)
index 68a49da..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-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/>
diff --git a/elpa/aio-20251117.644/aio-autoloads.el b/elpa/aio-20251117.644/aio-autoloads.el
deleted file mode 100644 (file)
index 326bff6..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-;;; 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
diff --git a/elpa/aio-20251117.644/aio-pkg.el b/elpa/aio-20251117.644/aio-pkg.el
deleted file mode 100644 (file)
index 2716b4d..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-;; -*- 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")))
diff --git a/elpa/aio-20251117.644/aio.el b/elpa/aio-20251117.644/aio.el
deleted file mode 100644 (file)
index 3b29784..0000000
+++ /dev/null
@@ -1,514 +0,0 @@
-;;; 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
diff --git a/elpa/aio-20260214.1529/README-elpa b/elpa/aio-20260214.1529/README-elpa
new file mode 100644 (file)
index 0000000..be6c712
--- /dev/null
@@ -0,0 +1,14 @@
+`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.
diff --git a/elpa/aio-20260214.1529/README.md b/elpa/aio-20260214.1529/README.md
new file mode 100644 (file)
index 0000000..8267bec
--- /dev/null
@@ -0,0 +1,182 @@
+# 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/
diff --git a/elpa/aio-20260214.1529/UNLICENSE b/elpa/aio-20260214.1529/UNLICENSE
new file mode 100644 (file)
index 0000000..68a49da
--- /dev/null
@@ -0,0 +1,24 @@
+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/>
diff --git a/elpa/aio-20260214.1529/aio-autoloads.el b/elpa/aio-20260214.1529/aio-autoloads.el
new file mode 100644 (file)
index 0000000..326bff6
--- /dev/null
@@ -0,0 +1,28 @@
+;;; 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
diff --git a/elpa/aio-20260214.1529/aio-pkg.el b/elpa/aio-20260214.1529/aio-pkg.el
new file mode 100644 (file)
index 0000000..f29edc1
--- /dev/null
@@ -0,0 +1,9 @@
+;; -*- 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")))
diff --git a/elpa/aio-20260214.1529/aio.el b/elpa/aio-20260214.1529/aio.el
new file mode 100644 (file)
index 0000000..57961cb
--- /dev/null
@@ -0,0 +1,514 @@
+;;; 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