aboutsummaryrefslogtreecommitdiff
path: root/src/combinator.lisp
diff options
context:
space:
mode:
Diffstat (limited to 'src/combinator.lisp')
-rw-r--r--src/combinator.lisp121
1 files changed, 82 insertions, 39 deletions
diff --git a/src/combinator.lisp b/src/combinator.lisp
index 352ae46..772ac6c 100644
--- a/src/combinator.lisp
+++ b/src/combinator.lisp
@@ -65,32 +65,37 @@ Usage notes:
(propappunapply (choose-propapp))))))
(setf (get ',name 'inline-combinator) t)))
-(defun skip-property-restarts ()
- (loop for restart in (compute-restarts)
- when (eql 'skip-property (restart-name restart))
- collect restart))
-
-;; There can be multiple SKIP-PROPERTY restarts established at once, and we
-;; need this handler to invoke the one established right after we establish
-;; this handler.
-(defmacro with-skip-failed-changes (&body forms)
- (with-gensyms (old-restarts)
- `(let ((,old-restarts (skip-property-restarts)))
- (handler-bind ((failed-change
- (lambda (c)
- (with-indented-inform
- (apply #'informat t
- (simple-condition-format-control c)
- (simple-condition-format-arguments c)))
- ;; We can't just use NSET-DIFFERENCE and take the
- ;; LASTCAR because NSET-DIFFERENCE provides no
- ;; ordering guarantees.
- (loop with chosen
- for restart in (skip-property-restarts)
- unless (member restart ,old-restarts)
- do (setq chosen restart)
- finally (invoke-restart chosen)))))
- ,@forms))))
+;; There can be multiple SKIP-* restarts with the same name established at
+;; once, and we need this handler to invoke one of the four established by the
+;; call to APPLY-AND-PRINT right after we establish this handler.
+(defmacro with-skip-failed-changes
+ ((&key (condition ''failed-change) (restart ''skip-property)) &body forms)
+ (once-only (condition restart)
+ (with-gensyms (old-restarts)
+ `(let* ((,old-restarts
+ (loop for restart
+ in (compute-restarts (make-condition ,condition))
+ when (eql (restart-name restart) ,restart)
+ collect restart)))
+ (handler-bind
+ ((failed-change
+ (lambda (c)
+ (when (subtypep (type-of c) ,condition)
+ (with-indented-inform
+ (apply #'informat t
+ (simple-condition-format-control c)
+ (simple-condition-format-arguments c)))
+ ;; We can't just use NSET-DIFFERENCE and take the
+ ;; LASTCAR because NSET-DIFFERENCE provides no ordering
+ ;; guarantees.
+ (loop with chosen and old-restarts = ,old-restarts
+ for restart in (compute-restarts c)
+ if (eql restart (car old-restarts))
+ do (pop old-restarts)
+ else if (eql (restart-name restart) ,restart)
+ do (setq chosen restart)
+ finally (invoke-restart chosen))))))
+ ,@forms)))))
(define-function-property-combinator eseqprops (&rest propapps)
(:retprop :type (collapse-types (mapcar #'propapptype propapps))
@@ -98,14 +103,28 @@ Usage notes:
:apply (lambda () (apply-and-print propapps))
:unapply (lambda () (apply-and-print propapps t))))
+(define-function-property-combinator eseqprops-until (condition &rest propapps)
+ "Like ESEQPROPS, but if CONDITION is signalled, handle it simply by skipping
+remaining elements of PROPAPPS. CONDITION must subtype FAILED-CHANGE."
+ (:retprop :type (collapse-types (mapcar #'propapptype propapps))
+ :hostattrs (lambda () (mapc #'propappattrs propapps))
+ :apply (lambda ()
+ (with-skip-failed-changes (:condition condition
+ :restart 'skip-sequence)
+ (apply-and-print propapps)))
+ :unapply (lambda ()
+ (with-skip-failed-changes (:condition condition
+ :restart 'skip-sequence)
+ (apply-and-print propapps t)))))
+
(define-function-property-combinator seqprops (&rest propapps)
(:retprop :type (collapse-types (mapcar #'propapptype propapps))
:hostattrs (lambda () (mapc #'propappattrs propapps))
:apply (lambda ()
- (with-skip-failed-changes
+ (with-skip-failed-changes ()
(apply-and-print propapps)))
:unapply (lambda ()
- (with-skip-failed-changes
+ (with-skip-failed-changes ()
(apply-and-print propapps t)))))
(defmacro with-requirements (propapp &body requirements)
@@ -118,10 +137,10 @@ apply the elements of REQUIREMENTS in reverse order."
(:retprop :type (collapse-types (mapcar #'propapptype propapps))
:hostattrs (lambda () (mapc #'propappattrs propapps))
:apply (lambda ()
- (with-skip-failed-changes
+ (with-skip-failed-changes ()
(apply-and-print propapps nil t)))
:unapply (lambda ()
- (with-skip-failed-changes
+ (with-skip-failed-changes ()
(apply-and-print propapps t t)))))
(defun apply-and-print
@@ -160,8 +179,21 @@ apply the elements of REQUIREMENTS in reverse order."
(informat t "~&~@[~A :: ~]~@[~A ... ~]~A~%"
(get-hostname) (propappdesc propapp) status))
;; Ensure POST-APPLY called exactly once for each propapp.
- (setq propapp nil))))
+ (setq propapp nil)))
+
+ (test (c) (subtypep (type-of c) 'aborted-change))
+ (ntest (c) (not (subtypep (type-of c) 'aborted-change)))
+
+ (pareport (s)
+ (format s "Skip (~{~S~^ ~})"
+ (cons (car propapp) (propappargs propapp))))
+ (seqreport (s)
+ (format s "Skip remainder of sequence containing (~{~S~^ ~})"
+ (cons (car propapp) (propappargs propapp)))))
(unwind-protect
+ ;; Establish restarts to be invoked by WITH-SKIP-FAILED-CHANGES
+ ;; or possibly interactively by the user. There are two of each
+ ;; because we want to handle ABORTED-CHANGE specially.
(restart-case
(alet (if announce
(with-output-to-string (*standard-output* buffer)
@@ -174,14 +206,25 @@ apply the elements of REQUIREMENTS in reverse order."
(propappapply propapp)))
(accumulate it)
(post-apply (if (eql it :no-change) "ok" "done")))
- (skip-property ()
- :report (lambda (s)
- (format s "Skip (~{~S~^ ~})"
- (cons (car propapp)
- (propappargs propapp))))
- (signal 'skipped-properties)
- (post-apply "failed")
- (accumulate nil)))
+ ;; Standard restarts for skipping over sequence entries.
+ (skip-property () :test ntest :report pareport
+ (signal 'skipped-properties) (post-apply "failed")
+ (accumulate nil))
+ (skip-property () :test test :report pareport
+ (signal 'skipped-properties) (post-apply "failed")
+ (accumulate :no-change))
+ ;; Special restarts for the whole sequence which return from
+ ;; the enclosing DOLIST based on the kind of error. If
+ ;; ABORTED-CHANGE, we assume that applying the current propapp
+ ;; made no change, so we return a value indicating whether
+ ;; properties earlier in PROPAPPS made a change. Otherwise, we
+ ;; assume that some change was made.
+ (skip-sequence () :test ntest :report seqreport
+ (signal 'skipped-properties) (post-apply "failed")
+ (return))
+ (skip-sequence () :test test :report seqreport
+ (signal 'skipped-properties) (post-apply "failed")
+ (return return-value)))
;; Ensure we print out the buffer contents if due to a non-local
;; exit neither of the other calls to POST-APPLY have been made.
(post-apply "failed"))