diff --git a/README.md b/README.md index b792913..2dc347a 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,10 @@ Given an aggregator and a value, should we include the value in our collection. Used in conjunction with the skip restart to orchestrate skipping items. +#### Place Setters + +This is function (or list thereof) that writes the aggregate value to the place after each + #### Signals and Restarts * collectors-signals:aggregating - signaled when we begin aggregating a value diff --git a/collectors.lisp b/collectors.lisp index 7f8a758..c83c6dd 100644 --- a/collectors.lisp +++ b/collectors.lisp @@ -20,12 +20,15 @@ #:with-collector-output #:with-collectors #:make-collector + #:with-alist-output + #:collecting #:make-pusher #:with-reducer #:make-reducer #:with-appender #:with-appender-output #:make-appender + #:appending #:with-string-builder #:with-string-builder-output #:make-string-builder @@ -400,6 +403,39 @@ FUNCTION and INITIAL-VALUE are passed directly to MAKE-REDUCER." ,@body (,name))) +(defmacro with-alist ((name &key place (collect-nil T) initial-value from-end) &body body) + "Bind NAME to a collector function and execute BODY. If + FROM-END is true the collector will actually be a pusher, (see + MAKE-PUSHER), otherwise NAME will be bound to a collector, + (see MAKE-COLLECTOR). + (with-collector (col) + (col 1) + (col 2) + (col 3) + (col)) => (1 2 3) + " + `(let ((,name (,(if from-end + 'make-pusher + 'make-collector) + :initial-value (or ,initial-value ,place) + :collect-nil ,collect-nil + :place-setter ,(when place `(lambda (new) (setf ,place new)))))) + (flet ((,name (&rest items) + (loop for (k v) on items by #'cddr + do (operate ,name (cons k v))))) + ,@body))) + +(defmacro with-alist-output ((name &key (collect-nil t) initial-value from-end place) + &body body) + `(with-alist (,name :collect-nil ,collect-nil + :initial-value ,initial-value + :from-end ,from-end + :place ,place) + ,@body + (,name))) + + + (defmacro with-collectors (names &body body) "Bind multiple collectors. Each element of NAMES should be a list as per WITH-COLLECTOR's first orgument." @@ -600,6 +636,27 @@ This form returns the result of that formatter" (flet ((,name (&rest ,flet-args) (apply ,col ,flet-args))) ,@body))))) +(defmacro collecting ((arg list) &body body) + "A mapping collecting macro for operating on elements of a list + (similar to (mapcar (lambda (,arg) ,@body) list), but using a collector + so all signals are in place)" + `(with-collector-output (output) + (dolist (,arg (alexandria:ensure-list ,list)) + (restart-case + (output (progn ,@body)) + (skip () "Skip this element")) + ))) + +(defmacro appending ((arg list) &body body) + "A mapping collecting macro for operating on elements of a list + (similar to (mapcan (lambda (,arg) ,@body) list), but using a collector + so all signals are in place)" + `(with-appender-output (output) + (dolist (,arg (alexandria:ensure-list ,list)) + (restart-case + (output (progn ,@body)) + (skip () "Skip this element"))))) +