www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - mixin string to template - advice needed

reply "Marek Janukowicz" <marek janukowicz.net> writes:
Hello

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" ));

( ... which results in ... )

    property bool hasInPort () {
     return (_wildcards & MatchWildcard.IN_PORT) > 0;
   }

    property bool hasInPort ( bool has ) {
     if (has) _wildcards |= MatchWildcard.IN_PORT;
     else _wildcards &= ~MatchWildcard.IN_PORT;
     return has;
   }

    property PortNumber inPort ()
     in {
       assert( hasInPort(), "InPort not set in wildcards" );
     }
   body {
     return _inPort;
   }

    property PortNumber inPort (PortNumber port) {
     hasInPort = true;
     _inPort = port;
     return _inPort;
   }

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")

Thank you for any help
Jul 26 2013
parent reply "anonymous" <anonymous example.com> writes:
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); }
Jul 26 2013
next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Friday, 26 July 2013 at 22:29:44 UTC, anonymous wrote:
 I've had a go at it (explanatory comments inside):
Yay! +1 for template mixin over "raw" mixins. BTW, you can simply your expression: mixin("enum flag = MatchWildcard." ~ underscorish(name) ~ ";"); By only mixing in the value itself: enum flag = mixin("MatchWildcard." ~ underscorish(name)); This improves clarity a little bit. Unfrotunatly, this doesn't seem to work for the alias line: mixin("alias field = _" ~ name ~ ";"); I'm unsure if this is a bug.
Jul 27 2013
prev sibling parent Marek Janukowicz <marek janukowicz.net> writes:
Thank for for your elaborated example - I especially liked the comments 
:)

This really shed some light for me on how much information you can get 
from template "alias" parameter. I also didn't realize you can use small 
string mixins just for small snippets that are difficult to implement 
with a regular template.

-- 
Marek Janukowicz
Jul 27 2013