From: Andreas Gerler Date: Sun, 26 Apr 2026 16:33:29 +0000 (+0200) Subject: update aio X-Git-Url: http://git.bundesbrandschatzamt.de/?a=commitdiff_plain;h=7828577f8e6d8e26daa860061924d357d88deb38;p=emacsconfig.git update aio --- diff --git a/elpa/aio-20251117.644/README.md b/elpa/aio-20251117.644/README.md deleted file mode 100644 index 8267bec6..00000000 --- a/elpa/aio-20251117.644/README.md +++ /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 index 68a49daa..00000000 --- a/elpa/aio-20251117.644/UNLICENSE +++ /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 diff --git a/elpa/aio-20251117.644/aio-autoloads.el b/elpa/aio-20251117.644/aio-autoloads.el deleted file mode 100644 index 326bff68..00000000 --- a/elpa/aio-20251117.644/aio-autoloads.el +++ /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))) - - - -;;; 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 deleted file mode 100644 index 2716b4d7..00000000 --- a/elpa/aio-20251117.644/aio-pkg.el +++ /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" . "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 deleted file mode 100644 index 3b297845..00000000 --- a/elpa/aio-20251117.644/aio.el +++ /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 -;; 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 index 00000000..be6c7125 --- /dev/null +++ b/elpa/aio-20260214.1529/README-elpa @@ -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 index 00000000..8267bec6 --- /dev/null +++ b/elpa/aio-20260214.1529/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-20260214.1529/UNLICENSE b/elpa/aio-20260214.1529/UNLICENSE new file mode 100644 index 00000000..68a49daa --- /dev/null +++ b/elpa/aio-20260214.1529/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-20260214.1529/aio-autoloads.el b/elpa/aio-20260214.1529/aio-autoloads.el new file mode 100644 index 00000000..326bff68 --- /dev/null +++ b/elpa/aio-20260214.1529/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-20260214.1529/aio-pkg.el b/elpa/aio-20260214.1529/aio-pkg.el new file mode 100644 index 00000000..f29edc1c --- /dev/null +++ b/elpa/aio-20260214.1529/aio-pkg.el @@ -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" . "wellons@nullprogram.com")) + :maintainers '(("Christopher Wellons" . "wellons@nullprogram.com"))) diff --git a/elpa/aio-20260214.1529/aio.el b/elpa/aio-20260214.1529/aio.el new file mode 100644 index 00000000..57961cbf --- /dev/null +++ b/elpa/aio-20260214.1529/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: 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