mixin string to template - advice needed

anonymous anonymous at example.com
Fri Jul 26 15:29:43 PDT 2013


On Friday, 26 July 2013 at 20:33:29 UTC, Marek Janukowicz wrote:
> I have a repetitive piece of code that I'm now generating using 
> string mixin. I humbly ask someone more skilled with D to 
> review the code and help me transforming it into regular 
> template mixin (as I would gladly avoid string mixin if 
> possible).
>
> string flaggedAttr(string type, string name, string capName, 
> string upName ) {
>
>   return "
>     @property bool has" ~ capName ~" () {
>       return (_wildcards & MatchWildcard." ~ upName ~") > 0;
>     }
>
>     @property bool has" ~ capName ~ " ( bool has ) {
>       if (has) _wildcards |= MatchWildcard." ~ upName ~ ";
>       else _wildcards &= ~MatchWildcard." ~ upName ~ ";
>       return has;
>     }
>
>     @property " ~ type ~ " " ~ name ~ " ()
>       in {
>         assert( has" ~ capName ~ "(), \"" ~ capName ~ " not set 
> in wildcards\" );
>       }
>     body {
>       return _" ~ name ~ ";
>     }
>
>     @property " ~ type ~ " " ~ name ~ " (" ~ type ~ " val) {
>       has" ~ capName ~ " = true;
>       _" ~ name ~ " = val;
>       return _" ~ name ~ ";
>     }
>     ";
> }
>
> ( ... and the in a struct ... )
>
> mixin(flaggedAttr( "PortNumber", "inPort", "InPort", "IN_PORT" 
> ));
[...]
> The problems I struggle to solve:
> * dynamic method names (like "hasInPort" created from argument 
> "inPort")
> * different versions of argument string (eg. "InPort" and 
> "IN_PORT" from argument "inPort")
> * getting value from enum (MatchWildcard) give the name of the 
> value (eg. "IN_PORT")

I've had a go at it (explanatory comments inside):

import std.array: front;
import std.conv: to;
import std.range: drop;

/* You can pass the field via a template alias parameter. Then 
use typeof to
get its type, and stringof to get its name: */
mixin template flaggedAttr(alias field)
{
     alias Type = typeof(field);
     static assert(field.stringof.front == '_');
     enum name = drop(field.stringof, 1); /* dropping the 
underscore */

     /* Generating "InPort" from "inPort" is trivial: capitalize 
the first
     character: */
     enum capName = name.front.toUpper.to!string ~ drop(name, 1);

     /* To generate "IN_PORT", I wrote a little function 
'underscorish' (awful
     name; implementation further down).
     You can heavily reduce the amount of string mixin by using it 
only to
     translate between names of the surrounding scope and local 
names. */
     mixin("enum flag = MatchWildcard." ~ underscorish(name) ~ 
";");
     mixin("alias field = _" ~ name ~ ";");
     /* Now, method implementations just use 'flag' and 'field'.
     Don't worry about name clashes. Every mixin has its own 
namespace. */

     @property bool has()
     {
         return (_wildcards & flag) > 0;
     }

     @property bool has(bool has)
     {
         if (has) _wildcards |= flag;
         else _wildcards &= ~flag;
         return has;
     }

     @property Type prop()
     in {
         assert(has(), capName ~ " not set in wildcards");
     }
     body {
         return field;
     }

     @property Type prop(Type val)
     {
         has = true;
         field = val;
         return field;
     }

     /* Making the implementations known by the desired names: */
     mixin("alias has" ~ capName ~ " = has;");
     mixin("alias " ~  name ~  " = prop;");
}

import std.uni: isUpper, toUpper;
private char[] underscorish(const(char)[] s)
{
     char[] result;
     foreach(dchar c; s)
     {
         if(isUpper(c)) result ~= '_';
         result ~= toUpper(c);
     }
     return result;
}
unittest
{
     assert(underscorish("fooBarBaz") == "FOO_BAR_BAZ");
}

/* Testing the whole thing: */
enum MatchWildcard
{
     IN_PORT = 1,
     FOO_BAR = 1 << 1,
}
struct S
{
     private uint _wildcards = 0;

     alias PortNumber = uint;
     private PortNumber _inPort;
     mixin flaggedAttr!_inPort;

     private uint _fooBar;
     mixin flaggedAttr!_fooBar;
}
void main()
{
     S s;

     assert(!s.hasInPort);
     s.inPort = 123;
     assert(s.inPort == 123);
     assert(s.hasInPort);

     assert(!s.hasFooBar);
     s.fooBar = 321;
     assert(s.fooBar == 321);
     assert(s.hasFooBar);
}


More information about the Digitalmars-d-learn mailing list