Macros must be staged - similarly to a good practice of not having too big functions, macros also must be small, and must transform code in small steps, with more macros further breaking the result down. Ideally, each macro must be a trivial rewrite doing only one small thing.
Another trick is to have a bunch of macros that would inject debugging output when enabled. Wrap every macro definition body in such a macro, and, with debugging turned on (can be selective), both source and output would be displayed. Another macro can be used to inject debugging info into a generated code.
I usually start writing macros the other way around - not from a code I'd like to see generated, but from a final form. Once I find the code with macros passable, I'll start implementing the macros, in small steps.
Same applies to a DSL design in general - first I write a code I want to see, and only then I fill in an implementation.
Macros must be staged - similarly to a good practice of not having too big functions, macros also must be small, and must transform code in small steps, with more macros further breaking the result down. Ideally, each macro must be a trivial rewrite doing only one small thing.
Another trick is to have a bunch of macros that would inject debugging output when enabled. Wrap every macro definition body in such a macro, and, with debugging turned on (can be selective), both source and output would be displayed. Another macro can be used to inject debugging info into a generated code.
I usually start writing macros the other way around - not from a code I'd like to see generated, but from a final form. Once I find the code with macros passable, I'll start implementing the macros, in small steps.
Same applies to a DSL design in general - first I write a code I want to see, and only then I fill in an implementation.