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