I've put some thought into macros into my various cross-assemblers. One thing that's important is to have different types of macros, and different types of argument values (some that are evaluated for you, some that are intentionally not evaluated.)
macro function square(integer value) {
return value * value;
}
integer x = square(foo() + bar()); //foo() and bar() each only called once
//square(integer) will exist as an exported function symbol
macro inline twice(statement operation) {
operation;
operation;
}
twice(foo()); //invokes foo() two times
//twice() will not generate an actual function
macro class vector(type T) {
T* pool;
integer capacity;
integer size;
function append(type U) { ... }
...
}
vector(integer) powersOfTwo{2, 4, 8, 16, 32, 64};
//any vector(type) will generate all the class functions for it