367 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Common Lisp
		
	
	
	
	
	
			
		
		
	
	
			367 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Common Lisp
		
	
	
	
	
	
| (in-package :alexandria)
 | |
| 
 | |
| (declaim (inline safe-endp))
 | |
| (defun safe-endp (x)
 | |
|   (declare (optimize safety))
 | |
|   (endp x))
 | |
| 
 | |
| (defun alist-plist (alist)
 | |
|   "Returns a property list containing the same keys and values as the
 | |
| association list ALIST in the same order."
 | |
|   (let (plist)
 | |
|     (dolist (pair alist)
 | |
|       (push (car pair) plist)
 | |
|       (push (cdr pair) plist))
 | |
|     (nreverse plist)))
 | |
| 
 | |
| (defun plist-alist (plist)
 | |
|   "Returns an association list containing the same keys and values as the
 | |
| property list PLIST in the same order."
 | |
|   (let (alist)
 | |
|     (do ((tail plist (cddr tail)))
 | |
|         ((safe-endp tail) (nreverse alist))
 | |
|       (push (cons (car tail) (cadr tail)) alist))))
 | |
| 
 | |
| (declaim (inline racons))
 | |
| (defun racons (key value ralist)
 | |
|   (acons value key ralist))
 | |
| 
 | |
| (macrolet
 | |
|     ((define-alist-get (name get-entry get-value-from-entry add doc)
 | |
|        `(progn
 | |
|           (declaim (inline ,name))
 | |
|           (defun ,name (alist key &key (test 'eql))
 | |
|             ,doc
 | |
|             (let ((entry (,get-entry key alist :test test)))
 | |
|               (values (,get-value-from-entry entry) entry)))
 | |
|           (define-setf-expander ,name (place key &key (test ''eql)
 | |
|                                        &environment env)
 | |
|             (multiple-value-bind
 | |
|                   (temporary-variables initforms newvals setter getter)
 | |
|                 (get-setf-expansion place env)
 | |
|               (when (cdr newvals)
 | |
|                 (error "~A cannot store multiple values in one place" ',name))
 | |
|               (with-unique-names (new-value key-val test-val alist entry)
 | |
|                 (values
 | |
|                  (append temporary-variables
 | |
|                          (list alist
 | |
|                                key-val
 | |
|                                test-val
 | |
|                                entry))
 | |
|                  (append initforms
 | |
|                          (list getter
 | |
|                                key
 | |
|                                test
 | |
|                                `(,',get-entry ,key-val ,alist :test ,test-val)))
 | |
|                  `(,new-value)
 | |
|                  `(cond
 | |
|                     (,entry
 | |
|                      (setf (,',get-value-from-entry ,entry) ,new-value))
 | |
|                     (t
 | |
|                      (let ,newvals
 | |
|                        (setf ,(first newvals) (,',add ,key ,new-value ,alist))
 | |
|                        ,setter
 | |
|                        ,new-value)))
 | |
|                  `(,',get-value-from-entry ,entry))))))))
 | |
|  (define-alist-get assoc-value assoc cdr acons
 | |
| "ASSOC-VALUE is an alist accessor very much like ASSOC, but it can
 | |
| be used with SETF.")
 | |
|  (define-alist-get rassoc-value rassoc car racons
 | |
| "RASSOC-VALUE is an alist accessor very much like RASSOC, but it can
 | |
| be used with SETF."))
 | |
| 
 | |
| (defun malformed-plist (plist)
 | |
|   (error "Malformed plist: ~S" plist))
 | |
| 
 | |
| (defmacro doplist ((key val plist &optional values) &body body)
 | |
|   "Iterates over elements of PLIST. BODY can be preceded by
 | |
| declarations, and is like a TAGBODY. RETURN may be used to terminate
 | |
| the iteration early. If RETURN is not used, returns VALUES."
 | |
|   (multiple-value-bind (forms declarations) (parse-body body)
 | |
|     (with-gensyms (tail loop results)
 | |
|       `(block nil
 | |
|          (flet ((,results ()
 | |
|                   (let (,key ,val)
 | |
|                     (declare (ignorable ,key ,val))
 | |
|                     (return ,values))))
 | |
|            (let* ((,tail ,plist)
 | |
|                   (,key (if ,tail
 | |
|                             (pop ,tail)
 | |
|                             (,results)))
 | |
|                  (,val (if ,tail
 | |
|                            (pop ,tail)
 | |
|                            (malformed-plist ',plist))))
 | |
|             (declare (ignorable ,key ,val))
 | |
|             ,@declarations
 | |
|             (tagbody
 | |
|                ,loop
 | |
|                ,@forms
 | |
|                (setf ,key (if ,tail
 | |
|                               (pop ,tail)
 | |
|                               (,results))
 | |
|                      ,val (if ,tail
 | |
|                               (pop ,tail)
 | |
|                               (malformed-plist ',plist)))
 | |
|                (go ,loop))))))))
 | |
| 
 | |
| (define-modify-macro appendf (&rest lists) append
 | |
|   "Modify-macro for APPEND. Appends LISTS to the place designated by the first
 | |
| argument.")
 | |
| 
 | |
| (define-modify-macro nconcf (&rest lists) nconc
 | |
|   "Modify-macro for NCONC. Concatenates LISTS to place designated by the first
 | |
| argument.")
 | |
| 
 | |
| (define-modify-macro unionf (list &rest args) union
 | |
|   "Modify-macro for UNION. Saves the union of LIST and the contents of the
 | |
| place designated by the first argument to the designated place.")
 | |
| 
 | |
| (define-modify-macro nunionf (list &rest args) nunion
 | |
|   "Modify-macro for NUNION. Saves the union of LIST and the contents of the
 | |
| place designated by the first argument to the designated place. May modify
 | |
| either argument.")
 | |
| 
 | |
| (define-modify-macro reversef () reverse
 | |
|   "Modify-macro for REVERSE. Copies and reverses the list stored in the given
 | |
| place and saves back the result into the place.")
 | |
| 
 | |
| (define-modify-macro nreversef () nreverse
 | |
|   "Modify-macro for NREVERSE. Reverses the list stored in the given place by
 | |
| destructively modifying it and saves back the result into the place.")
 | |
| 
 | |
| (defun circular-list (&rest elements)
 | |
|   "Creates a circular list of ELEMENTS."
 | |
|   (let ((cycle (copy-list elements)))
 | |
|     (nconc cycle cycle)))
 | |
| 
 | |
| (defun circular-list-p (object)
 | |
|   "Returns true if OBJECT is a circular list, NIL otherwise."
 | |
|   (and (listp object)
 | |
|        (do ((fast object (cddr fast))
 | |
|             (slow (cons (car object) (cdr object)) (cdr slow)))
 | |
|            (nil)
 | |
|          (unless (and (consp fast) (listp (cdr fast)))
 | |
|            (return nil))
 | |
|          (when (eq fast slow)
 | |
|            (return t)))))
 | |
| 
 | |
| (defun circular-tree-p (object)
 | |
|   "Returns true if OBJECT is a circular tree, NIL otherwise."
 | |
|   (labels ((circularp (object seen)
 | |
|              (and (consp object)
 | |
|                   (do ((fast (cons (car object) (cdr object)) (cddr fast))
 | |
|                        (slow object (cdr slow)))
 | |
|                       (nil)
 | |
|                     (when (or (eq fast slow) (member slow seen))
 | |
|                       (return-from circular-tree-p t))
 | |
|                     (when (or (not (consp fast)) (not (consp (cdr slow))))
 | |
|                       (return
 | |
|                         (do ((tail object (cdr tail)))
 | |
|                             ((not (consp tail))
 | |
|                              nil)
 | |
|                           (let ((elt (car tail)))
 | |
|                             (circularp elt (cons object seen))))))))))
 | |
|     (circularp object nil)))
 | |
| 
 | |
| (defun proper-list-p (object)
 | |
|   "Returns true if OBJECT is a proper list."
 | |
|   (cond ((not object)
 | |
|          t)
 | |
|         ((consp object)
 | |
|          (do ((fast object (cddr fast))
 | |
|               (slow (cons (car object) (cdr object)) (cdr slow)))
 | |
|              (nil)
 | |
|            (unless (and (listp fast) (consp (cdr fast)))
 | |
|              (return (and (listp fast) (not (cdr fast)))))
 | |
|            (when (eq fast slow)
 | |
|              (return nil))))
 | |
|         (t
 | |
|          nil)))
 | |
| 
 | |
| (deftype proper-list ()
 | |
|   "Type designator for proper lists. Implemented as a SATISFIES type, hence
 | |
| not recommended for performance intensive use. Main usefullness as a type
 | |
| designator of the expected type in a TYPE-ERROR."
 | |
|   `(and list (satisfies proper-list-p)))
 | |
| 
 | |
| (defun circular-list-error (list)
 | |
|   (error 'type-error
 | |
|          :datum list
 | |
|          :expected-type '(and list (not circular-list))))
 | |
| 
 | |
| (macrolet ((def (name lambda-list doc step declare ret1 ret2)
 | |
|              (assert (member 'list lambda-list))
 | |
|              `(defun ,name ,lambda-list
 | |
|                 ,doc
 | |
|                 (do ((last list fast)
 | |
|                      (fast list (cddr fast))
 | |
|                      (slow (cons (car list) (cdr list)) (cdr slow))
 | |
|                      ,@(when step (list step)))
 | |
|                     (nil)
 | |
|                   (declare (dynamic-extent slow) ,@(when declare (list declare))
 | |
|                            (ignorable last))
 | |
|                   (when (safe-endp fast)
 | |
|                     (return ,ret1))
 | |
|                   (when (safe-endp (cdr fast))
 | |
|                     (return ,ret2))
 | |
|                   (when (eq fast slow)
 | |
|                     (circular-list-error list))))))
 | |
|   (def proper-list-length (list)
 | |
|     "Returns length of LIST, signalling an error if it is not a proper list."
 | |
|     (n 1 (+ n 2))
 | |
|     ;; KLUDGE: Most implementations don't actually support lists with bignum
 | |
|     ;; elements -- and this is WAY faster on most implementations then declaring
 | |
|     ;; N to be an UNSIGNED-BYTE.
 | |
|     (fixnum n)
 | |
|     (1- n)
 | |
|     n)
 | |
| 
 | |
|   (def lastcar (list)
 | |
|       "Returns the last element of LIST. Signals a type-error if LIST is not a
 | |
| proper list."
 | |
|     nil
 | |
|     nil
 | |
|     (cadr last)
 | |
|     (car fast))
 | |
| 
 | |
|   (def (setf lastcar) (object list)
 | |
|       "Sets the last element of LIST. Signals a type-error if LIST is not a proper
 | |
| list."
 | |
|     nil
 | |
|     nil
 | |
|     (setf (cadr last) object)
 | |
|     (setf (car fast) object)))
 | |
| 
 | |
| (defun make-circular-list (length &key initial-element)
 | |
|   "Creates a circular list of LENGTH with the given INITIAL-ELEMENT."
 | |
|   (let ((cycle (make-list length :initial-element initial-element)))
 | |
|     (nconc cycle cycle)))
 | |
| 
 | |
| (deftype circular-list ()
 | |
|   "Type designator for circular lists. Implemented as a SATISFIES type, so not
 | |
| recommended for performance intensive use. Main usefullness as the
 | |
| expected-type designator of a TYPE-ERROR."
 | |
|   `(satisfies circular-list-p))
 | |
| 
 | |
| (defun ensure-car (thing)
 | |
|   "If THING is a CONS, its CAR is returned. Otherwise THING is returned."
 | |
|   (if (consp thing)
 | |
|       (car thing)
 | |
|       thing))
 | |
| 
 | |
| (defun ensure-cons (cons)
 | |
|   "If CONS is a cons, it is returned. Otherwise returns a fresh cons with CONS
 | |
|   in the car, and NIL in the cdr."
 | |
|   (if (consp cons)
 | |
|       cons
 | |
|       (cons cons nil)))
 | |
| 
 | |
| (defun ensure-list (list)
 | |
|   "If LIST is a list, it is returned. Otherwise returns the list designated by LIST."
 | |
|   (if (listp list)
 | |
|       list
 | |
|       (list list)))
 | |
| 
 | |
| (defun remove-from-plist (plist &rest keys)
 | |
|   "Returns a propery-list with same keys and values as PLIST, except that keys
 | |
| in the list designated by KEYS and values corresponding to them are removed.
 | |
| The returned property-list may share structure with the PLIST, but PLIST is
 | |
| not destructively modified. Keys are compared using EQ."
 | |
|   (declare (optimize (speed 3)))
 | |
|   ;; FIXME: possible optimization: (remove-from-plist '(:x 0 :a 1 :b 2) :a)
 | |
|   ;; could return the tail without consing up a new list.
 | |
|   (loop for (key . rest) on plist by #'cddr
 | |
|         do (assert rest () "Expected a proper plist, got ~S" plist)
 | |
|         unless (member key keys :test #'eq)
 | |
|         collect key and collect (first rest)))
 | |
| 
 | |
| (defun delete-from-plist (plist &rest keys)
 | |
|   "Just like REMOVE-FROM-PLIST, but this version may destructively modify the
 | |
| provided PLIST."
 | |
|   (declare (optimize speed))
 | |
|   (loop with head = plist
 | |
|         with tail = nil   ; a nil tail means an empty result so far
 | |
|         for (key . rest) on plist by #'cddr
 | |
|         do (assert rest () "Expected a proper plist, got ~S" plist)
 | |
|            (if (member key keys :test #'eq)
 | |
|                ;; skip over this pair
 | |
|                (let ((next (cdr rest)))
 | |
|                  (if tail
 | |
|                      (setf (cdr tail) next)
 | |
|                      (setf head next)))
 | |
|                ;; keep this pair
 | |
|                (setf tail rest))
 | |
|         finally (return head)))
 | |
| 
 | |
| (define-modify-macro remove-from-plistf (&rest keys) remove-from-plist
 | |
|                      "Modify macro for REMOVE-FROM-PLIST.")
 | |
| (define-modify-macro delete-from-plistf (&rest keys) delete-from-plist
 | |
|                      "Modify macro for DELETE-FROM-PLIST.")
 | |
| 
 | |
| (declaim (inline sans))
 | |
| (defun sans (plist &rest keys)
 | |
|   "Alias of REMOVE-FROM-PLIST for backward compatibility."
 | |
|   (apply #'remove-from-plist plist keys))
 | |
| 
 | |
| (defun mappend (function &rest lists)
 | |
|   "Applies FUNCTION to respective element(s) of each LIST, appending all the
 | |
| all the result list to a single list. FUNCTION must return a list."
 | |
|   (loop for results in (apply #'mapcar function lists)
 | |
|         append results))
 | |
| 
 | |
| (defun setp (object &key (test #'eql) (key #'identity))
 | |
|   "Returns true if OBJECT is a list that denotes a set, NIL otherwise. A list
 | |
| denotes a set if each element of the list is unique under KEY and TEST."
 | |
|   (and (listp object)
 | |
|        (let (seen)
 | |
|          (dolist (elt object t)
 | |
|            (let ((key (funcall key elt)))
 | |
|              (if (member key seen :test test)
 | |
|                  (return nil)
 | |
|                  (push key seen)))))))
 | |
| 
 | |
| (defun set-equal (list1 list2 &key (test #'eql) (key nil keyp))
 | |
|   "Returns true if every element of LIST1 matches some element of LIST2 and
 | |
| every element of LIST2 matches some element of LIST1. Otherwise returns false."
 | |
|   (let ((keylist1 (if keyp (mapcar key list1) list1))
 | |
|         (keylist2 (if keyp (mapcar key list2) list2)))
 | |
|     (and (dolist (elt keylist1 t)
 | |
|            (or (member elt keylist2 :test test)
 | |
|                (return nil)))
 | |
|          (dolist (elt keylist2 t)
 | |
|            (or (member elt keylist1 :test test)
 | |
|                (return nil))))))
 | |
| 
 | |
| (defun map-product (function list &rest more-lists)
 | |
|   "Returns a list containing the results of calling FUNCTION with one argument
 | |
| from LIST, and one from each of MORE-LISTS for each combination of arguments.
 | |
| In other words, returns the product of LIST and MORE-LISTS using FUNCTION.
 | |
| 
 | |
| Example:
 | |
| 
 | |
|  (map-product 'list '(1 2) '(3 4) '(5 6))
 | |
|   => ((1 3 5) (1 3 6) (1 4 5) (1 4 6)
 | |
|       (2 3 5) (2 3 6) (2 4 5) (2 4 6))
 | |
| "
 | |
|   (labels ((%map-product (f lists)
 | |
|              (let ((more (cdr lists))
 | |
|                    (one (car lists)))
 | |
|                (if (not more)
 | |
|                    (mapcar f one)
 | |
|                    (mappend (lambda (x)
 | |
|                               (%map-product (curry f x) more))
 | |
|                             one)))))
 | |
|     (%map-product (ensure-function function) (cons list more-lists))))
 | |
| 
 | |
| (defun flatten (tree)
 | |
|   "Traverses the tree in order, collecting non-null leaves into a list."
 | |
|   (let (list)
 | |
|     (labels ((traverse (subtree)
 | |
|                (when subtree
 | |
|                  (if (consp subtree)
 | |
|                      (progn
 | |
|                        (traverse (car subtree))
 | |
|                        (traverse (cdr subtree)))
 | |
|                      (push subtree list)))))
 | |
|       (traverse tree))
 | |
|     (nreverse list)))
 |