1 module janet.register;
2 
3 import janet.c;
4 
5 import janet;
6 
7 import std.algorithm.searching : startsWith;
8 private enum isInternal(string field) = field.startsWith("__");
9 
10 import std.traits : hasStaticMember,isFunction,FunctionAttribute,functionAttributes,arity,Parameters;
11 
12 private template isPropertySetter(alias sym)
13     if(isFunction!sym)
14 {
15     enum isPropertySetter = (functionAttributes!sym & FunctionAttribute.property) && arity!sym == 1;
16 }
17 
18 import std.meta : anySatisfy;
19 
20 enum isSettableProperty(alias sym) = !(isFunction!sym) ||
21 anySatisfy!(isPropertySetter,__traits(getOverloads,__traits(parent,sym),__traits(identifier,sym)));
22 
23 private template GetParameterToPropertyFunc(alias sym)
24     if(isFunction!sym && isSettableProperty!sym)
25 {
26     static foreach(overload;__traits(getOverloads,__traits(parent,sym),__traits(identifier,sym)))
27     {
28         import std.traits : arity,Parameters;
29         static if(arity!overload == 1)
30         {
31             alias GetParameterToPropertyFunc = Parameters!overload[0];
32         }
33     }
34 }
35 /**
36     This gives us the ability to take a D-initialized object and pass it to Janet without
37     moving any data around in particular. The void* should be a pointer given
38     by janet_unwrap_abstract on a Janet which was earlier made by janet_wrap_abstract
39     on an object of type T. It should be used in any static functions that will be
40     expected for registerType on the void* that's sent in.
41 */
42 T getDataFromHelper(T)(void* data)
43 {
44     // though to be honest this might actually be a fantastic misunderstanding/abuse of what abstract types are
45     auto helper = *cast(JanetAbstractClassHelper!T*) data;
46     return helper.obj;
47 }
48 
49 private template defaultGetter(T)
50 {
51     extern(C) int getterFunc(void* data, Janet key,Janet* _out) //Cannot be @nogc because it might call non-@nogc functions.
52     {
53         import std.string : fromStringz;
54         immutable string keyStr = (&key).as!(string,JanetStrType.KEYWORD);
55         debug import std.stdio : writeln;
56         T realData = getDataFromHelper!(T)(data);
57         switch(keyStr)
58         {
59             static foreach(field; __traits(allMembers,T))
60             {
61                 static if(!isInternal!field &&
62                         field != "this" &&
63                         field != "Monitor")
64                 {
65                     static if(!isFunction!(mixin("T."~field)) || isSettableProperty!(mixin("T."~field)))
66                     {
67                         case field:
68                             *_out = janetWrap(mixin("realData."~field));
69                             return 1;
70                     }
71                     else
72                     {
73                         case field:
74                             *_out = janetWrap(makeJanetCFunc!(mixin("T."~field))(realData));
75                             return 1;
76                     }
77                 }
78             }
79                 default:
80                     return 0;
81             }
82     }
83     auto defaultGetter = &getterFunc;
84 }
85 
86 private template defaultPut(T)
87 {
88     extern(C) void defaultPutFunc(void* data, Janet key, Janet value)
89     {
90         string keyStr = (&key).as!(string,JanetStrType.KEYWORD);
91         T realData = getDataFromHelper!(T)(data);
92         switch(keyStr)
93         {
94             static foreach(field; __traits(allMembers,T))
95             {
96                 static if(!isInternal!field &&
97                         field != "this" &&
98                         field != "Monitor" &&
99                         isSettableProperty!(mixin("T."~field)))
100                 {
101                 /*
102                 */
103                 case field:
104                     static if(isFunction!(mixin("T."~field)))
105                     {
106                         mixin("realData."~field~" = value.as!(GetParameterToPropertyFunc!(T."~field~"));");
107                     }
108                     else
109                     {
110                         if(janetCompatible!(typeof(mixin("T."~field)))(value))
111                         {
112                             mixin("realData."~field) = value.as!(typeof(mixin("T."~field)));
113                         }
114                     }
115                     return;
116                 }
117             }
118             default:
119                 return;
120         }
121     }
122     auto defaultPut = &defaultPutFunc;
123 }
124 
125 private template janetHash(T)
126 {
127     extern(C) int janetHashFunc(void* p, size_t len) 
128     {
129         T realData = getDataFromHelper!(T)(p);
130         return cast(int)((realData.toHash%uint.max)-int.max);
131     }
132     auto janetHash = &janetHashFunc;
133 }
134 
135 private template janetCall(T)
136 {
137     extern(C) Janet janetCallFunc (void* p, int argc, Janet* argv)
138     {
139         import std.traits : arity;
140         T realData = getDataFromHelper!(T)(p);
141         return makeJanetCFunc!(realData.opCall,T)(realData)(argc,argv);
142     }
143     auto janetCall = &janetCallFunc;
144 }
145 
146 private template janetCompare(T)
147 {
148     extern(C) int janetCompareFunc(void* lhs, void* rhs)
149     {
150         auto lHelper = *cast(JanetAbstractClassHelper!T*) lhs;
151         auto rHelper = *cast(JanetAbstractClassHelper!T*) rhs;
152         return lHelper.obj.opCmp(rHelper.obj);
153     }
154     auto janetCompare = &janetCompareFunc;
155 }
156 
157 /** Allows one to register a JanetAbstractType with Janet.
158 
159     One can add static string to the class by the name of "janetPackage", which will be prepended to its class name in Janet before a '/'.
160 
161     For example: register class "Bar" with janetPackage "Foo" will result in "Foo/Bar".
162 
163     This process may be made easier in a later version.
164 
165     A registered class looks for the following functions:
166 
167     - Janet __janetGet(void* data,Janet key)
168     
169     - void __janetPut(void* data,Janet key,Janet value)
170     
171     - void __janetMarshal (void* p, JanetMarshalContext* ctx)
172     
173     - void __janetUnmarshal (void* p, JanetMarshalContext* ctx)
174     
175     - void __janetTostring (void* p, JanetBuffer* buffer)
176     
177     - int __janetGC (void* data, size_t len)
178     
179     - int __janetGCMark (void* data, size_t len)
180 
181     - Janet __janetNext (void* data, Janet key)
182 
183     - int __janetHash (void* p, size_t len)
184     
185     All of these are optional; get, put and hash will have defaults applied (see the source code for info) if either is not defined. Compare and call are automatically generated for types with opCmp and opCall respectively.
186 
187 */
188 @nogc const(JanetAbstractType)* registerType(T)()
189     if(is(T == class))
190 {
191     import core.memory : pureMalloc;
192     static if(hasStaticMember!(T,"__janetPackage"))
193     {
194         enum typeName = T.__janetPackage~"/"~T.stringof;
195     }
196     else
197     {
198         enum typeName = T.stringof;
199     }
200     const auto existingType = janet_get_abstract_type(janet_csymbolv(cast(const(char*))typeName));
201     if(existingType)
202     {
203         return existingType;
204     }
205     JanetAbstractType newType;
206     newType.name = cast(const(char*))typeName;
207     static if(hasStaticMember!(T,"__janetGet"))
208     {
209         newType.get = &T.__janetGet;
210     }
211     else
212     {
213         newType.get = defaultGetter!T;
214     }
215     static if(hasStaticMember!(T,"__janetPut"))
216     {
217         newType.get = &T.__janetPut;
218     }
219     else
220     {
221         newType.put = defaultPut!T;
222     }
223     static if(hasStaticMember!(T,"__janetMarshal"))
224     {
225         newType.marshal = &T.__janetMarshal;
226     }
227     static if(hasStaticMember!(T,"__janetUnmarshal"))
228     {
229         newType.unmarshal = &T.__janetUnmarshal;
230     }
231     static if(hasStaticMember!(T,"__janetGC"))
232     {
233         newType.gc = &T.__janetGC;
234     }
235     static if(hasStaticMember!(T,"__janetGCMark"))
236     {
237         newType.gcmark = &T.__janetGCMark;
238     }
239     static if(hasStaticMember!(T,"__janetToString"))
240     {
241         newType.tostring = &T.__janetToString;
242     }
243     static if(hasStaticMember!(T,"__janetNext"))
244     {
245         newType.next = &T.__janetNext;
246     }
247     static if(hasStaticMember!(T,"__janetHash"))
248     {
249         newType.hash = &T.__janetHash;
250     }
251     else
252     {
253         newType.hash = janetHash!T;
254     }
255     import std.traits : isCallable, isOrderingComparable;
256     static if(isCallable!T)
257     {
258         newType.call = janetCall!T;
259     }
260     static if(isOrderingComparable!T)
261     {
262         newType.compare = janetCompare!T;
263     }
264     auto typePointer = cast(JanetAbstractType*)pureMalloc(JanetAbstractType.sizeof);
265     *typePointer = newType;
266     //I am *pretty* sure the below line keeps the allocated memory from leaking.
267     //it's 64 bytes per registered type though so it's probably irrelevant
268     janet_register_abstract_type(typePointer);
269     return typePointer;
270 }
271 
272 unittest
273 {
274     import std.stdio : writeln;
275     class SmallClass
276     {
277         int justAnInt = 4;
278     }
279     writeln("Performing class wrapping memory tests.");
280     import std.range : iota;
281     foreach(int i;iota(0,10_000))
282     {
283         SmallClass boo = new SmallClass();
284         janetWrap(boo);
285     }
286     writeln("Success.");
287 }
288 
289 unittest
290 {
291     struct Bar
292     {
293         int foo;
294         void voidFunc()
295         {
296             return;
297         }
298     }
299     class TestClass
300     {
301         static immutable string __janetPackage="tests";
302         int a;
303         string b;
304         Bar bar;
305         int testFunc(int b)
306         {
307             return b + 2;
308         }
309         private int _private;
310         @property int getSetInt()
311         {
312             return _private; 
313         }
314         @property int getSetInt(int n) 
315         {
316             return _private = n;
317         }
318     }
319     import std.file;
320     import std.parallelism : task;
321     import std.stdio : writeln;
322     writeln("Performing class register test.");
323     TestClass baz = new TestClass();
324     baz.a = 5;
325     baz.b = "foobar";
326     baz.bar.foo = 10;
327     baz.getSetInt = 12;
328     assert(baz.a == 5);
329     assert(baz.b == "foobar");
330     assert(baz.bar.foo == 10);
331     import std.string : toStringz;
332     janetDef(coreEnv,"abstractTest",baz,"abstractTest");
333     Janet testJanet;
334     writeln("Checking abstractTest exists...");
335     assert(!doString(`(assert (abstract? abstractTest) "abstractTest exists")`));
336     writeln("File to be done...");
337     assert(doFile("./source/tests/dtests/register.janet",&testJanet)==0,"Abstract test errored! "~testJanet.as!string);
338     writeln("Success.");
339 }