> but I don’t buy the argument that “real” macros are only able in s-exprs
Metaprogamming can be done also semantically in other languages. Elixir for example has also macros and a good part of the core of the language is written with them. When writing a macro you then get an AST in input (represented with tuples in Elixir) and give another AST as output. I think Rust has something similar.
The reason why s-exps are better to write macros is that the AST has the same form has the code you write in the editor. This way textual code and code as data have the same representation, making it easier to reason and manipulate the code.
Metaprogamming can be done also semantically in other languages. Elixir for example has also macros and a good part of the core of the language is written with them. When writing a macro you then get an AST in input (represented with tuples in Elixir) and give another AST as output. I think Rust has something similar.
The reason why s-exps are better to write macros is that the AST has the same form has the code you write in the editor. This way textual code and code as data have the same representation, making it easier to reason and manipulate the code.