Thread overview
mixin string to template - advice needed
Jul 26, 2013
Marek Janukowicz
Jul 26, 2013
anonymous
Jul 27, 2013
monarch_dodra
Jul 27, 2013
Marek Janukowicz
July 26, 2013
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
July 26, 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);
}
July 27, 2013
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.
July 27, 2013
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