]> git.bundesbrandschatzamt.de Git - emacsconfig.git/commitdiff
update aio
authorAndreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
Wed, 24 Dec 2025 11:01:24 +0000 (12:01 +0100)
committerAndreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
Wed, 24 Dec 2025 11:01:24 +0000 (12:01 +0100)
elpa/aio-20251117.644/README.md [new file with mode: 0644]
elpa/aio-20251117.644/UNLICENSE [new file with mode: 0644]
elpa/aio-20251117.644/aio-autoloads.el [new file with mode: 0644]
elpa/aio-20251117.644/aio-pkg.el [new file with mode: 0644]
elpa/aio-20251117.644/aio.el [new file with mode: 0644]

diff --git a/elpa/aio-20251117.644/README.md b/elpa/aio-20251117.644/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-20251117.644/UNLICENSE b/elpa/aio-20251117.644/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-20251117.644/aio-autoloads.el b/elpa/aio-20251117.644/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-20251117.644/aio-pkg.el b/elpa/aio-20251117.644/aio-pkg.el
new file mode 100644 (file)
index 0000000..2716b4d
--- /dev/null
@@ -0,0 +1,9 @@
+;; -*- 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
new file mode 100644 (file)
index 0000000..3b29784
--- /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: 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