IBM S/370 Macro Assembler also was very feature-rich. I needed to have modules be loaded into the kernel in a certain order, so I implemented sorting of symbolic names in macro-asm (had to use loops at the macro level if I recall), and then assembly code would be emitted clean, already in the right sequence.
DCB was the same magic incantation as #include <stdio.h>
Excessively using macros in assembler, just as in any language, amounts to writing a translator (code generator); problem, then, is that the language being translated from is usually crap, and the language being used to write the translator (the macro language) is also usually rather limited.
What ad-hoc code generation lacks, as a rule, is type constraints and structured expression(syntax and semantics). If it has those, it is bordering on being a complete compiler.
But if your base environment doesn't have notions of types or structured expressions either, which is mostly true of assemblers, then you are free to use macros to program at a higher level. The expressive power you gain by leveraging macros doesn't have a real downside because the core language is already so limited that it won't be more legible or maintainable to do it by hand.
Hence why PC Assembly was so much more fun than dealing with it on UNIX derived platforms.