1 module decorated;
2 
3 import std.traits : isCallable;
4 
5 private enum isDecoratedName(string str) = {
6     import std.ascii : isDigit;
7     import std.uni : isAlpha;
8 
9     alias alpha = (in dchar c) => c == '_' || isAlpha(c);
10     alias alphanum = (in dchar c) => alpha(c) || isDigit(c);
11 
12     if (!str.length || isDigit(str[0])) return false;
13 
14     foreach (dchar dc; str) if (!alphanum(dc)) return false;
15 
16     return true;
17 } ();
18 private enum isDecoratedFun(alias smb) = isCallable!smb || __traits(isTemplate, smb);
19 
20 struct decor(alias fun, args...) if(isDecoratedFun!fun) {}
21 
22 mixin template decorated(string name, alias fun)
23     if (isDecoratedName!name && isDecoratedFun!fun)
24 {
25     auto _internal_helper_(Args...)(Args args)
26     {
27         auto impl(attrs...)()
28         {
29             static if (attrs.length)
30             {
31                 alias next = impl!(attrs[1..$]);
32                 static if (is(attrs[0] : decor!Args, Args...))
33                 {
34                     static if (__traits(isTemplate, Args[0]))
35                     {
36                         import std.meta : Instantiate;
37                         import std.traits : ReturnType;
38                         alias call = Instantiate!(Args[0], ReturnType!next);
39 
40                         static if (Args.length > 1)
41                             return call(Args[1..$])(next);
42                         else
43                             return call(next);
44                     }
45                     else
46                     {
47                         static if (Args.length > 1)
48                             return Args[0](Args[1..$])(next);
49                         else
50                             return Args[0](next);
51                     }
52                 }
53                 else return attrs[0](next);
54             }
55             else
56             {
57                 // if it's a lambda we must return 'fun'
58                 static if (__traits(compiles, { return &fun; }))
59                     return &fun;
60                 else
61                     return fun;
62             }
63         }
64 
65         auto call = impl!(__traits(getAttributes, _internal_helper_))();
66         static if (isCallable!(typeof(call)))
67         {
68             import std.traits : ParameterDefaults;
69             static if (args.length)
70             {
71                 import core.lifetime : forward;
72                 return call(forward!args, ParameterDefaults!fun[args.length .. $]);
73             }
74             else return call(ParameterDefaults!fun);
75         }
76         else return call;
77     }
78 
79     mixin("alias "~name~" = _internal_helper_;");
80 }
81 
82 @safe pure nothrow unittest
83 {
84     auto myMap(F)(F f)
85     {
86         import std.algorithm : map;
87         return f().map!"a + 1";
88     }
89 
90     // https://issues.dlang.org/show_bug.cgi?id=22694
91     struct S {
92         import std.algorithm : filter, sum;
93 
94         @sum
95         @filter!"a & 1"
96         @myMap
97         mixin decorated!("foo",
98         {
99             import std.range : iota;
100             import std.array : array;
101             return 10.iota.array;
102         });
103     }
104 
105     with (S.init)
106     {
107         assert(foo() == 25);
108     }
109 }
110 
111 @safe pure nothrow unittest
112 {
113     auto hello(F)(F f)
114     {
115         return () {
116             return "Hello "~f();
117         };
118     }
119 
120     auto say(T)(string text)
121     {
122         return (T t) {
123             return () {
124                 return text~" "~t();
125             };
126         };
127     }
128 
129     // https://issues.dlang.org/show_bug.cgi?id=22694
130     struct S
131     {
132         @hello
133         mixin decorated!("foo",
134         {
135             import std.stdio : writeln;
136             return "Decorated";
137         });
138 
139         @decor!(say, "D rox:")
140         @hello
141         mixin decorated!("bar",
142         {
143             return "Decorated";
144         });
145 
146         auto oldBaz() { return "Decorated"; }
147         @decor!(say, "D rox:")
148         @hello
149         mixin decorated!("baz", oldBaz);
150     }
151 
152     with (S.init)
153     {
154         assert(foo() == "Hello Decorated");
155         assert(bar() == "D rox: Hello Decorated");
156         assert(baz() == "D rox: Hello Decorated");
157     }
158 }
159 
160 @safe nothrow unittest
161 {
162     auto cache(F)(F f)
163     {
164         import std.functional : memoize;
165         alias mem = memoize!f;
166         return &mem;
167     }
168 
169     // https://issues.dlang.org/show_bug.cgi?id=22694
170     struct S
171     {
172         static size_t expensive; // simulates an expensive algorithm
173 
174         @cache
175         mixin decorated!("cached", (size_t n = 1)
176         {
177             expensive++; // simulates an expensive algorithm
178             return expensive;
179         });
180     }
181 
182     with (S.init)
183     {
184         // runs expensive stuff
185         assert(cached(46) == 1);
186         assert(cached(45) == 2);
187 
188         // skips the expensive stuff
189         assert(cached(46) == 1);
190         assert(cached(45) == 2);
191 
192         // can be used with default arguments
193         assert(cached() == 3);
194     }
195 }