In your previous example about the factorial, there is no reason at all to use a macro,
since the compiler can figure out perfectly well if the function is pure (ie only depends on
the arguments and doesn't access globals). So the whole factorial(x) can be calculated at
compile time if a constant is used as argument.
For unit testing, I almost never use boolean expressions in macros, so instead of (for example)
assert_equal(a == b, "a is not b");
it's much better to use
assert_equals(a, b);
assert_greater(a ,b);
because the code can then immediately print something like: expected <x>, got <y>.
There might be other cases where a boolean expression is really needed, but I can't think
of them right now.. I'll spent some time thinking about this.
When thinking about the implementation, let's talk about the macro changing variables in the caller's environment,
eg: (the example is a bit contrived, but i hope the meaning is clear)
public macro increment_b_with(x) {
b += x;
}
(In this case, b could also have been added as argument, but for the example it's not).
So when parsing the code, the macro is ok, but when it's used, there must be a variable
b in scope. If not, you ideally want a nice syntax error, like:
caller.c2:10 macro 'mod.increment_b_with' needs external identifier 'b'
One last idea is to let a developer impose some type restrictions. So for example an argument
must be type struct X. This does however come into the domain of regular functions..