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)
1611b5b8
   `(lambda (it)
      (declare (ignorable 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
 
fffb1074
 (defmacro str->stream ((op arg &rest r))
   (let ((string (case op
                   (cl:funcall (car r))
                   (t arg))))
     (alexandria:once-only (arg)
       (alexandria:with-gensyms (s)
         `(with-input-from-string (,s ,arg)
            (,op ,s ,@r))))))
 
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
 
114e9cc5
 (defmacro retry-once ((flag-sym) &body body)
   `(let ((,flag-sym t))
1611b5b8
      (tagbody
       start
         ,@body
114e9cc5
         (when ,flag-sym
           (setf ,flag-sym nil)
           (go start)))))
8decda77
 
 (defun split-at (el list &key (test #'eql) (key nil))
   (if key
       (loop for it on list
             until (funcall test (funcall key (car it)) el)
             collect (car it) into head
             finally (return (values head it)))
       (loop for it on list
             until (funcall test (car it) el)
             collect (car it) into head
             finally (return (values head it)))))