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 }