Andy Freeman <anamax@earthlink.net> wrote:
+---------------
| Macros don't just control evaluation.
|
| Macros "just" write code. That code may evaluate things that appeared
| as arguments to the macro, but it may also include code that wasn't in
| the macro call. That code may define names that were macro arguments.
|
| Macros let a programmer write shorter and clearer than was possible
| otherwise.
+---------------
In particular, macros give you access to the compile-time environment,
so you can do certain kinds of bookkeeping *once* at compile time and
have the results compiled into your program. That is, not only can you
write in a more declarative style, you can have said declarations link
themselves into your chosen application data structures at compile time!
I won't repeat the whole story again here, since I've told it several
times before [search for "FOCAL macros rpw3 Warnock" (without the quotes)
to find a few], but I once used macros in MACRO-10 assembler (for the
DEC PDP-10) to do some very complicated pre-processing for the lexer
for an implementation of the FOCAL interactive programming language.
Various macro calls that declared pieces of the language syntax were
sprinkled throughout the code, and these macro calls cooperated with
one another to build a *single* final highly-compacted data structure
which was dropped into the source of the program as initialization
statements at the very end. This data structure was then used at runtime
to provide an extremely efficient lexer/parser for FOCAL commands and
arithmetic expressions.
The same task would be trivial to do today with CL macros; it was
possible in 1970 with MACRO-10 only because the macros in the latter
were Turing-complete at compile time. As I've said before:
Macros in MACRO-10 could do compile-time looping and branching,
could tear apart lists of arguments and individual arguments
(considered as lists of characters), could generate new symbols
based on arguments provided, could define/set/modify compile-time
variables (including those whose names were generated by macros),
and since MACRO-10 was a two-pass assembler that provided the ".if1"
and ".if2" tests, macros could gather information during pass one
(storing it in compile-time variables/symbols) and during pass two
emit local code chosen based on the *global* contents of the program!
Sound familiar?!? ;-} ;-}
Or for a much simpler, more "Lispy" example, consider the following
macro used in the CMUCL compiler (file "unix.lisp") for generating
enums & bitmasks:
> (defmacro def-enum (inc cur &rest names)
(flet ((defform (name)
(prog1 (when name `(defconstant ,name ,cur))
(setf cur (funcall inc cur 1)))))
`(progn ,@(mapcar #'defform names))))
DEF-ENUM
> (macroexpand '(def-enum + 3 foo bar baz)) ; simple C-style enum
(PROGN
(DEFCONSTANT FOO 3)
(DEFCONSTANT BAR 4)
(DEFCONSTANT BAZ 5))
T
> (macroexpand '(def-enum ash 1 foo bar baz)) ; "enum" of bitmasks
(PROGN
(DEFCONSTANT FOO 1)
(DEFCONSTANT BAR 2)
(DEFCONSTANT BAZ 4))
T
> (defun rsh8 (x &optional y)
(declare (ignore y))
(ash x -8))
> (macroexpand '(def-enum rsh8 #xff000000
byte3-mask byte2-mask byte1-mask byte0-mask))
(PROGN
(DEFCONSTANT BYTE3-MASK 4278190080)
(DEFCONSTANT BYTE2-MASK 16711680)
(DEFCONSTANT BYTE1-MASK 65280)
(DEFCONSTANT BYTE0-MASK 255))
T
>
-Rob
-----
Rob Warnock <rpw3@rpw3.org>
627 26th Avenue <URL:http://rpw3.org/>
San Mateo, CA 94403 (650)572-2607