diff --git a/doc/elpaca-thunk.el b/doc/elpaca-thunk.el new file mode 100644 index 00000000..15d1f46d --- /dev/null +++ b/doc/elpaca-thunk.el @@ -0,0 +1,38 @@ +;;; Example use of `elpaca-thunk' -*- lexical-binding: t; -*- + +;; Assuming that Elpaca has been bootstrapped +(require 'elpaca) + +;; Compile-time dependency +(eval-when-compile + ;; Built-in library + (require 'generator) + ;; Macro definition + (defmacro noop-macro (&rest body) + "Do nothing to BODY." + (declare (indent 0)) + (macroexp-progn body)) + ;; Inline function definition + (defsubst noop-fn (arg) + "Do nothing to ARG." + arg)) + +;; Unlike `elpaca', `elpaca-thunk' wraps BODY directly in a thunk +;; (nullary function), which means macros and inline functions are +;; expanded at compile time. Conversely, this also means that +;; bootstrapping can create dependency problem. Don't use it unless +;; you're sure what you're doing! +(elpaca-thunk nil + ;; This shouldn't lead to void-function + (noop-macro + (message (noop-fn "first")) + (noop-fn (iter-lambda () nil)) + (message (noop-fn "second")))) + +;; Process the above order +(elpaca-process-queues) + +;; Local Variables: +;; no-native-compile: t +;; no-update-autoloads: t +;; End: diff --git a/elpaca.el b/elpaca.el index 3dad368c..6f2132b1 100644 --- a/elpaca.el +++ b/elpaca.el @@ -1450,20 +1450,21 @@ When MESSAGE is non-nil, message the list of dependents." "Debounces interactive evaluation of multiple `elpaca' forms.") ;;;; COMMANDS/MACROS -;;;###autoload -(defmacro elpaca (order &rest body) - "Queue ORDER for installation/activation, defer execution of BODY. -If ORDER is `nil`, defer BODY until orders have been processed." - (declare (indent 1) (debug t)) +(defun elpaca--expand (order body make-thunk) + "Expand `elpaca' and alike. +ORDER and BODY are as in `elpaca', while MAKE-THUNK is used to produce +the thunk when given BODY." + (declare (side-effect-free t)) (let ((o (gensym "order-")) (item (gensym "item-")) (q (gensym "q-"))) `(let* ((,o ,@(if (memq (car-safe order) '(quote \`)) `(,order) `(',order))) (,item (elpaca--first ,o)) (,q (or (and after-init-time (elpaca--q (elpaca-get ,item))) (car elpaca--queues)))) ,@(when body - `((if ,item - (setf (alist-get ,item (elpaca-q<-forms ,q)) (lambda () (eval '(progn ,@body) t))) - ;;@FIX: nil semantics not good for multiple deferred... - (push (cons ,item (lambda () (eval '(progn ,@body) t))) (elpaca-q<-forms ,q))))) + (let ((thunk (funcall make-thunk body))) + `((if ,item + (setf (alist-get ,item (elpaca-q<-forms ,q)) ,thunk) + ;;@FIX: nil semantics not good for multiple deferred... + (push (cons ,item ,thunk) (elpaca-q<-forms ,q)))))) (when ,o (elpaca--queue ,o ,q)) (when after-init-time (when-let ((e (elpaca-get ,item))) @@ -1476,6 +1477,23 @@ If ORDER is `nil`, defer BODY until orders have been processed." (run-at-time elpaca-interactive-interval nil #'elpaca-process-queues))) nil))) +;;;###autoload +(defmacro elpaca (order &rest body) + "Queue ORDER for installation/activation, defer execution of BODY. +If ORDER is `nil`, defer BODY until orders have been processed." + (declare (indent 1) (debug t)) + (elpaca--expand + order body + (lambda (body) `(lambda () (eval '(progn ,@body) t))))) + +;;;###autoload +(defmacro elpaca-thunk (order &rest body) + "Queue ORDER like `elpaca', but BODY is wrapped in a thunk." + (declare (indent 1) (debug t)) + (elpaca--expand + order body + (lambda (body) `(lambda () ,@body)))) + (defcustom elpaca-wait-interval 0.01 "Seconds between `elpaca-wait' status checks." :type 'number)