mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-10-29 17:50:22 +00:00
82 lines
3.2 KiB
Common Lisp
82 lines
3.2 KiB
Common Lisp
;; @file macros-advanced.cl
|
|
;;
|
|
;; @breif Advanced macro practices - defining your own macros
|
|
;;
|
|
;; Macro definition skeleton:
|
|
;; (defmacro name (parameter*)
|
|
;; "Optional documentation string"
|
|
;; body-form*)
|
|
;;
|
|
;; Note that backquote expression is most often used in the `body-form`
|
|
;;
|
|
|
|
; `primep` test a number for prime
|
|
(defun primep (n)
|
|
"test a number for prime"
|
|
(if (< n 2) (return-from primep))
|
|
(do ((i 2 (1+ i)) (p t (not (zerop (mod n i)))))
|
|
((> i (sqrt n)) p)
|
|
(when (not p) (return))))
|
|
; `next-prime` return the next prime bigger than the specified number
|
|
(defun next-prime (n)
|
|
"return the next prime bigger than the speicified number"
|
|
(do ((i (1+ n) (1+ i)))
|
|
((primep i) i)))
|
|
;
|
|
; The recommended procedures to writting a new macro are as follows:
|
|
; 1. Write a sample call to the macro and the code it should expand into
|
|
(do-primes (p 0 19)
|
|
(format t "~d " p))
|
|
; Expected expanded codes
|
|
(do ((p (next-prime (- 0 1)) (next-prime p)))
|
|
((> p 19))
|
|
(format t "~d " p))
|
|
; 2. Write code that generate the hardwritten expansion from the arguments in
|
|
; the sample call
|
|
(defmacro do-primes (var-and-range &rest body)
|
|
(let ((var (first var-and-range))
|
|
(start (second var-and-range))
|
|
(end (third var-and-range)))
|
|
`(do ((,var (next-prime (- ,start 1)) (next-prime ,var)))
|
|
((> ,var ,end))
|
|
,@body)))
|
|
; 2-1. More concise implementations with the 'parameter list destructuring' and
|
|
; '&body' synonym, it also emits more friendly messages on incorrent input.
|
|
(defmacro do-primes ((var start end) &body body)
|
|
`(do ((,var (next-prime (- ,start 1)) (next-prime ,var)))
|
|
((> ,var ,end))
|
|
,@body))
|
|
; 2-2. Test the result of macro expansion with the `macroexpand-1` function
|
|
(macroexpand-1 '(do-primes (p 0 19) (format t "~d " p)))
|
|
; 3. Make sure the macro abstraction does not "leak"
|
|
(defmacro do-primes ((var start end) &body body)
|
|
(let ((end-value-name (gensym)))
|
|
`(do ((,var (next-prime (- ,start 1)) (next-prime ,var))
|
|
(,end-value-name ,end))
|
|
((> ,var ,end-value-name))
|
|
,@body)))
|
|
; 3-1. Rules to observe to avoid common and possible leaks
|
|
; a. include any subforms in the expansion in positions that will be evaluated
|
|
; in the same order as the subforms appear in the macro call
|
|
; b. make sure subforms are evaluated only once by creating a variable in the
|
|
; expansion to hold the value of evaluating the argument form, and then
|
|
; using that variable anywhere else the value is needed in the expansion
|
|
; c. use `gensym` at macro expansion time to create variable names used in the
|
|
; expansion
|
|
;
|
|
; Appendix I. Macro-writting macros, 'with-gensyms', to guranttee that rule c
|
|
; gets observed.
|
|
; Example usage of `with-gensyms`
|
|
(defmacro do-primes-a ((var start end) &body body)
|
|
"do-primes implementation with macro-writting macro 'with-gensyms'"
|
|
(with-gensyms (end-value-name)
|
|
`(do ((,var (next-prime (- ,start 1)) (next-prime ,var))
|
|
(,end-value-name ,end))
|
|
((> ,var ,end-value-name))
|
|
,@body)))
|
|
; Define the macro, note how comma is used to interpolate the value of the loop
|
|
; expression
|
|
(defmacro with-gensyms ((&rest names) &body body)
|
|
`(let ,(loop for n in names collect `(,n (gensym)))
|
|
,@body)
|
|
) |