git.fiddlerwoaroof.com
Raw Blame History
(in-package #:fwoar.lisputils)

(defmacro neither (&rest forms)
  `(not (or ,@forms)))

(defmacro neither-null (&rest forms)
  `(neither ,@(loop for form
                      in forms
                    collecting `(null ,form))))


(defmacro let-each ((&key (be '*)) &body forms)
  "Bind each element successively to the symbol specified via :be"
  `(let* ,(loop for form in forms
                collect (list be form))
     ,be))

(defmacro let-first ((&key (be '*)) bound &body forms)
  "Bind the result of the first form to the symbol specified via :be"
  `(let* ((,be ,bound))
     ,@forms
     ,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)))
       ,@(cddr forms)
       ,be)))

(defmacro lambda-if ((test &rest args) &body body)
  "Make a lambda that wraps an call to if"
  `(lambda ,args
     (if (,test ,@args)
         ,@body)))

(defmacro lambda-cond ((&rest args) &body body)
  "Make a lambda that wraps an call to cond"
  `(lambda ,args
     (cond
       ,@body)))

(defmacro alambda (&body body)
  `(lambda (anaphora:it)
     (declare (ignorable anaphora:it))
     ,@body))

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun map-cons (cb cons)
    (cond
      ((null cons) '())
      ((consp (cdr cons)) (cons (funcall cb (car cons))
                                (map-cons cb (cdr cons))))
      (t (list (funcall cb (car cons))
               (funcall cb (cdr cons))))))

  (defun generate-declarations-for (sym ignored ignorable)
    (let ((ignores (list))
          (ignorables (list)))
      (map-cons (lambda (_1)
                  (cond ((member _1 ignorable)
                         (push _1 ignorables))
                        ((member _1 ignored)
                         (push _1 ignores))))
                (alexandria:ensure-cons sym))
      (if (or ignores ignorables)
          `((declare
             ,@(when ignores
                 `((ignore ,@ignores)))
             ,@(when ignorables
                 `((ignorable ,@ignorables)))))
          '())))

  (defun find-ignored-vars (body)
    (let ((possible-declarations (car body))
          (ignored-vars nil)
          (ignorable-vars nil))
      (if (and (consp possible-declarations)
               (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))))
      (values ignored-vars
              ignorable-vars
              body)))

  (defun ensure-mapping (list &optional (key-fn 'identity))
    "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)
                               ((symbolp x) `(,(funcall key-fn x) ,x))
                               ((null (cdr x)) `(,(funcall key-fn #1=(car x)) ,#1#))
                               (t x))))
      (mapcar symbols->mappings list)))


  (defun rollup-list (list &optional body)
    (labels ((helper (list &optional accum start)
               (tagbody
                start
                  (cond
                    ((endp list) (return-from rollup-list accum))
                    (t (psetf accum  (cond
                                       ((null accum) (car list))
                                       (start `(,@(car list) ,@accum))
                                       (t `(,@(car list) ,accum)))
                              list (cdr list)
                              start nil)
                       ;; NOTE: REMEMBER! This call to #'GO is the "tail call"
                       (go start))))))
      (helper (reverse list) body t))))

(defmacro m-lambda (sym &rest args)
  (let ((arglist (loop for x in args
                       unless (member x (list '&optional '&key '&rest))
                         collect (ctypecase x
                                   (cons                  (car x))
                                   ((or symbol keyword string) x)))))
    `(lambda (,@args)
       (,sym ,@arglist))))

(defun get-ignored-vars (body)
  (let ((declarations (cdr (assoc 'declare body))))
    (values (cdr (assoc 'ignore declarations))
            (cdr (assoc 'ignorable declarations)))))

(defmacro destructuring-lambda ((&rest args) &body body)
  "A lambda whose arguments can be lambda-lists to be destructured"
  (multiple-value-bind (ignored ignorable body) (find-ignored-vars body)
    (let* ((args-syms (mapcar (lambda (_) (declare (ignore _)) (gensym "arg"))
                              args))
           (args (mapcar #'list args args-syms))
           (destructuring-expressions
             (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)))
      `(lambda ,args-syms
         ,destructuring-expressions))))


;;; CASES:::
#|
;; (fw.lu::destructuring-lambda ((slot slot-keyword . r))
;;   (make-slot-spec slot slot-keyword))
;;
;; (fw.lu::destructuring-lambda ((slot slot-keyword . r))
;;   (declare (ignore r))
;;   (make-slot-spec slot slot-keyword))
;;
;; (fw.lu::destructuring-lambda ((slot slot-keyword . r) b c)
;;   (make-slot-spec slot slot-keyword))
;;
;; (fw.lu::destructuring-lambda ((slot slot-keyword . r) b)
;;   (make-slot-spec slot slot-keyword))
;;
;; (fw.lu::destructuring-lambda ((slot slot-keyword . r) b)
;;   (declare (ignore r))
;;   (make-slot-spec slot slot-keyword))
|#

(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."
  (alexandria:once-only (from to)
    `(progn
       (setf ,@(apply #'append
                      (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))))))
       ,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"
  (cons (alexandria:make-keyword (string-downcase k))
        (typecase v
          (string (coerce v 'simple-string))
          (t v))))

(defun %default-pair-transform (k v)
  (cons (alexandria:make-keyword (string-upcase k)) v))

(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)
                           append (find-nonoperator-symbols thing))))))))))

(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)))))

(defmacro default-when (default test &body body)
  "return the default unless the test is true"
  (warn "default-when is deprecated, renamed to default-unless")
  (alexandria:once-only (default)
    `(or (when ,test
           ,@body)
         ,default)))

(defmacro default-unless (default test &body body)
  "return the default unless the test is true"
  (alexandria:once-only (default)
    `(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)
                                        ;TODO: does this duplicate ensure-mapping?
  (cons 'list
        (iterate:iterate
          (iterate:for (key value) in symbols)
          (iterate:collect `(list* ,(symbol-name key) ,value)))))

(defun inits (l)
  (serapeum:with-collector (c)
    (let ((its ()))
      (mapc (lambda (it)
              (push it its)
              (c (reverse its)))
            l))))

(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))
  )

(defmacro slots-to-pairs (obj (&rest slots))
  (declare (optimize (debug 3)))
  "Produce a alist from a set of object slots and their values"
  (alexandria:once-only (obj)
    (let* ((slots (ensure-mapping slots))
           (bindings (iterate:iterate
                       (iterate:for (slot v &key bind-from) in slots)
                       (iterate:collect (or bind-from slot)))))
      `(with-slots ,bindings ,obj
         ,(make-pairs slots)))))

(defmacro setfs (&body body)
  "Make setf a bit nicer to use with paredit"
  (list* 'setf (apply #'append body)))

(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
          (let (,@result-binding)
            (prog1 (list ,@(mapcar #'car result-binding))
              ,@body))))

;; TODO: use multiple values . . .
(defmacro prog1-let ((&rest result-binding) &body body)
  "Bind a bunch of symbols to values and return them via prog1"
  `(let (,@result-binding)
     (multiple-value-prog1 (values ,@(mapcar #'car result-binding))
       ,@body)))

(defmacro prog1-bind ((var val) &body body)
  `(let ((,var ,val))
     (prog1 ,var
       ,@body)))

(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))
         ,then-form
         ,else-form)))

(defmacro with ((var val) &body body)
  "A stripped down let for binding a single name"
  `(let ((,var ,val))
     ,@body))

(defun do-acons (alist key datum)
  (acons key datum alist))
(define-modify-macro aconsf (key datum) do-acons)


(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)

;;(defun ensure-list (val)
;;  (typecase val
;;    (list val)
;;    (t (list val))))


(defmacro defun-ct (name (&rest args) &body body)
  `(eval-when (:load-toplevel :compile-toplevel :execute)
     (defun ,name ,args
       ,@body)))

(defmacro retry-once (&body body)
  (alexandria:with-gensyms (flag)
    `(let ((,flag t))
       (tagbody
        start
          ,@body
          (when ,flag
            (setf ,flag nil)
            (go start))))))