git.fiddlerwoaroof.com
Raw Blame History
;;;; fwoar.lisputils.lisp

(in-package #:fwoar.lisputils)

(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 ensure-mapping (list)
    "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) `(,x ,x))
                               ((null (cdr x)) `(,#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 destructuring-lambda ((&rest args) &body body)
  "A lambda whose arguments can be lambda-lists to be destructured"
  (let* ((args-syms (mapcar (alambda (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)
                               `(let ((,arg ,arg-sym)))))
               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."
  (once-only (from to)
    `(progn
       (setf ,@(apply #'append
                      (iterate (for (fro-slot to-slot) in (ensure-mapping slots))
                               (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 (make-keyword (string-downcase k))
        (typecase v
          (string (coerce v 'simple-string))
          (t v))))

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

(defmacro default-when (default test &body body)
  "return the default unless the test is true"
  (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 (for (key value) in symbols)
                 (collect `(list* ,(symbol-name key) ,value)))))

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

(defun normalize-html (html)
  "Convert possibly bad HTML to sane HTML as best as possible."
  (let ((plump:*tag-dispatchers* plump:*html-tags*))
    (with-output-to-string (ss)
      (prog1 ss
        (map 'vector
           (lambda (x) (plump:serialize (plump:parse (plump:text x)) ss))
           html)))))