What curious newbies might miss from discussions of macros is that they are a separate language operating at compile time. They are usually advertised as code operating on itself or something like that. They're not. Powerful, yes, but not that cool.

Your comment is true about most languages, but false about Lisp.

Lisp has no compile time vs run time distinction. It only has lists. What is done with a list depends on what the first thing in the list is determined to be:

1. Function - evaluate the arguments then pass it to the function.

2. Macro - manipulate the list according to the macro and then get another list.

3. Special Form - follow the special rules for the special form. (The list of special forms varies from Lisp to Lisp, and are the elementary building blocks from which the language was built.)

In Lisp, macros are advertised as code operating on itself, because that is exactly what they are. And yes, they are exactly that cool.

Every reference on Lisp macros says that they are executed at compile-time.

From http://www.paulgraham.com/icad.html: There is no real distinction between read-time, compile-time, and runtime. You can compile or run code while reading, read or run code while compiling, and read or compile code at runtime.

That is from one of the founders of YCombinator, the author of http://www.paulgraham.com/onlisptext.html and http://www.paulgraham.com/acl.html.

Then today you learn that Paul Graham is a tool.

There are Lisps that improve on the situation. In particular, vau calculi/fexprs are worth examining. http://lambda-the-ultimate.org/node/4093

There are also systems like Forths, where the compiler and interpreter really are the same system, in two different modes of operation. In these systems, there is only runtime; compiletime is a specific style of runtime.

Could you expand on how Forth differs from Lisp? I don't know Forth.

In Forth, the interpreter reads tokens. By default, each token is read and then executed. There is a token that switches the interpreter into "compiling" mode. When in compiling mode, each token is read and then used to instruct the compiler to emit code representing the token. There is a traditional and beautiful interplay [0] between the Forth interpreter and its initial stream of tokens, as the tokens customize the interpreter and compiler by augmenting their behaviors.

The main contrast that I would draw between Forths and Lisps is syntax. Forths don't really have syntax; they have token-parsing streams. Lisps are extremely tree-oriented, but Forths are stack-oriented.

[0] https://github.com/nornagon/jonesforth