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