;;; Copyright 2013 Google Inc. ;;; ;;; Licensed under the Apache License, Version 2.0 (the "License"); ;;; you may not use this file except in compliance with the License. ;;; You may obtain a copy of the License at ;;; ;;; http://www.apache.org/licenses/LICENSE-2.0 ;;; ;;; Unless required by applicable law or agreed to in writing, software ;;; distributed under the License is distributed on an "AS IS" BASIS, ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ;;; See the License for the specific language governing permissions and ;;; limitations under the License. ;;; A Lisp macro is a function that accepts Lisp data and produces a Lisp form. ;;; When the macro is called, its macro function receives unevaluated arguments ;;; and may use them to produce a new Lisp form. This form is then spliced in ;;; place of the original macro call and is then evaluated. (defmacro my-and (&rest forms) ;; We use a LABELS local function to allow for recursive expansion. (labels ((generate (forms) (cond ((null forms) 'nil) ((null (rest forms)) (first forms)) (t `(when ,(first forms) ,(generate (rest forms))))))) ; wowy (generate forms))) (when (= 2 3) "hello") (define-test my-and ;; ASSERT-EXPANDS macroexpands the first form once and checks if it is equal ;; to the second form. (assert-expands (my-and (= 0 (random 6)) (error "Bang!")) '(when (= 0 (random 6)) (error "Bang!"))) (assert-expands (my-and (= 0 (random 6)) (= 0 (random 6)) (= 0 (random 6)) (error "Bang!")) '(when (= 0 (random 6)) (when (= 0 (random 6)) (when (= 0 (random 6)) (error "Bang!")))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; A common macro pitfall is capturing a variable defined by the user. (define-test variable-capture (macrolet ((for ((var start stop) &body body) `(do ((,var ,start (1+ ,var)) (limit ,stop)) ((> ,var limit)) ,@body))) (let ((limit 10) (result '())) (for (i 0 3) (push i result) (assert-equal 3 limit)) (assert-equal '(0 1 2 3) (nreverse result))))) ; didn't get it on first tries, ugh ;; oh, ok - then try to use names that wouldn't happen in outside condext ;; so that explicitly defined things in outside context get overshadowed ;; ok ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Another pitfall is evaluating some forms multiple times where they are only ;;; meant to be evaluated once. (define-test multiple-evaluation ;; We use MACROLET for defining a local macro. (macrolet ((for ((var start stop) &body body) `(do ((,var ,start (1+ ,var))) ((> ,var ,stop)) ,@body))) (let ((side-effects '()) (result '())) ;; Our functions RETURN-0 and RETURN-3 have side effects. (flet ((return-0 () (push 0 side-effects) 0) (return-3 () (push 3 side-effects) 3)) (for (i (return-0) (return-3)) (push i result))) (assert-equal '(0 1 2 3) (nreverse result)) (assert-equal '(0 3 3 3 3 3) (nreverse side-effects))))) ; omg, fuck this guessing ; ok, the ,stop was evaluated on each iteraction ; to check "whether to stop" ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Yet another pitfall is not respecting the evaluation order of the macro ;;; subforms. (define-test wrong-evaluation-order (macrolet ((for ((var start stop) &body body) ;; The function GENSYM creates GENerated SYMbols, guaranteed to ;; be unique in the whole Lisp system. Because of that, they ;; cannot capture other symbols, preventing variable capture. (let ((limit (gensym "LIMIT"))) `(do ((,limit ,stop) (,var ,start (1+ ,var))) ((> ,var ,limit)) ,@body)))) (let ((side-effects '()) (result '())) (flet ((return-0 () (push 0 side-effects) 0) (return-3 () (push 3 side-effects) 3)) (for (i (return-0) (return-3)) (push i result))) (assert-equal '(0 1 2 3) (nreverse result)) (assert-equal '(3 0) (nreverse side-effects))))) ;; didn't got on first try, ;; but yes, for gensym limit ,stop is evaluated first ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define-test for (macrolet ((for ((var start stop) &body body) ;; Fill in the blank with a correct FOR macroexpansion that is ;; not affected by the three macro pitfalls mentioned above. (let ((initial (gensym "INITIAL")) (limit (gensym "LIMIT"))) `(do* ((,initial ,start) (,limit ,stop) (,var ,initial (1+ ,var))) ((> ,var ,limit)) ,@body)))) (let ((side-effects '()) (result '())) (flet ((return-0 () (push 0 side-effects) 0) (return-3 () (push 3 side-effects) 3)) (for (i (return-0) (return-3)) (push i result))) (assert-equal '(0 1 2 3) (nreverse result)) (assert-equal '(0 3) (nreverse side-effects))))) ;; (do* ((a (return-0)) ;; (b (return-3)) ;; (i a (1+ i))) ;; ((> i b)) ;; (push i result)) ;; ;; so, my mistake was: using DO and trying to cross reference temp vars ;; and mistyping 1+ as 1_