Skip to content

Commit

Permalink
Give scope guards dynamic extent
Browse files Browse the repository at this point in the history
This brings the semantics closer to RAII.
  • Loading branch information
ruricolist committed Feb 18, 2024
1 parent 1c418c2 commit 8b72484
Showing 1 changed file with 61 additions and 48 deletions.
109 changes: 61 additions & 48 deletions defer.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
(deftype scope-condition ()
'(member :exit :success :failure))

(defvar-unbound *guarded-scope*
"The current guarded scope.")

(declaim (inline make-guarded-scope))
(defstruct guarded-scope
(guards nil :type list)
(success nil :type boolean))

(declaim (inline %make-scope-guard))
(defstruct-read-only (scope-guard (:constructor %make-scope-guard))
(thunk :type (function () (values &optional)))
Expand All @@ -25,69 +33,74 @@
(funcall (scope-guard-thunk scope-guard))
(setf (unbox (scope-guard-called scope-guard)) t)))

(defloop execute-scope-guards (scope-guards)
(unwind-protect
(execute-scope-guard (first scope-guards))
(execute-scope-guards (rest scope-guards))))
(-> execute-scope-guards (guarded-scope) (values &optional))
(defun execute-scope-guards (guarded-scope)
(nlet execute-scope-guards ((scope-guards (guarded-scope-guards guarded-scope)))
(unwind-protect
(execute-scope-guard (first scope-guards))
(execute-scope-guards (rest scope-guards))))
(values))

(defmacro unwind-protect* (protected &body cleanup)
"Like `unwind-protect', but try to guarantee cleanup forms cannot be
interrupted."
#+sbcl
`(sb-sys:without-interrupts
(unwind-protect
(sb-sys:with-local-interrupts
,protected)
,@cleanup))
;; CCL at least guarantees no interrupts in cleanup.
(unwind-protect
(sb-sys:with-local-interrupts
,protected)
,@cleanup))
;; CCL at least guarantees no interrupts in cleanup. TODO Does
;; anyone else?
#+ccl
`(unwind-protect ,protected ,@cleanup)
;; TODO.
#-(or ccl sbcl)
`(unwind-protect ,protected ,@cleanup))

(defmacro with-guarded-scope ((&key) &body body)
(with-unique-names (success scope-guards)
`(let ((,success nil)
(,scope-guards '()))
(macrolet ((with-scope-guard ((&key (on :exit)) &body body)
(ecase-of scope-condition on
(:exit
`(push
(make-scope-guard
(lambda ()
,@body
(values)))
,',scope-guards))
(:success
`(with-scope-guard ()
(when ,',success
,@body)))
(:failure
`(with-scope-guard ()
(unless ,',success
,@body))))))
(unwind-protect*
(multiple-value-prog1
(locally ,@body)
(setf ,success t))
(execute-scope-guards ,scope-guards))))))
(with-unique-names (guarded-scope)
`(let* ((,guarded-scope (make-guarded-scope))
(*guarded-scope* ,guarded-scope))
(unwind-protect*
(multiple-value-prog1
(locally ,@body)
(setf (guard-scope-success ,guarded-scope) t))
(execute-scope-guards ,guarded-scope)))))

(defmacro with-scope-guard ((&key (on :exit)) &body body)
(with-unique-names (guarded-scope)
`(let ((,guarded-scope *guarded-scope*))
,(ecase-of scope-condition on
(:exit
`(push
(make-scope-guard
(lambda ()
,@body
(values)))
,guarded-scope))
(:success
`(with-scope-guard ()
(when (guarded-scope-success ,guarded-scope)
,@body)))
(:failure
`(with-scope-guard ()
(unless (guarded-scope-success ,guarded-scope)
,@body)))))))

(defun call-deferred (fn &rest args)
(with-scope-guard (:on :exit)
(apply fn args)))

(defmacro defer ((fn . args))
"Define a single function call as an uncondiional scope
guard.
(defmacro with-defer ((&rest kwargs &key &allow-other-keys)
&body body)
"Bind the `defer' macro within a `with-guarded-scope' form.
Using `defer' defines a single function call as an uncondiional scope
guard. The function's arguments are executed immediately, but the
The function's arguments are executed immediately, but the
function itself is not called until the scope guard is run."
`(flet ((call/deferred (fn args)
(with-scope-guard (:on :exit)
(apply fn args))))
(macrolet ((defer ((fn . args))
(with-unique-names (temp-args)
`(let ((,temp-args (list ,@args)))
(call/deferred ,fn ,temp-args)))))
(with-guarded-scope (,kwargs)
,@body))))
(with-unique-names (temp-args)
`(let ((,temp-args (list ,@args)))
(call-deferred #',fn ,temp-args))))

(comment
(lambda ()
Expand Down

0 comments on commit 8b72484

Please sign in to comment.