What are AST Macros?

David Bennett davidbennett at bravevision.com
Fri Apr 13 11:54:12 UTC 2018


On Friday, 6 April 2018 at 20:33:10 UTC, Chris Katko wrote:
> Sorry if this is "re-opening" an old thread, but did anything 
> come from this and DIP50? It seems like a really interesting 
> concept and this thread was one of the first results for a 
> Google search.
>
> Thanks.

Thanks for reminding me about this thread, I thought I would see 
how close i could get to having this work now that I know D's 
edges better:

On Sunday, 11 July 2010 at 13:29:36 UTC, Michel Fortin wrote:
>
> That said, I don't feel like I'm cheating when using string 
> mixins. I find them a quite good substitute to AST macros. And 
> perhaps string mixins are simpler too: you don't have to learn 
> a new macro syntax, you just manipulate strings. Though I'm 
> still waiting for the day we can use string mixins in 
> expressions to do things like this:
>
> 	int num = 1;
> 	string result = substitute!"Number: $num";
> 	assert(result == "Number: 1");

So what i came up with was:

--- substituter.d
module substituter;

string substituteForMixin(string input){

     input ~= ` `;
     string output = ``;
     size_t l= input.length-1;

     for(size_t i=0; i<l; i++){
         if(input[i]=='#' && input[i+1]=='{'){
             output ~= (i==0) ? `to!string(` : `"~to!string(`;
             i+=2;
             while(input[i] != '}'){
                 output ~= input[i++];
             }
             output ~= (i==l-1) ? `)` : `)~"`;
         }else{
             if(i==0) output ~= `"`;
             output ~= input[i];
             if(i==l-1) output ~= `"`;
         }
     }
     return output;
}

mixin template Substituter(){
     string substitute(alias string ident)(){
         import std.conv : to;
         mixin(`return `~substituteForMixin(ident)~`;`);
     };
}

@safe unittest{

     mixin Substituter;

     int number = 42;

     assert(
         substitute!("number is #{number}")
         == "number is 42"
     );
     assert(
         substitute!("#{number+1} is the result of number+1")
         == "43 is the result of number+1"
     );
     assert(
         substitute!("you get #{number*number} when you square 
number")
         == "you get 1764 when you square number"
     );
}
---

I think thats as clean as I can make it currently. Also I'm using 
#{} syntax to allow basic expressions and converting to!string on 
everything.

Not sure if this syntax is documented but you can use enum like:

enum wrapmixin(alias string s) = {mixin(s)};
enum wrapmixin(alias string s){mixin(s)};

And the mixin() is run at the time the enum is called. The only 
problem with this though is the lambda/function is defined in the 
scope enum was defined not where the enum was called. So I think 
its just sugar for:

template wrapmixin(alias string s){alias wrapmixin = {mixin(s);}}
template wrapmixin(alias string s){auto wrapmixin(){mixin(s);}}

To work around this I used a mixin template to work around the 
scope issue.

Now to bring us back to 2018, I think that this enum version of 
the code should inject the function/lambda into the scope of it's 
caller. That would allow us to do a lot of cool things.

Also the other idea I had was to have mixin functions that only 
take compiletime args (they are UFCS-able though, unlike 
templates) and mixin themselves when called like:

---
mixin add1Xtimes(alias int a, alias int t){
     uint i=t;
     do{a++;}while(--i);
     return a;
}
@safe unittest{
     uint n=0;
     n.add1Xtimes!(4).add1Xtimes!(6);
     assert(n==10);
}
---

And that becomes:

---
@safe unittest{
     uint n=0;
     uint __m0_i=4;
     do{n++;}while(--__m0_i);
     uint __m1_i=6;
     do{n++;}while(--__m1_i);
     assert(n==10);
}
---

Notice that if the return is never used it's stripped out.

I haven't really given much thought to the implementation of this 
though.



More information about the Digitalmars-d mailing list