macros.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP
 (HTM) git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Tags
 (DIR) README
 (DIR) LICENSE
       ---
       macros.lisp (13999B)
       ---
            1 (in-package :alexandria)
            2 
            3 (defmacro with-gensyms (names &body forms)
            4   "Binds a set of variables to gensyms and evaluates the implicit progn FORMS.
            5 
            6 Each element within NAMES is either a symbol SYMBOL or a pair (SYMBOL
            7 STRING-DESIGNATOR). Bare symbols are equivalent to the pair (SYMBOL SYMBOL).
            8 
            9 Each pair (SYMBOL STRING-DESIGNATOR) specifies that the variable named by SYMBOL
           10 should be bound to a symbol constructed using GENSYM with the string designated
           11 by STRING-DESIGNATOR being its first argument."
           12   `(let ,(mapcar (lambda (name)
           13                    (multiple-value-bind (symbol string)
           14                        (etypecase name
           15                          (symbol
           16                           (values name (symbol-name name)))
           17                          ((cons symbol (cons string-designator null))
           18                           (values (first name) (string (second name)))))
           19                      `(,symbol (gensym ,string))))
           20                  names)
           21      ,@forms))
           22 
           23 (defmacro with-unique-names (names &body forms)
           24   "Alias for WITH-GENSYMS."
           25   `(with-gensyms ,names ,@forms))
           26 
           27 (defmacro once-only (specs &body forms)
           28   "Constructs code whose primary goal is to help automate the handling of
           29 multiple evaluation within macros. Multiple evaluation is handled by introducing
           30 intermediate variables, in order to reuse the result of an expression.
           31 
           32 The returned value is a list of the form
           33 
           34   (let ((<gensym-1> <expr-1>)
           35         ...
           36         (<gensym-n> <expr-n>))
           37     <res>)
           38 
           39 where GENSYM-1, ..., GENSYM-N are the intermediate variables introduced in order
           40 to evaluate EXPR-1, ..., EXPR-N once, only. RES is code that is the result of
           41 evaluating the implicit progn FORMS within a special context determined by
           42 SPECS. RES should make use of (reference) the intermediate variables.
           43 
           44 Each element within SPECS is either a symbol SYMBOL or a pair (SYMBOL INITFORM).
           45 Bare symbols are equivalent to the pair (SYMBOL SYMBOL).
           46 
           47 Each pair (SYMBOL INITFORM) specifies a single intermediate variable:
           48 
           49 - INITFORM is an expression evaluated to produce EXPR-i
           50 
           51 - SYMBOL is the name of the variable that will be bound around FORMS to the
           52   corresponding gensym GENSYM-i, in order for FORMS to generate RES that
           53   references the intermediate variable
           54 
           55 The evaluation of INITFORMs and binding of SYMBOLs resembles LET. INITFORMs of
           56 all the pairs are evaluated before binding SYMBOLs and evaluating FORMS.
           57 
           58 Example:
           59 
           60   The following expression
           61 
           62   (let ((x '(incf y)))
           63     (once-only (x)
           64       `(cons ,x ,x)))
           65 
           66   ;;; =>
           67   ;;; (let ((#1=#:X123 (incf y)))
           68   ;;;   (cons #1# #1#))
           69 
           70   could be used within a macro to avoid multiple evaluation like so
           71 
           72   (defmacro cons1 (x)
           73     (once-only (x)
           74       `(cons ,x ,x)))
           75 
           76   (let ((y 0))
           77     (cons1 (incf y)))
           78 
           79   ;;; => (1 . 1)
           80 
           81 Example:
           82 
           83   The following expression demonstrates the usage of the INITFORM field
           84 
           85   (let ((expr '(incf y)))
           86     (once-only ((var `(1+ ,expr)))
           87       `(list ',expr ,var ,var)))
           88 
           89   ;;; =>
           90   ;;; (let ((#1=#:VAR123 (1+ (incf y))))
           91   ;;;   (list '(incf y) #1# #1))
           92 
           93   which could be used like so
           94 
           95   (defmacro print-succ-twice (expr)
           96     (once-only ((var `(1+ ,expr)))
           97       `(format t \"Expr: ~s, Once: ~s, Twice: ~s~%\" ',expr ,var ,var)))
           98 
           99   (let ((y 10))
          100     (print-succ-twice (incf y)))
          101 
          102   ;;; >>
          103   ;;; Expr: (INCF Y), Once: 12, Twice: 12"
          104   (let ((gensyms (make-gensym-list (length specs) "ONCE-ONLY"))
          105         (names-and-forms (mapcar (lambda (spec)
          106                                    (etypecase spec
          107                                      (list
          108                                       (destructuring-bind (name form) spec
          109                                         (cons name form)))
          110                                      (symbol
          111                                       (cons spec spec))))
          112                                  specs)))
          113     ;; bind in user-macro
          114     `(let ,(mapcar (lambda (g n) (list g `(gensym ,(string (car n)))))
          115                    gensyms names-and-forms)
          116        ;; bind in final expansion
          117        `(let (,,@(mapcar (lambda (g n)
          118                            ``(,,g ,,(cdr n)))
          119                          gensyms names-and-forms))
          120           ;; bind in user-macro
          121           ,(let ,(mapcar (lambda (n g) (list (car n) g))
          122                          names-and-forms gensyms)
          123              ,@forms)))))
          124 
          125 (defun parse-body (body &key documentation whole)
          126   "Parses BODY into (values remaining-forms declarations doc-string).
          127 Documentation strings are recognized only if DOCUMENTATION is true.
          128 Syntax errors in body are signalled and WHOLE is used in the signal
          129 arguments when given."
          130   (let ((doc nil)
          131         (decls nil)
          132         (current nil))
          133     (tagbody
          134      :declarations
          135        (setf current (car body))
          136        (when (and documentation (stringp current) (cdr body))
          137          (if doc
          138              (error "Too many documentation strings in ~S." (or whole body))
          139              (setf doc (pop body)))
          140          (go :declarations))
          141        (when (and (listp current) (eql (first current) 'declare))
          142          (push (pop body) decls)
          143          (go :declarations)))
          144     (values body (nreverse decls) doc)))
          145 
          146 (defun parse-ordinary-lambda-list (lambda-list &key (normalize t)
          147                                    allow-specializers
          148                                    (normalize-optional normalize)
          149                                    (normalize-keyword normalize)
          150                                    (normalize-auxilary normalize))
          151   "Parses an ordinary lambda-list, returning as multiple values:
          152 
          153 1. Required parameters.
          154 
          155 2. Optional parameter specifications, normalized into form:
          156 
          157    (name init suppliedp)
          158 
          159 3. Name of the rest parameter, or NIL.
          160 
          161 4. Keyword parameter specifications, normalized into form:
          162 
          163    ((keyword-name name) init suppliedp)
          164 
          165 5. Boolean indicating &ALLOW-OTHER-KEYS presence.
          166 
          167 6. &AUX parameter specifications, normalized into form
          168 
          169    (name init).
          170 
          171 7. Existence of &KEY in the lambda-list.
          172 
          173 Signals a PROGRAM-ERROR is the lambda-list is malformed."
          174   (let ((state :required)
          175         (allow-other-keys nil)
          176         (auxp nil)
          177         (required nil)
          178         (optional nil)
          179         (rest nil)
          180         (keys nil)
          181         (keyp nil)
          182         (aux nil))
          183     (labels ((fail (elt)
          184                (simple-program-error "Misplaced ~S in ordinary lambda-list:~%  ~S"
          185                                      elt lambda-list))
          186              (check-variable (elt what &optional (allow-specializers allow-specializers))
          187                (unless (and (or (symbolp elt)
          188                                 (and allow-specializers
          189                                      (consp elt) (= 2 (length elt)) (symbolp (first elt))))
          190                             (not (constantp elt)))
          191                  (simple-program-error "Invalid ~A ~S in ordinary lambda-list:~%  ~S"
          192                                        what elt lambda-list)))
          193              (check-spec (spec what)
          194                (destructuring-bind (init suppliedp) spec
          195                  (declare (ignore init))
          196                  (check-variable suppliedp what nil))))
          197       (dolist (elt lambda-list)
          198         (case elt
          199           (&optional
          200            (if (eq state :required)
          201                (setf state elt)
          202                (fail elt)))
          203           (&rest
          204            (if (member state '(:required &optional))
          205                (setf state elt)
          206                (fail elt)))
          207           (&key
          208            (if (member state '(:required &optional :after-rest))
          209                (setf state elt)
          210                (fail elt))
          211            (setf keyp t))
          212           (&allow-other-keys
          213            (if (eq state '&key)
          214                (setf allow-other-keys t
          215                      state elt)
          216                (fail elt)))
          217           (&aux
          218            (cond ((eq state '&rest)
          219                   (fail elt))
          220                  (auxp
          221                   (simple-program-error "Multiple ~S in ordinary lambda-list:~%  ~S"
          222                                         elt lambda-list))
          223                  (t
          224                   (setf auxp t
          225                         state elt))
          226                  ))
          227           (otherwise
          228            (when (member elt '#.(set-difference lambda-list-keywords
          229                                                 '(&optional &rest &key &allow-other-keys &aux)))
          230              (simple-program-error
          231               "Bad lambda-list keyword ~S in ordinary lambda-list:~%  ~S"
          232               elt lambda-list))
          233            (case state
          234              (:required
          235               (check-variable elt "required parameter")
          236               (push elt required))
          237              (&optional
          238               (cond ((consp elt)
          239                      (destructuring-bind (name &rest tail) elt
          240                        (check-variable name "optional parameter")
          241                        (cond ((cdr tail)
          242                               (check-spec tail "optional-supplied-p parameter"))
          243                              ((and normalize-optional tail)
          244                               (setf elt (append elt '(nil))))
          245                              (normalize-optional
          246                               (setf elt (append elt '(nil nil)))))))
          247                     (t
          248                      (check-variable elt "optional parameter")
          249                      (when normalize-optional
          250                        (setf elt (cons elt '(nil nil))))))
          251               (push (ensure-list elt) optional))
          252              (&rest
          253               (check-variable elt "rest parameter")
          254               (setf rest elt
          255                     state :after-rest))
          256              (&key
          257               (cond ((consp elt)
          258                      (destructuring-bind (var-or-kv &rest tail) elt
          259                        (cond ((consp var-or-kv)
          260                               (destructuring-bind (keyword var) var-or-kv
          261                                 (unless (symbolp keyword)
          262                                   (simple-program-error "Invalid keyword name ~S in ordinary ~
          263                                                          lambda-list:~%  ~S"
          264                                                         keyword lambda-list))
          265                                 (check-variable var "keyword parameter")))
          266                              (t
          267                               (check-variable var-or-kv "keyword parameter")
          268                               (when normalize-keyword
          269                                 (setf var-or-kv (list (make-keyword var-or-kv) var-or-kv)))))
          270                        (cond ((cdr tail)
          271                               (check-spec tail "keyword-supplied-p parameter"))
          272                              ((and normalize-keyword tail)
          273                               (setf tail (append tail '(nil))))
          274                              (normalize-keyword
          275                               (setf tail '(nil nil))))
          276                        (setf elt (cons var-or-kv tail))))
          277                     (t
          278                      (check-variable elt "keyword parameter")
          279                      (setf elt (if normalize-keyword
          280                                    (list (list (make-keyword elt) elt) nil nil)
          281                                    elt))))
          282               (push elt keys))
          283              (&aux
          284               (if (consp elt)
          285                   (destructuring-bind (var &optional init) elt
          286                     (declare (ignore init))
          287                     (check-variable var "&aux parameter"))
          288                   (progn
          289                     (check-variable elt "&aux parameter")
          290                     (setf elt (list* elt (when normalize-auxilary
          291                                            '(nil))))))
          292               (push elt aux))
          293              (t
          294               (simple-program-error "Invalid ordinary lambda-list:~%  ~S" lambda-list)))))))
          295     (values (nreverse required) (nreverse optional) rest (nreverse keys)
          296             allow-other-keys (nreverse aux) keyp)))
          297 
          298 ;;;; DESTRUCTURING-*CASE
          299 
          300 (defun expand-destructuring-case (key clauses case)
          301   (once-only (key)
          302     `(if (typep ,key 'cons)
          303          (,case (car ,key)
          304            ,@(mapcar (lambda (clause)
          305                        (destructuring-bind ((keys . lambda-list) &body body) clause
          306                          `(,keys
          307                            (destructuring-bind ,lambda-list (cdr ,key)
          308                              ,@body))))
          309                      clauses))
          310          (error "Invalid key to DESTRUCTURING-~S: ~S" ',case ,key))))
          311 
          312 (defmacro destructuring-case (keyform &body clauses)
          313   "DESTRUCTURING-CASE, -CCASE, and -ECASE are a combination of CASE and DESTRUCTURING-BIND.
          314 KEYFORM must evaluate to a CONS.
          315 
          316 Clauses are of the form:
          317 
          318   ((CASE-KEYS . DESTRUCTURING-LAMBDA-LIST) FORM*)
          319 
          320 The clause whose CASE-KEYS matches CAR of KEY, as if by CASE, CCASE, or ECASE,
          321 is selected, and FORMs are then executed with CDR of KEY is destructured and
          322 bound by the DESTRUCTURING-LAMBDA-LIST.
          323 
          324 Example:
          325 
          326  (defun dcase (x)
          327    (destructuring-case x
          328      ((:foo a b)
          329       (format nil \"foo: ~S, ~S\" a b))
          330      ((:bar &key a b)
          331       (format nil \"bar: ~S, ~S\" a b))
          332      (((:alt1 :alt2) a)
          333       (format nil \"alt: ~S\" a))
          334      ((t &rest rest)
          335       (format nil \"unknown: ~S\" rest))))
          336 
          337   (dcase (list :foo 1 2))        ; => \"foo: 1, 2\"
          338   (dcase (list :bar :a 1 :b 2))  ; => \"bar: 1, 2\"
          339   (dcase (list :alt1 1))         ; => \"alt: 1\"
          340   (dcase (list :alt2 2))         ; => \"alt: 2\"
          341   (dcase (list :quux 1 2 3))     ; => \"unknown: 1, 2, 3\"
          342 
          343  (defun decase (x)
          344    (destructuring-case x
          345      ((:foo a b)
          346       (format nil \"foo: ~S, ~S\" a b))
          347      ((:bar &key a b)
          348       (format nil \"bar: ~S, ~S\" a b))
          349      (((:alt1 :alt2) a)
          350       (format nil \"alt: ~S\" a))))
          351 
          352   (decase (list :foo 1 2))        ; => \"foo: 1, 2\"
          353   (decase (list :bar :a 1 :b 2))  ; => \"bar: 1, 2\"
          354   (decase (list :alt1 1))         ; => \"alt: 1\"
          355   (decase (list :alt2 2))         ; => \"alt: 2\"
          356   (decase (list :quux 1 2 3))     ; =| error
          357 "
          358   (expand-destructuring-case keyform clauses 'case))
          359 
          360 (defmacro destructuring-ccase (keyform &body clauses)
          361   (expand-destructuring-case keyform clauses 'ccase))
          362 
          363 (defmacro destructuring-ecase (keyform &body clauses)
          364   (expand-destructuring-case keyform clauses 'ecase))
          365 
          366 (dolist (name '(destructuring-ccase destructuring-ecase))
          367   (setf (documentation name 'function) (documentation 'destructuring-case 'function)))
          368 
          369 
          370