From 33439c33e5861b690b209051d9e1048fb9314905 Mon Sep 17 00:00:00 2001 From: Andreas Gerler Date: Wed, 24 Dec 2025 12:01:24 +0100 Subject: [PATCH] update aio --- elpa/aio-20251117.644/README.md | 182 +++++++++ elpa/aio-20251117.644/UNLICENSE | 24 ++ elpa/aio-20251117.644/aio-autoloads.el | 28 ++ elpa/aio-20251117.644/aio-pkg.el | 9 + elpa/aio-20251117.644/aio.el | 514 +++++++++++++++++++++++++ 5 files changed, 757 insertions(+) create mode 100644 elpa/aio-20251117.644/README.md create mode 100644 elpa/aio-20251117.644/UNLICENSE create mode 100644 elpa/aio-20251117.644/aio-autoloads.el create mode 100644 elpa/aio-20251117.644/aio-pkg.el create mode 100644 elpa/aio-20251117.644/aio.el diff --git a/elpa/aio-20251117.644/README.md b/elpa/aio-20251117.644/README.md new file mode 100644 index 00000000..8267bec6 --- /dev/null +++ b/elpa/aio-20251117.644/README.md @@ -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 index 00000000..68a49daa --- /dev/null +++ b/elpa/aio-20251117.644/UNLICENSE @@ -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 diff --git a/elpa/aio-20251117.644/aio-autoloads.el b/elpa/aio-20251117.644/aio-autoloads.el new file mode 100644 index 00000000..326bff68 --- /dev/null +++ b/elpa/aio-20251117.644/aio-autoloads.el @@ -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))) + + + +;;; Generated autoloads from aio.el + +(register-definition-prefixes "aio" '("aio-")) + +;;; 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 index 00000000..2716b4d7 --- /dev/null +++ b/elpa/aio-20251117.644/aio-pkg.el @@ -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" . "wellons@nullprogram.com")) + :maintainers '(("Christopher Wellons" . "wellons@nullprogram.com"))) diff --git a/elpa/aio-20251117.644/aio.el b/elpa/aio-20251117.644/aio.el new file mode 100644 index 00000000..3b297845 --- /dev/null +++ b/elpa/aio-20251117.644/aio.el @@ -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 +;; 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 -- 2.47.3