git.fiddlerwoaroof.com
fwoar-lisputils.lisp
cc8390ee
 (in-package #:fwoar.lisputils)
 
012d021d
 (defmacro neither (&rest forms)
   `(not (or ,@forms)))
07f6eaf7
 
 (defmacro neither-null (&rest forms)
   `(neither ,@(loop for form
012d021d
                       in forms
                     collecting `(null ,form))))
07f6eaf7
 
 
c0730f1a
 (defmacro let-each ((&key (be '*)) &body forms)
   "Bind each element successively to the symbol specified via :be"
   `(let* ,(loop for form in forms
012d021d
                 collect (list be form))
c0730f1a
      ,be))
 
9c529e1a
 (defmacro let-first ((&key (be '*)) bound &body forms)
c0730f1a
   "Bind the result of the first form to the symbol specified via :be"
9c529e1a
   `(let* ((,be ,bound))
      ,@forms
c0730f1a
      ,be))
 
 (defmacro let-second ((&key (be '*)) &body forms)
   "Bind the result of the second form to the symbol specified via :be"
   `(progn
      ,(car forms)
      (let* ((,be ,(cadr forms)))
9c529e1a
        ,@(cddr forms)
c0730f1a
        ,be)))
 
cc8390ee
 (defmacro lambda-if ((test &rest args) &body body)
   "Make a lambda that wraps an call to if"
   `(lambda ,args
      (if (,test ,@args)
e7c6d164
          ,@body)))
cc8390ee
 
 (defmacro lambda-cond ((&rest args) &body body)
   "Make a lambda that wraps an call to cond"
   `(lambda ,args
      (cond
        ,@body)))
 
 (defmacro alambda (&body body)
c4b8ada4
   `(lambda (anaphora:it)
      (declare (ignorable anaphora:it))
cc8390ee
      ,@body))
 
e5349999
 (eval-when (:compile-toplevel :load-toplevel :execute)
e143b109
   (defun map-cons (cb cons)
     (cond
       ((null cons) '())
       ((consp (cdr cons)) (cons (funcall cb (car cons))
e7c6d164
                                 (map-cons cb (cdr cons))))
e143b109
       (t (list (funcall cb (car cons))
e7c6d164
                (funcall cb (cdr cons))))))
e143b109
 
   (defun generate-declarations-for (sym ignored ignorable)
     (let ((ignores (list))
e7c6d164
           (ignorables (list)))
       (map-cons (lambda (_1)
                   (cond ((member _1 ignorable)
                          (push _1 ignorables))
                         ((member _1 ignored)
                          (push _1 ignores))))
                 (alexandria:ensure-cons sym))
e143b109
       (if (or ignores ignorables)
e7c6d164
           `((declare
              ,@(when ignores
                  `((ignore ,@ignores)))
              ,@(when ignorables
                  `((ignorable ,@ignorables)))))
           '())))
e143b109
 
   (defun find-ignored-vars (body)
     (let ((possible-declarations (car body))
e7c6d164
           (ignored-vars nil)
           (ignorable-vars nil))
334428a3
       (if (and (consp possible-declarations)
e7c6d164
                (eq (car possible-declarations) 'declare)
                (consp (cadr possible-declarations)))
           (let* ((declarations (cdr possible-declarations)))
             (setf ignored-vars (cdr (assoc 'ignore declarations))
                   ignorable-vars (cdr (assoc 'ignorable declarations))
                   body (cdr body))))
e143b109
       (values ignored-vars
e7c6d164
               ignorable-vars
               body)))
e143b109
 
555da269
   (defun ensure-mapping (list &optional (key-fn 'identity))
cc8390ee
     "Take a list and make sure that it's parseable as a let-style binding.
      Very handy for certain sorts of macros."
     (let ((symbols->mappings (lambda-cond (x)
555da269
                                ((symbolp x) `(,(funcall key-fn x) ,x))
                                ((null (cdr x)) `(,(funcall key-fn #1=(car x)) ,#1#))
e7c6d164
                                (t x))))
cc8390ee
       (mapcar symbols->mappings list)))
 
e5349999
 
e143b109
   (defun rollup-list (list &optional body)
     (labels ((helper (list &optional accum start)
e7c6d164
                (tagbody
                 start
                   (cond
                     ((endp list) (return-from rollup-list accum))
                     (t (psetf accum  (cond
                                        ((null accum) (car list))
555da269
                                        (start `(,@(car list) ,@accum))
e7c6d164
                                        (t `(,@(car list) ,accum)))
                               list (cdr list)
                               start nil)
                        ;; NOTE: REMEMBER! This call to #'GO is the "tail call"
                        (go start))))))
e143b109
       (helper (reverse list) body t))))
cc8390ee
 
07f6eaf7
 (defmacro m-lambda (sym &rest args)
   (let ((arglist (loop for x in args
012d021d
                        unless (member x (list '&optional '&key '&rest))
                          collect (ctypecase x
                                    (cons                  (car x))
                                    ((or symbol keyword string) x)))))
07f6eaf7
     `(lambda (,@args)
        (,sym ,@arglist))))
 
0e15cea0
 (defun get-ignored-vars (body)
   (let ((declarations (cdr (assoc 'declare body))))
     (values (cdr (assoc 'ignore declarations))
             (cdr (assoc 'ignorable declarations)))))
 
cc8390ee
 (defmacro destructuring-lambda ((&rest args) &body body)
   "A lambda whose arguments can be lambda-lists to be destructured"
e143b109
   (multiple-value-bind (ignored ignorable body) (find-ignored-vars body)
     (let* ((args-syms (mapcar (lambda (_) (declare (ignore _)) (gensym "arg"))
e7c6d164
                               args))
            (args (mapcar #'list args args-syms))
            (destructuring-expressions
012d021d
              (rollup-list
               (loop for (arg arg-sym) in args
                     collect (if (consp arg)
                                 `(destructuring-bind ,arg ,arg-sym
                                    (declare (ignore ,@ignored) (ignorable ,@ignorable)))
                                 `(let ((,arg ,arg-sym))
                                    ,@(generate-declarations-for arg ignored ignorable))))
               body)))
e143b109
       `(lambda ,args-syms
e7c6d164
          ,destructuring-expressions))))
cc8390ee
 
e5349999
 
 ;;; CASES:::
 #|
43b7e4e9
 ;; (fw.lu::destructuring-lambda ((slot slot-keyword . r))
 ;;   (make-slot-spec slot slot-keyword))
555da269
 ;;
43b7e4e9
 ;; (fw.lu::destructuring-lambda ((slot slot-keyword . r))
 ;;   (declare (ignore r))
 ;;   (make-slot-spec slot slot-keyword))
555da269
 ;;
43b7e4e9
 ;; (fw.lu::destructuring-lambda ((slot slot-keyword . r) b c)
 ;;   (make-slot-spec slot slot-keyword))
555da269
 ;;
43b7e4e9
 ;; (fw.lu::destructuring-lambda ((slot slot-keyword . r) b)
 ;;   (make-slot-spec slot slot-keyword))
555da269
 ;;
43b7e4e9
 ;; (fw.lu::destructuring-lambda ((slot slot-keyword . r) b)
 ;;   (declare (ignore r))
 ;;   (make-slot-spec slot slot-keyword))
e5349999
 |#
 
cc8390ee
 (defun alist-string-hash-table (alist)
   "Make a hash table suitable for strings and other non-eql types
    from an association list"
   (alexandria:alist-hash-table alist :test #'equal))
 
 (defmacro copy-slots (slots from to)
   "Given a list of slots specified as let-style bindings, copy them
    from one object to another."
0e15cea0
   (alexandria:once-only (from to)
cc8390ee
     `(progn
aabf976a
        (setf ,@(apply #'append
012d021d
                       (iterate:iterate
                         (iterate:for (fro-slot to-slot) iterate:in (ensure-mapping slots))
                         (iterate:collect `((slot-value ,to ',to-slot) (slot-value ,from ',fro-slot))))))
cc8390ee
        ,to)))
 
 (defun transform-alist (function alist)
   "Run down an alist, applying a given function to each element"
   (mapcar (destructuring-lambda ((k . v)) (funcall function k v))
           alist))
 
 (defun %json-pair-transform (k v)
   "Ugly hack to make jonathan work correctly with string values.
    TODO: move this elsewhere"
0e15cea0
   (cons (alexandria:make-keyword (string-downcase k))
cc8390ee
         (typecase v
           (string (coerce v 'simple-string))
           (t v))))
 
 (defun %default-pair-transform (k v)
0e15cea0
   (cons (alexandria:make-keyword (string-upcase k)) v))
cc8390ee
 
0b31a5b5
 (defun find-nonoperator-symbols (form)
   (alexandria:flatten
    (remove-duplicates
     (typecase form
       (symbol (list form))
       (cons (append
              (when (consp (car form))
                (find-nonoperator-symbols (car form)))
              (typecase (cdr form)
                (symbol (list (cdr form)))
                (cons (loop for thing in (cdr form)
012d021d
                            append (find-nonoperator-symbols thing))))))))))
0b31a5b5
 
8ef95ab1
 (defmacro may ((op arg &rest r))
   (let ((cond (case op
                 (cl:funcall (car r))
                 (t arg))))
     (alexandria:once-only (arg)
       `(when ,cond
          (,op ,arg ,@r)))))
0b31a5b5
 
cc8390ee
 (defmacro default-when (default test &body body)
222e6786
   "return the default unless the test is true"
   (warn "default-when is deprecated, renamed to default-unless")
0e15cea0
   (alexandria:once-only (default)
222e6786
     `(or (when ,test
            ,@body)
          ,default)))
 
 (defmacro default-unless (default test &body body)
cc8390ee
   "return the default unless the test is true"
0e15cea0
   (alexandria:once-only (default)
cc8390ee
     `(or (when ,test
            ,@body)
          ,default)))
 
 (defmacro transform-result ((list-transform &optional (pair-transform #'identity)) &rest alist)
   "Transform an alist that results from some operation as a whole and, optionally, apply a
    transformation to each key-value pair."
   `(funcall ,list-transform (transform-alist ,pair-transform ,@alist)))
 
 (defun make-pairs (symbols)
e7c6d164
                                         ;TODO: does this duplicate ensure-mapping?
cc8390ee
   (cons 'list
012d021d
         (iterate:iterate
           (iterate:for (key value) in symbols)
           (iterate:collect `(list* ,(symbol-name key) ,value)))))
cc8390ee
 
41cbd2ef
 (defun inits (l)
   (serapeum:with-collector (c)
     (let ((its ()))
       (mapc (lambda (it)
               (push it its)
               (c (reverse its)))
             l))))
 
c8375f9b
 (defmacro closing ((op &rest args))
   (let ((stream-sym (gensym "STRING")))
     `(with-open-stream (,stream-sym ,(first args))
        (,op ,stream-sym ,@(cdr args)))))
 #+fw.ignore
 (progn
   (closing (read-line (open "/foo/bar"
                             :element-type '(unsigned-byte 8))
                       nil
                       :the-end))
   )
 
cc8390ee
 (defmacro slots-to-pairs (obj (&rest slots))
0e15cea0
   (declare (optimize (debug 3)))
cc8390ee
   "Produce a alist from a set of object slots and their values"
0e15cea0
   (alexandria:once-only (obj)
cc8390ee
     (let* ((slots (ensure-mapping slots))
012d021d
            (bindings (iterate:iterate
                        (iterate:for (slot v &key bind-from) in slots)
                        (iterate:collect (or bind-from slot)))))
cc8390ee
       `(with-slots ,bindings ,obj
          ,(make-pairs slots)))))
 
a16eb0ec
 (defmacro setfs (&body body)
   "Make setf a bit nicer to use with paredit"
   (list* 'setf (apply #'append body)))
 
e143b109
 (defmacro prog2-let (first-form (&rest result-binding) &body body)
   "Execute a form, make a bunch of bindings and retern the bound values via prog1 after executing body"
   `(progn ,first-form
e7c6d164
           (let (,@result-binding)
             (prog1 (list ,@(mapcar #'car result-binding))
               ,@body))))
e143b109
 
0052b5eb
 ;; TODO: use multiple values . . .
21620473
 (defmacro prog1-let ((&rest result-binding) &body body)
e143b109
   "Bind a bunch of symbols to values and return them via prog1"
21620473
   `(let (,@result-binding)
1837a009
      (multiple-value-prog1 (values ,@(mapcar #'car result-binding))
21620473
        ,@body)))
 
e4551d8e
 (defmacro prog1-bind ((var val) &body body)
   `(let ((,var ,val))
      (prog1 ,var
        ,@body)))
 
e143b109
 (defmacro if-let* ((&rest bindings) &body (then-form &optional else-form))
   "Like if-let, but sets bindings sequentially.  Doesn't short-circuit."
   `(let* ,bindings
      (if (and ,@(mapcar #'car bindings))
e7c6d164
          ,then-form
          ,else-form)))
e143b109
 
 (defmacro with ((var val) &body body)
   "A stripped down let for binding a single name"
   `(let ((,var ,val))
      ,@body))
 
0e15cea0
 (defun do-acons (alist key datum)
   (acons key datum alist))
 (define-modify-macro aconsf (key datum) do-acons)
e143b109
 
afe39782
 
 (defun do-adjoin (list item &rest r &key key test test-not)
   (declare (ignore key test test-not))
   (apply #'adjoin item list r))
 (define-compiler-macro do-adjoin (list item &rest r)
   (alexandria:once-only (list item)
     `(adjoin ,item ,list ,@r)))
 (define-modify-macro adjoinf (item &rest r)
   do-adjoin)
 
455afb29
 ;;(defun ensure-list (val)
 ;;  (typecase val
 ;;    (list val)
 ;;    (t (list val))))
a16eb0ec
 
40f16faf
 
 (defmacro defun-ct (name (&rest args) &body body)
   `(eval-when (:load-toplevel :compile-toplevel :execute)
      (defun ,name ,args
        ,@body)))
efb4c8a7
 
 (defmacro retry-once (&body body)
   (alexandria:with-gensyms (flag)
     `(let ((,flag t))
        (tagbody
         start
           ,@body
           (when ,flag
             (setf ,flag nil)
             (go start))))))