370 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Common Lisp
		
	
	
	
	
	
			
		
		
	
	
			370 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Common Lisp
		
	
	
	
	
	
(in-package :alexandria)
 | 
						|
 | 
						|
(defmacro with-gensyms (names &body forms)
 | 
						|
  "Binds a set of variables to gensyms and evaluates the implicit progn FORMS.
 | 
						|
 | 
						|
Each element within NAMES is either a symbol SYMBOL or a pair (SYMBOL
 | 
						|
STRING-DESIGNATOR). Bare symbols are equivalent to the pair (SYMBOL SYMBOL).
 | 
						|
 | 
						|
Each pair (SYMBOL STRING-DESIGNATOR) specifies that the variable named by SYMBOL
 | 
						|
should be bound to a symbol constructed using GENSYM with the string designated
 | 
						|
by STRING-DESIGNATOR being its first argument."
 | 
						|
  `(let ,(mapcar (lambda (name)
 | 
						|
                   (multiple-value-bind (symbol string)
 | 
						|
                       (etypecase name
 | 
						|
                         (symbol
 | 
						|
                          (values name (symbol-name name)))
 | 
						|
                         ((cons symbol (cons string-designator null))
 | 
						|
                          (values (first name) (string (second name)))))
 | 
						|
                     `(,symbol (gensym ,string))))
 | 
						|
                 names)
 | 
						|
     ,@forms))
 | 
						|
 | 
						|
(defmacro with-unique-names (names &body forms)
 | 
						|
  "Alias for WITH-GENSYMS."
 | 
						|
  `(with-gensyms ,names ,@forms))
 | 
						|
 | 
						|
(defmacro once-only (specs &body forms)
 | 
						|
  "Constructs code whose primary goal is to help automate the handling of
 | 
						|
multiple evaluation within macros. Multiple evaluation is handled by introducing
 | 
						|
intermediate variables, in order to reuse the result of an expression.
 | 
						|
 | 
						|
The returned value is a list of the form
 | 
						|
 | 
						|
  (let ((<gensym-1> <expr-1>)
 | 
						|
        ...
 | 
						|
        (<gensym-n> <expr-n>))
 | 
						|
    <res>)
 | 
						|
 | 
						|
where GENSYM-1, ..., GENSYM-N are the intermediate variables introduced in order
 | 
						|
to evaluate EXPR-1, ..., EXPR-N once, only. RES is code that is the result of
 | 
						|
evaluating the implicit progn FORMS within a special context determined by
 | 
						|
SPECS. RES should make use of (reference) the intermediate variables.
 | 
						|
 | 
						|
Each element within SPECS is either a symbol SYMBOL or a pair (SYMBOL INITFORM).
 | 
						|
Bare symbols are equivalent to the pair (SYMBOL SYMBOL).
 | 
						|
 | 
						|
Each pair (SYMBOL INITFORM) specifies a single intermediate variable:
 | 
						|
 | 
						|
- INITFORM is an expression evaluated to produce EXPR-i
 | 
						|
 | 
						|
- SYMBOL is the name of the variable that will be bound around FORMS to the
 | 
						|
  corresponding gensym GENSYM-i, in order for FORMS to generate RES that
 | 
						|
  references the intermediate variable
 | 
						|
 | 
						|
The evaluation of INITFORMs and binding of SYMBOLs resembles LET. INITFORMs of
 | 
						|
all the pairs are evaluated before binding SYMBOLs and evaluating FORMS.
 | 
						|
 | 
						|
Example:
 | 
						|
 | 
						|
  The following expression
 | 
						|
 | 
						|
  (let ((x '(incf y)))
 | 
						|
    (once-only (x)
 | 
						|
      `(cons ,x ,x)))
 | 
						|
 | 
						|
  ;;; =>
 | 
						|
  ;;; (let ((#1=#:X123 (incf y)))
 | 
						|
  ;;;   (cons #1# #1#))
 | 
						|
 | 
						|
  could be used within a macro to avoid multiple evaluation like so
 | 
						|
 | 
						|
  (defmacro cons1 (x)
 | 
						|
    (once-only (x)
 | 
						|
      `(cons ,x ,x)))
 | 
						|
 | 
						|
  (let ((y 0))
 | 
						|
    (cons1 (incf y)))
 | 
						|
 | 
						|
  ;;; => (1 . 1)
 | 
						|
 | 
						|
Example:
 | 
						|
 | 
						|
  The following expression demonstrates the usage of the INITFORM field
 | 
						|
 | 
						|
  (let ((expr '(incf y)))
 | 
						|
    (once-only ((var `(1+ ,expr)))
 | 
						|
      `(list ',expr ,var ,var)))
 | 
						|
 | 
						|
  ;;; =>
 | 
						|
  ;;; (let ((#1=#:VAR123 (1+ (incf y))))
 | 
						|
  ;;;   (list '(incf y) #1# #1))
 | 
						|
 | 
						|
  which could be used like so
 | 
						|
 | 
						|
  (defmacro print-succ-twice (expr)
 | 
						|
    (once-only ((var `(1+ ,expr)))
 | 
						|
      `(format t \"Expr: ~s, Once: ~s, Twice: ~s~%\" ',expr ,var ,var)))
 | 
						|
 | 
						|
  (let ((y 10))
 | 
						|
    (print-succ-twice (incf y)))
 | 
						|
 | 
						|
  ;;; >>
 | 
						|
  ;;; Expr: (INCF Y), Once: 12, Twice: 12"
 | 
						|
  (let ((gensyms (make-gensym-list (length specs) "ONCE-ONLY"))
 | 
						|
        (names-and-forms (mapcar (lambda (spec)
 | 
						|
                                   (etypecase spec
 | 
						|
                                     (list
 | 
						|
                                      (destructuring-bind (name form) spec
 | 
						|
                                        (cons name form)))
 | 
						|
                                     (symbol
 | 
						|
                                      (cons spec spec))))
 | 
						|
                                 specs)))
 | 
						|
    ;; bind in user-macro
 | 
						|
    `(let ,(mapcar (lambda (g n) (list g `(gensym ,(string (car n)))))
 | 
						|
                   gensyms names-and-forms)
 | 
						|
       ;; bind in final expansion
 | 
						|
       `(let (,,@(mapcar (lambda (g n)
 | 
						|
                           ``(,,g ,,(cdr n)))
 | 
						|
                         gensyms names-and-forms))
 | 
						|
          ;; bind in user-macro
 | 
						|
          ,(let ,(mapcar (lambda (n g) (list (car n) g))
 | 
						|
                         names-and-forms gensyms)
 | 
						|
             ,@forms)))))
 | 
						|
 | 
						|
(defun parse-body (body &key documentation whole)
 | 
						|
  "Parses BODY into (values remaining-forms declarations doc-string).
 | 
						|
Documentation strings are recognized only if DOCUMENTATION is true.
 | 
						|
Syntax errors in body are signalled and WHOLE is used in the signal
 | 
						|
arguments when given."
 | 
						|
  (let ((doc nil)
 | 
						|
        (decls nil)
 | 
						|
        (current nil))
 | 
						|
    (tagbody
 | 
						|
     :declarations
 | 
						|
       (setf current (car body))
 | 
						|
       (when (and documentation (stringp current) (cdr body))
 | 
						|
         (if doc
 | 
						|
             (error "Too many documentation strings in ~S." (or whole body))
 | 
						|
             (setf doc (pop body)))
 | 
						|
         (go :declarations))
 | 
						|
       (when (and (listp current) (eql (first current) 'declare))
 | 
						|
         (push (pop body) decls)
 | 
						|
         (go :declarations)))
 | 
						|
    (values body (nreverse decls) doc)))
 | 
						|
 | 
						|
(defun parse-ordinary-lambda-list (lambda-list &key (normalize t)
 | 
						|
                                   allow-specializers
 | 
						|
                                   (normalize-optional normalize)
 | 
						|
                                   (normalize-keyword normalize)
 | 
						|
                                   (normalize-auxilary normalize))
 | 
						|
  "Parses an ordinary lambda-list, returning as multiple values:
 | 
						|
 | 
						|
1. Required parameters.
 | 
						|
 | 
						|
2. Optional parameter specifications, normalized into form:
 | 
						|
 | 
						|
   (name init suppliedp)
 | 
						|
 | 
						|
3. Name of the rest parameter, or NIL.
 | 
						|
 | 
						|
4. Keyword parameter specifications, normalized into form:
 | 
						|
 | 
						|
   ((keyword-name name) init suppliedp)
 | 
						|
 | 
						|
5. Boolean indicating &ALLOW-OTHER-KEYS presence.
 | 
						|
 | 
						|
6. &AUX parameter specifications, normalized into form
 | 
						|
 | 
						|
   (name init).
 | 
						|
 | 
						|
7. Existence of &KEY in the lambda-list.
 | 
						|
 | 
						|
Signals a PROGRAM-ERROR is the lambda-list is malformed."
 | 
						|
  (let ((state :required)
 | 
						|
        (allow-other-keys nil)
 | 
						|
        (auxp nil)
 | 
						|
        (required nil)
 | 
						|
        (optional nil)
 | 
						|
        (rest nil)
 | 
						|
        (keys nil)
 | 
						|
        (keyp nil)
 | 
						|
        (aux nil))
 | 
						|
    (labels ((fail (elt)
 | 
						|
               (simple-program-error "Misplaced ~S in ordinary lambda-list:~%  ~S"
 | 
						|
                                     elt lambda-list))
 | 
						|
             (check-variable (elt what &optional (allow-specializers allow-specializers))
 | 
						|
               (unless (and (or (symbolp elt)
 | 
						|
                                (and allow-specializers
 | 
						|
                                     (consp elt) (= 2 (length elt)) (symbolp (first elt))))
 | 
						|
                            (not (constantp elt)))
 | 
						|
                 (simple-program-error "Invalid ~A ~S in ordinary lambda-list:~%  ~S"
 | 
						|
                                       what elt lambda-list)))
 | 
						|
             (check-spec (spec what)
 | 
						|
               (destructuring-bind (init suppliedp) spec
 | 
						|
                 (declare (ignore init))
 | 
						|
                 (check-variable suppliedp what nil))))
 | 
						|
      (dolist (elt lambda-list)
 | 
						|
        (case elt
 | 
						|
          (&optional
 | 
						|
           (if (eq state :required)
 | 
						|
               (setf state elt)
 | 
						|
               (fail elt)))
 | 
						|
          (&rest
 | 
						|
           (if (member state '(:required &optional))
 | 
						|
               (setf state elt)
 | 
						|
               (fail elt)))
 | 
						|
          (&key
 | 
						|
           (if (member state '(:required &optional :after-rest))
 | 
						|
               (setf state elt)
 | 
						|
               (fail elt))
 | 
						|
           (setf keyp t))
 | 
						|
          (&allow-other-keys
 | 
						|
           (if (eq state '&key)
 | 
						|
               (setf allow-other-keys t
 | 
						|
                     state elt)
 | 
						|
               (fail elt)))
 | 
						|
          (&aux
 | 
						|
           (cond ((eq state '&rest)
 | 
						|
                  (fail elt))
 | 
						|
                 (auxp
 | 
						|
                  (simple-program-error "Multiple ~S in ordinary lambda-list:~%  ~S"
 | 
						|
                                        elt lambda-list))
 | 
						|
                 (t
 | 
						|
                  (setf auxp t
 | 
						|
                        state elt))
 | 
						|
                 ))
 | 
						|
          (otherwise
 | 
						|
           (when (member elt '#.(set-difference lambda-list-keywords
 | 
						|
                                                '(&optional &rest &key &allow-other-keys &aux)))
 | 
						|
             (simple-program-error
 | 
						|
              "Bad lambda-list keyword ~S in ordinary lambda-list:~%  ~S"
 | 
						|
              elt lambda-list))
 | 
						|
           (case state
 | 
						|
             (:required
 | 
						|
              (check-variable elt "required parameter")
 | 
						|
              (push elt required))
 | 
						|
             (&optional
 | 
						|
              (cond ((consp elt)
 | 
						|
                     (destructuring-bind (name &rest tail) elt
 | 
						|
                       (check-variable name "optional parameter")
 | 
						|
                       (cond ((cdr tail)
 | 
						|
                              (check-spec tail "optional-supplied-p parameter"))
 | 
						|
                             ((and normalize-optional tail)
 | 
						|
                              (setf elt (append elt '(nil))))
 | 
						|
                             (normalize-optional
 | 
						|
                              (setf elt (append elt '(nil nil)))))))
 | 
						|
                    (t
 | 
						|
                     (check-variable elt "optional parameter")
 | 
						|
                     (when normalize-optional
 | 
						|
                       (setf elt (cons elt '(nil nil))))))
 | 
						|
              (push (ensure-list elt) optional))
 | 
						|
             (&rest
 | 
						|
              (check-variable elt "rest parameter")
 | 
						|
              (setf rest elt
 | 
						|
                    state :after-rest))
 | 
						|
             (&key
 | 
						|
              (cond ((consp elt)
 | 
						|
                     (destructuring-bind (var-or-kv &rest tail) elt
 | 
						|
                       (cond ((consp var-or-kv)
 | 
						|
                              (destructuring-bind (keyword var) var-or-kv
 | 
						|
                                (unless (symbolp keyword)
 | 
						|
                                  (simple-program-error "Invalid keyword name ~S in ordinary ~
 | 
						|
                                                         lambda-list:~%  ~S"
 | 
						|
                                                        keyword lambda-list))
 | 
						|
                                (check-variable var "keyword parameter")))
 | 
						|
                             (t
 | 
						|
                              (check-variable var-or-kv "keyword parameter")
 | 
						|
                              (when normalize-keyword
 | 
						|
                                (setf var-or-kv (list (make-keyword var-or-kv) var-or-kv)))))
 | 
						|
                       (cond ((cdr tail)
 | 
						|
                              (check-spec tail "keyword-supplied-p parameter"))
 | 
						|
                             ((and normalize-keyword tail)
 | 
						|
                              (setf tail (append tail '(nil))))
 | 
						|
                             (normalize-keyword
 | 
						|
                              (setf tail '(nil nil))))
 | 
						|
                       (setf elt (cons var-or-kv tail))))
 | 
						|
                    (t
 | 
						|
                     (check-variable elt "keyword parameter")
 | 
						|
                     (setf elt (if normalize-keyword
 | 
						|
                                   (list (list (make-keyword elt) elt) nil nil)
 | 
						|
                                   elt))))
 | 
						|
              (push elt keys))
 | 
						|
             (&aux
 | 
						|
              (if (consp elt)
 | 
						|
                  (destructuring-bind (var &optional init) elt
 | 
						|
                    (declare (ignore init))
 | 
						|
                    (check-variable var "&aux parameter"))
 | 
						|
                  (progn
 | 
						|
                    (check-variable elt "&aux parameter")
 | 
						|
                    (setf elt (list* elt (when normalize-auxilary
 | 
						|
                                           '(nil))))))
 | 
						|
              (push elt aux))
 | 
						|
             (t
 | 
						|
              (simple-program-error "Invalid ordinary lambda-list:~%  ~S" lambda-list)))))))
 | 
						|
    (values (nreverse required) (nreverse optional) rest (nreverse keys)
 | 
						|
            allow-other-keys (nreverse aux) keyp)))
 | 
						|
 | 
						|
;;;; DESTRUCTURING-*CASE
 | 
						|
 | 
						|
(defun expand-destructuring-case (key clauses case)
 | 
						|
  (once-only (key)
 | 
						|
    `(if (typep ,key 'cons)
 | 
						|
         (,case (car ,key)
 | 
						|
           ,@(mapcar (lambda (clause)
 | 
						|
                       (destructuring-bind ((keys . lambda-list) &body body) clause
 | 
						|
                         `(,keys
 | 
						|
                           (destructuring-bind ,lambda-list (cdr ,key)
 | 
						|
                             ,@body))))
 | 
						|
                     clauses))
 | 
						|
         (error "Invalid key to DESTRUCTURING-~S: ~S" ',case ,key))))
 | 
						|
 | 
						|
(defmacro destructuring-case (keyform &body clauses)
 | 
						|
  "DESTRUCTURING-CASE, -CCASE, and -ECASE are a combination of CASE and DESTRUCTURING-BIND.
 | 
						|
KEYFORM must evaluate to a CONS.
 | 
						|
 | 
						|
Clauses are of the form:
 | 
						|
 | 
						|
  ((CASE-KEYS . DESTRUCTURING-LAMBDA-LIST) FORM*)
 | 
						|
 | 
						|
The clause whose CASE-KEYS matches CAR of KEY, as if by CASE, CCASE, or ECASE,
 | 
						|
is selected, and FORMs are then executed with CDR of KEY is destructured and
 | 
						|
bound by the DESTRUCTURING-LAMBDA-LIST.
 | 
						|
 | 
						|
Example:
 | 
						|
 | 
						|
 (defun dcase (x)
 | 
						|
   (destructuring-case x
 | 
						|
     ((:foo a b)
 | 
						|
      (format nil \"foo: ~S, ~S\" a b))
 | 
						|
     ((:bar &key a b)
 | 
						|
      (format nil \"bar: ~S, ~S\" a b))
 | 
						|
     (((:alt1 :alt2) a)
 | 
						|
      (format nil \"alt: ~S\" a))
 | 
						|
     ((t &rest rest)
 | 
						|
      (format nil \"unknown: ~S\" rest))))
 | 
						|
 | 
						|
  (dcase (list :foo 1 2))        ; => \"foo: 1, 2\"
 | 
						|
  (dcase (list :bar :a 1 :b 2))  ; => \"bar: 1, 2\"
 | 
						|
  (dcase (list :alt1 1))         ; => \"alt: 1\"
 | 
						|
  (dcase (list :alt2 2))         ; => \"alt: 2\"
 | 
						|
  (dcase (list :quux 1 2 3))     ; => \"unknown: 1, 2, 3\"
 | 
						|
 | 
						|
 (defun decase (x)
 | 
						|
   (destructuring-case x
 | 
						|
     ((:foo a b)
 | 
						|
      (format nil \"foo: ~S, ~S\" a b))
 | 
						|
     ((:bar &key a b)
 | 
						|
      (format nil \"bar: ~S, ~S\" a b))
 | 
						|
     (((:alt1 :alt2) a)
 | 
						|
      (format nil \"alt: ~S\" a))))
 | 
						|
 | 
						|
  (decase (list :foo 1 2))        ; => \"foo: 1, 2\"
 | 
						|
  (decase (list :bar :a 1 :b 2))  ; => \"bar: 1, 2\"
 | 
						|
  (decase (list :alt1 1))         ; => \"alt: 1\"
 | 
						|
  (decase (list :alt2 2))         ; => \"alt: 2\"
 | 
						|
  (decase (list :quux 1 2 3))     ; =| error
 | 
						|
"
 | 
						|
  (expand-destructuring-case keyform clauses 'case))
 | 
						|
 | 
						|
(defmacro destructuring-ccase (keyform &body clauses)
 | 
						|
  (expand-destructuring-case keyform clauses 'ccase))
 | 
						|
 | 
						|
(defmacro destructuring-ecase (keyform &body clauses)
 | 
						|
  (expand-destructuring-case keyform clauses 'ecase))
 | 
						|
 | 
						|
(dolist (name '(destructuring-ccase destructuring-ecase))
 | 
						|
  (setf (documentation name 'function) (documentation 'destructuring-case 'function)))
 | 
						|
 | 
						|
 | 
						|
 |