1 /*
2 MIT License
3 Copyright (c) 2017 Boris Barboris
4 */
5 
6 module daii.closure;
7 
8 import std.conv: to;
9 import std.experimental.allocator: make, dispose;
10 import std.experimental.allocator.mallocator: Mallocator;
11 import std.functional: forward;
12 
13 import daii.refcounted;
14 import daii.utils;
15 
16 
17 /// Abstract callable object.
18 abstract class Closure(Ret, Args...)
19 {
20     Ret call(Args args) @trusted;
21 }
22 
23 template AllocationContext(Allocator = Mallocator, bool Atomic = true)
24 {
25     template CtxRefCounted(T)
26     {
27         alias CtxRefCounted = RefCounted!(T, Atomic, Allocator);
28     }
29 
30     /// RAII wrapper for callable closure.
31     struct Delegate(Ret, Args...)
32     {
33         @disable this();
34 
35         private this(CtxRefCounted!(Closure!(Ret, Args)) clos) @safe
36         {
37             _closure = clos;
38         }
39 
40         CtxRefCounted!(Closure!(Ret, Args)) _closure;
41 
42         bool opEquals(const ref Delegate!(Ret, Args) s) const @safe @nogc
43         {
44             return _closure.v is s._closure.v;
45         }
46 
47         bool opEquals(const Delegate!(Ret, Args) s) const @safe
48         {
49             return _closure.v is s._closure.v;
50         }
51 
52         Ret opCall(Args args) @safe
53         {
54             return _closure.v.call(forward!args);
55         }
56     }
57 
58     /// Bread and butter
59     auto autodlg(ExArgs...)(ExArgs exargs) @safe
60     {
61         static if (isStaticAllocator!Allocator)
62             enum f_idx = 0; // index of function in exargs
63         else
64         {
65             static assert(isAllocator!(ExArgs[0]));
66             enum f_idx = 1; // 0 is allocator instance
67         }
68         static assert(ExArgs.length > f_idx);
69         static assert(isFunctionPointerType!(ExArgs[f_idx]));
70 
71         // return type of the delegate
72         alias RetType = ReturnType!(ExArgs[f_idx]);
73         alias AllArgs = ParamTypes!(ExArgs[f_idx]);
74 
75         static assert(AllArgs.length >= (ExArgs.length - 1 - f_idx));
76 
77         // Actual type of a delegate is deduced here. Arguments of function
78         // passed by programmer are reduced until all captured variables are
79         // bound to respective parameters in the function.
80 
81         // This is how many arguments are in the resulting delegate type opCall.
82         enum int dlgArgCount = AllArgs.length - ExArgs.length + 1 + f_idx;
83 
84         // Deleagte argument types are taken from the function pointer
85         alias DlgArgs = Take!(dlgArgCount, AllArgs);
86 
87         // Captured variable types are taken from, surprise, captured variables,
88         // passed after the function in exargs.
89         alias CapturedArgs = Skip!(1 + f_idx, ExArgs);
90 
91         static class CClosure: Closure!(RetType, DlgArgs)
92         {
93             RetType function(AllArgs) _f;
94 
95             // captured variables are mixed-in inside class body
96             mixin(fieldExpand!(CapturedArgs.length, "CapturedArgs"));
97 
98             this(RetType function(AllArgs) f, CapturedArgs cpt) @safe
99             {
100                 _f = f;
101                 foreach (i, field; CapturedArgs)
102                 {
103                     // captured fields are named field0, field1 ...
104                     mixin("this.field" ~ to!string(i)) = cpt[i];
105                 }
106             }
107 
108             override RetType call(DlgArgs args) @trusted
109             {
110                 static if (CapturedArgs.length > 0)
111                 {
112                     enum string captee_fields =
113                         fieldsToParamListStr(CapturedArgs.length);
114                     return _f(forward!args, mixin(captee_fields));
115                 }
116                 else
117                     return _f(forward!args);
118             }
119         }
120 
121         // reference-counted closure is constructed...
122         CtxRefCounted!(Closure!(RetType, DlgArgs)) rfc =
123             CtxRefCounted!(CClosure).make(exargs);
124         // and passed to the delegate
125         Delegate!(RetType, DlgArgs) dlg = Delegate!(RetType, DlgArgs)(rfc);
126         return dlg;
127     }
128 }
129 
130 unittest
131 {
132     alias Dlg = AllocationContext!(Mallocator, true).Delegate!(void, int);
133     static int counter = 0;
134     static int ccount = 0;
135     class CClosure: Closure!(void, int)
136     {
137         this() { ccount++; }
138         ~this() { ccount--; }
139         override void call(int v) { counter++; }
140     }
141     {
142         RefCounted!(Closure!(void, int)) rfc = RefCounted!CClosure.make;
143         Dlg dlg = Dlg(rfc);
144         dlg(3);
145         dlg(-1);
146         assert(counter == 2);
147         assert(ccount == 1);
148     }
149     assert(ccount == 0);
150 }
151 
152 unittest
153 {
154     alias Ctx = AllocationContext!(Mallocator, true);
155     alias Dlg = Ctx.Delegate!(void, int);
156     int sum = 0;
157     int[] arr = [3, 5, 1, 9, 4];
158     void map(int[] arr, Dlg dlg)
159     {
160         for (int i = 0; i < arr.length; i++)
161             dlg(arr[i]);
162     }
163     Dlg d = Ctx.autodlg((int x, int* s) { *s += x; }, &sum);
164     map(arr, d);
165     assert(sum == 22);
166 }
167 
168 unittest
169 {
170     alias Ctx = AllocationContext!(Mallocator, true);
171     alias Dlg = Ctx.Delegate!(int, int, int);
172     int[] arr = [3, 5, 1, 9, 4];
173     int reduce(D)(int[] arr, D dlg)
174     {
175         int res = dlg(arr[0], arr[1]);
176         for (int i = 2; i < arr.length; i++)
177             res = dlg(res, arr[i]);
178         return res;
179     }
180     Dlg d = Ctx.autodlg((int x, int y) => x + y);
181     int sum = reduce(arr, d);
182     assert(sum == 22);
183 }
184 
185 unittest
186 {
187     alias Ctx = AllocationContext!(Mallocator, true);
188     alias Dlg = Ctx.Delegate!(void);
189     Dlg d = Ctx.autodlg((){});
190     d();
191 }
192 
193 unittest
194 {
195     alias Ctx = AllocationContext!(Mallocator, true);
196     alias Dlg = Ctx.Delegate!(int, int, float);
197     Dlg d = Ctx.autodlg((int x, float y){ return 3;});
198     d(4, 4.0f);
199 }
200 
201 import std.experimental.allocator.showcase;
202 
203 unittest
204 {
205     alias AllocType = StackFront!(4096, Mallocator);
206     AllocType al;
207     alias Ctx = AllocationContext!(AllocType*, true);
208     alias Dlg = Ctx.Delegate!(int, int, float);
209     Dlg d = Ctx.autodlg(&al, (int x, float y){ return 3;});
210     d(4, 4.0f);
211 }