1 /**
2  * Defines DuckPointer, which allows quacking without templates.
3  */
4 module quack.pointer;
5 import quack.extends, quack.mixins;
6 
7 import tested;
8 
9 /**
10  * Convience wrapper for creating a DuckPointer.
11  */
12 template duck( Pointee ) if( isExtendable!Pointee )
13 {
14     DuckPointer!Pointee duck( Pointer )( Pointer* p )
15     {
16         return DuckPointer!Pointee( p );
17     }
18 }
19 
20 /**
21  * Pointer to any type that `extends` Pointee.
22  *
23  * To make a DuckPointer for a mixin, it's recommended to define a struct
24  * that only implements the mixin.
25  *
26  * Params:
27  *  Pointee =           The type the pointee refers to.
28  */
29 template DuckPointer( Pointee ) if( isExtendable!Pointee )
30 {
31     struct DuckPointer
32     {
33     public:
34         mixin( ImplementMembers );
35 
36         this( Pointer )( Pointer* p )
37         {
38             impl = cast(void*)p;
39             destroyer = ptr => typeid(Pointer).destroy( ptr );
40 
41             // Assign pointers
42             foreach( member; __traits( allMembers, Pointee ) )
43             {
44                 import std.algorithm: among;
45                 import std.traits: isSomeFunction;
46                 static if( !member.among( "this", "~this" ) )
47                 {
48                     static if( __traits( hasMember, this, "__" ~ member ) )
49                     {
50                         // We store a pointer, so store that.
51                         mixin( "this.__" ~ member ) = &__traits( getMember, p, member );
52                     }
53                     else
54                     {
55                         // No pointer, assign the reference directly.
56                         mixin( "this." ~ member ) = &__traits( getMember, p, member );
57                     }
58                 }
59             }
60         }
61 
62         ~this()
63         {
64             destroyer( impl );
65         }
66 
67     private:
68         void* impl;
69         void function( void* ) destroyer;
70     }
71 
72     // The code to implement the pass throughs.
73     private enum ImplementMembers = {
74         string members = "";
75 
76         foreach( member; __traits( allMembers, Pointee ) )
77         {
78             import std.algorithm: among;
79             import std.string: replace;
80             static if( !member.among( "this", "~this" ) )
81             {
82                 // If member is a field, this'll fail citing a need for a `this` pointer.
83                 static if( __traits( compiles, &__traits( getMember, Pointee, member ) ) )
84                 {
85                     members ~= "public " ~ typeof( &__traits( getMember, Pointee, member ) ).stringof.replace( "function", "delegate" ) ~
86                                 " " ~ member ~ ";";
87                 }
88                 else
89                 {
90                     // If it's a var, store a pointer to it, and return a reference to the dereferenced pointer.
91                     members ~= "private " ~ typeof( __traits( getMember, Pointee, member ) ).stringof ~ "* __" ~ member ~ ";";
92                     members ~= "public @property ref " ~ typeof( __traits( getMember, Pointee, member ) ).stringof ~ " " ~ member ~ "() {
93                         return *__" ~ member ~ "; }";
94                 }
95             }
96         }
97 
98         return members;
99     } ();
100 }
101 ///
102 @name( "DuckPointer Structs" )
103 unittest
104 {
105     struct IFace1
106     {
107         int getX() { return 12; }
108     }
109     struct Struct1
110     {
111         int x;
112         int getX() { return x; }
113     }
114 
115     Struct1 s1;
116     s1.x = 42;
117     DuckPointer!IFace1 ptr1 = duck!IFace1( &s1 );
118     assert( ptr1.getX() == 42 );
119 }
120 ///
121 @name( "DuckPointer Template Mixin" )
122 unittest
123 {
124     struct MyMixinImpl
125     {
126         mixin MyMixin!();
127     }
128     alias MixinPtr = DuckPointer!MyMixinImpl;
129 
130     struct Struct1
131     {
132         mixin MyMixin!();
133     }
134 
135     Struct1 s1;
136     s1.x = 42;
137     MixinPtr ptr1 = MixinPtr( &s1 );
138     assert( ptr1.x == 42 );
139     assert( ptr1.getX() == 42 );
140     ++ptr1.x;
141     assert( ptr1.x == 43 );
142     assert( ptr1.getX() == 43 );
143     assert( s1.x == 43 );
144 }
145 version( unittest )
146 private mixin template MyMixin()
147 {
148     int x;
149     int getX() { return x; }
150 }
151 ///
152 @name( "DuckPointer String Mixin" )
153 unittest
154 {
155     enum myMixin = q{
156         int x;
157         int getX() { return x; }
158     };
159     struct MyMixinImpl
160     {
161         mixin( myMixin );
162     }
163     alias MixinPtr = DuckPointer!MyMixinImpl;
164 
165     struct Struct1
166     {
167         mixin( myMixin );
168     }
169 
170     Struct1 s1;
171     s1.x = 42;
172     MixinPtr ptr1 = MixinPtr( &s1 );
173     assert( ptr1.x == 42 );
174     assert( ptr1.getX() == 42 );
175     ++ptr1.x;
176     assert( ptr1.x == 43 );
177     assert( ptr1.getX() == 43 );
178     assert( s1.x == 43 );
179 }