106 lines
		
	
	
	
		
			5 KiB
		
	
	
	
		
			Common Lisp
		
	
	
	
	
	
			
		
		
	
	
			106 lines
		
	
	
	
		
			5 KiB
		
	
	
	
		
			Common Lisp
		
	
	
	
	
	
(in-package :alexandria)
 | 
						|
 | 
						|
(defun extract-function-name (spec)
 | 
						|
  "Useful for macros that want to mimic the functional interface for functions
 | 
						|
like #'eq and 'eq."
 | 
						|
  (if (and (consp spec)
 | 
						|
           (member (first spec) '(quote function)))
 | 
						|
      (second spec)
 | 
						|
      spec))
 | 
						|
 | 
						|
(defun generate-switch-body (whole object clauses test key &optional default)
 | 
						|
  (with-gensyms (value)
 | 
						|
    (setf test (extract-function-name test))
 | 
						|
    (setf key (extract-function-name key))
 | 
						|
    (when (and (consp default)
 | 
						|
               (member (first default) '(error cerror)))
 | 
						|
      (setf default `(,@default "No keys match in SWITCH. Testing against ~S with ~S."
 | 
						|
                      ,value ',test)))
 | 
						|
    `(let ((,value (,key ,object)))
 | 
						|
      (cond ,@(mapcar (lambda (clause)
 | 
						|
                        (if (member (first clause) '(t otherwise))
 | 
						|
                            (progn
 | 
						|
                              (when default
 | 
						|
                                (error "Multiple default clauses or illegal use of a default clause in ~S."
 | 
						|
                                       whole))
 | 
						|
                              (setf default `(progn ,@(rest clause)))
 | 
						|
                              '(()))
 | 
						|
                            (destructuring-bind (key-form &body forms) clause
 | 
						|
                              `((,test ,value ,key-form)
 | 
						|
                                ,@forms))))
 | 
						|
                      clauses)
 | 
						|
            (t ,default)))))
 | 
						|
 | 
						|
(defmacro switch (&whole whole (object &key (test 'eql) (key 'identity))
 | 
						|
                         &body clauses)
 | 
						|
  "Evaluates first matching clause, returning its values, or evaluates and
 | 
						|
returns the values of T or OTHERWISE if no keys match."
 | 
						|
  (generate-switch-body whole object clauses test key))
 | 
						|
 | 
						|
(defmacro eswitch (&whole whole (object &key (test 'eql) (key 'identity))
 | 
						|
                          &body clauses)
 | 
						|
  "Like SWITCH, but signals an error if no key matches."
 | 
						|
  (generate-switch-body whole object clauses test key '(error)))
 | 
						|
 | 
						|
(defmacro cswitch (&whole whole (object &key (test 'eql) (key 'identity))
 | 
						|
                          &body clauses)
 | 
						|
  "Like SWITCH, but signals a continuable error if no key matches."
 | 
						|
  (generate-switch-body whole object clauses test key '(cerror "Return NIL from CSWITCH.")))
 | 
						|
 | 
						|
(defmacro whichever (&rest possibilities &environment env)
 | 
						|
  "Evaluates exactly one of POSSIBILITIES, chosen at random."
 | 
						|
  (setf possibilities (mapcar (lambda (p) (macroexpand p env)) possibilities))
 | 
						|
  (if (every (lambda (p) (constantp p)) possibilities)
 | 
						|
      `(svref (load-time-value (vector ,@possibilities)) (random ,(length possibilities)))
 | 
						|
      (labels ((expand (possibilities position random-number)
 | 
						|
                 (if (null (cdr possibilities))
 | 
						|
                     (car possibilities)
 | 
						|
                     (let* ((length (length possibilities))
 | 
						|
                            (half (truncate length 2))
 | 
						|
                            (second-half (nthcdr half possibilities))
 | 
						|
                            (first-half (butlast possibilities (- length half))))
 | 
						|
                       `(if (< ,random-number ,(+ position half))
 | 
						|
                            ,(expand first-half position random-number)
 | 
						|
                            ,(expand second-half (+ position half) random-number))))))
 | 
						|
        (with-gensyms (random-number)
 | 
						|
          (let ((length (length possibilities)))
 | 
						|
            `(let ((,random-number (random ,length)))
 | 
						|
               ,(expand possibilities 0 random-number)))))))
 | 
						|
 | 
						|
(defmacro xor (&rest datums)
 | 
						|
  "Evaluates its arguments one at a time, from left to right. If more than one
 | 
						|
argument evaluates to a true value no further DATUMS are evaluated, and NIL is
 | 
						|
returned as both primary and secondary value. If exactly one argument
 | 
						|
evaluates to true, its value is returned as the primary value after all the
 | 
						|
arguments have been evaluated, and T is returned as the secondary value. If no
 | 
						|
arguments evaluate to true NIL is retuned as primary, and T as secondary
 | 
						|
value."
 | 
						|
  (with-gensyms (xor tmp true)
 | 
						|
    `(let (,tmp ,true)
 | 
						|
       (block ,xor
 | 
						|
         ,@(mapcar (lambda (datum)
 | 
						|
                     `(if (setf ,tmp ,datum)
 | 
						|
                          (if ,true
 | 
						|
                              (return-from ,xor (values nil nil))
 | 
						|
                              (setf ,true ,tmp))))
 | 
						|
                   datums)
 | 
						|
         (return-from ,xor (values ,true t))))))
 | 
						|
 | 
						|
(defmacro nth-value-or (nth-value &body forms)
 | 
						|
  "Evaluates FORM arguments one at a time, until the NTH-VALUE returned by one
 | 
						|
of the forms is true. It then returns all the values returned by evaluating
 | 
						|
that form. If none of the forms return a true nth value, this form returns
 | 
						|
NIL."
 | 
						|
  (once-only (nth-value)
 | 
						|
    (with-gensyms (values)
 | 
						|
      `(let ((,values (multiple-value-list ,(first forms))))
 | 
						|
         (if (nth ,nth-value ,values)
 | 
						|
             (values-list ,values)
 | 
						|
             ,(if (rest forms)
 | 
						|
                  `(nth-value-or ,nth-value ,@(rest forms))
 | 
						|
                  nil))))))
 | 
						|
 | 
						|
(defmacro multiple-value-prog2 (first-form second-form &body forms)
 | 
						|
  "Evaluates FIRST-FORM, then SECOND-FORM, and then FORMS. Yields as its value
 | 
						|
all the value returned by SECOND-FORM."
 | 
						|
  `(progn ,first-form (multiple-value-prog1 ,second-form ,@forms)))
 |