What is preventing a language like C from having Lisp macros? At what point in the compilation process does C forego the ability to manipulate its code tree?
And, is this specifically an interpreted vs. compiled issue?
Yes, you can have Lisp-like macros in an imperative language, because Lisp supports imperative programming. The main difference between macros in C and Lisp is how easy it is to manipulate the source tree:
In C, there are declarations, declarators, statements, expressions, blocks, a handful of distinct control structures, labels, etc. New syntactic constructs might require changes to the parser. Macros will need to construct these data structures.
In Lisp, there are only s-expressions. New syntactic constructs require no changes to the parser. Only one data structure means the API for constructing a syntax tree is very simple and easy to remember.
There are some languages with more complicated syntax (like C) but which have powerful macro facilities (like Lisp). For example, Haskell. However, the interface for writing macros in Haskell is somewhat more complicated, since you need functions for creating and applying type constructors, expressions, declarations, expressions, etc., instead of just a single constructor for lists.
A template in a macro in Haskell has its type annotated:
[e| ... |] -- expression
[d| ... |] -- declaration
[t| ... |] -- type
[p| ... |] -- pattern
By comparison, those letters e
, d
, t
, and p
are not needed in Lisp macros. These are necessary in Haskell not because Haskell is strongly typed, but because the annotations put the parser in the correct state so it can parse the contents with the proper context. Again, the Lisp syntax only has one context.
Most languages can be interpreted, compiled, or both at the same time. C can be either or both. Lisp can be either or both. Macros require the compiler to execute code at compile-time, which can be done either by interpreting the macro code, or by compiling the macro and then executing it. So interpreted-versus-compiled is really a non-issue (it is a non-issue in almost every discussion about languages).
Rust, which is certainly a C-like language for some definitions of "C-like", has a Scheme-like macro system.
Haskell has typed macros which are as powerful:
http://www.haskell.org/ghc/docs/7.0.2/html/users_guide/template-haskell.html
In the case of C, I think it stems from the fact that C's design tries to keep things simple, so that a program's semantics is easy to understand (unlike C++ with its many features that allows creation of DSLs)
You could use some more powerful preprocessor with C, for instance gpp can be used as a more powerful cpp
replacement while staying quite compatible with it.
But gpp
, like cpp
works on textual representations, not on abstract syntax trees.
You could customize your C compiler (in particular, GCC): for instance by extending GCC with MELT - you can add your own builtins and pragmas and change optimizations in the compiler.
With MELT you mostly work on the tree and Gimple internal representations inside GCC.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With