1 module janet.register;
2 
3 import janet.c;
4 
5 import janet.d;
6 
7 private template isInternal(string field) // grabbed up from LuaD
8 {
9 	enum isInternal = field.length >= 2 && field[0..2] == "__";
10 }
11 
12 import std.traits : hasStaticMember,isFunction;
13 
14 private template defaultGetter(T)
15 {
16     alias GetterFunc = Janet function (void* data, Janet key);
17     extern(C) Janet getterFunc(void* data, Janet key)
18     {
19         if(key.type != JanetType.JANET_KEYWORD && key.type != JanetType.JANET_STRING)
20         {
21             return janet_wrap_nil();
22         }
23         import std.string : fromStringz;
24         string keyStr = fromJanetString(key);
25         /*
26             While it's fresh in my mind:
27             the void* passed in is a pointer to a JanetDAbstractHead!T's data member,
28             which is a union of a long[] and an object of class T.
29             As objects of classes are pass-by-reference, this object is actually a pointer, and can be accessed as such with a void*.
30             Since we know that the data at the union is a memory address, we can convert it to a void**, then treat the data there
31             as an object of class T.
32         */
33         T realData = cast(T)*(cast(void**)data);
34         switch(keyStr)
35         {
36             static foreach(field; __traits(allMembers,T))
37             {
38                 static if(!isInternal!field &&
39                         field != "this" &&
40                         field != "Monitor")
41                 {
42                     static if(isFunction!(mixin("T."~field)))
43                     {
44                         case field:
45                             return janetWrap(makeJanetCFunc!(mixin("realData."~field))(&mixin("realData."~field)));
46                     }
47                     else
48                     {
49                         case field:
50                             return janetWrap(mixin("realData."~field));
51                     }
52                 }
53             }
54                 default:
55                     return janet_wrap_nil();
56             }
57     }
58     auto defaultGetter = &getterFunc;
59 }
60 
61 private template defaultPut(T)
62 {
63     extern(C) void defaultPutFunc(void* data, Janet key, Janet value)
64     {
65         if(key.type != JanetType.JANET_KEYWORD && key.type != JanetType.JANET_STRING)
66         {
67             return;
68         }
69         string keyStr = key.fromJanetString;
70         T realData = cast(T)*(cast(void**)data);
71         switch(keyStr)
72         {
73             static foreach(field; __traits(allMembers,T))
74             {
75                 static if(!isInternal!field &&
76                         field != "this" &&
77                         field != "Monitor" &&
78                         !isFunction!(mixin("T."~field)))
79                 {
80                 case field:
81                     if(janetCompatible!(typeof(mixin("T."~field)))(value))
82                     {
83                         mixin("realData."~field) = getFromJanet!(typeof(mixin("T."~field)))(value);
84                     }
85                     return;
86                 }
87             }
88             default:
89                 return;
90         }
91     }
92     auto defaultPut = &defaultPutFunc;
93 }
94 /** Allows one to register a JanetAbstractType with Janet.
95 
96     The returned object can be used as an argument for the janet_abstract function, which will return a void*.
97 
98     This void* can be safely(?) cast to T (TODO: prove safety, make function to ensure it if so).
99 
100     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 '/'.
101 
102     For example: register class "Bar" with janetPackage "Foo" will result in "Foo/Bar".
103 
104     This process may be made easier in a later version.
105 
106     A registered class looks for the following functions:
107 
108     - Janet __janetGet(void* data,Janet key)
109     
110     - void __janetPut(void* data,Janet key,Janet value)
111     
112     - void __janetMarshal (void* p, JanetMarshalContext* ctx)
113     
114     - void __janetUnmarshal (void* p, JanetMarshalContext* ctx)
115     
116     - void __janetTostring (void* p, JanetBuffer* buffer)
117     
118     - int __janetGC (void* data, size_t len)
119     
120     - int __janetGCMark (void* data, size_t len)
121     
122     All of these are optional; get and put will have defaults applied (see the source code for info) if none is defined.
123 
124 */
125 const(JanetAbstractType)* registerType(T)()
126     if(is(T == class))
127 {
128     JanetAbstractType* newType = new JanetAbstractType;
129     import std.string : toStringz;
130     static if(hasStaticMember!(T,"janetPackage"))
131     {
132         newType.name = cast(const(char)*)toStringz(T.janetPackage~"/"~T.stringof);
133     }
134     else
135     {
136         newType.name = cast(const(char)*)toStringz(T.stringof);
137     }
138     {
139         const auto existingType = janet_get_abstract_type(janet_csymbolv(newType.name));
140         if(existingType)
141         {
142             return existingType;
143         }
144     }
145     static if(hasStaticMember!(T,"__janetGet"))
146     {
147         newType.get = &T.__janetGet;
148     }
149     else
150     {
151         newType.get = defaultGetter!T;
152     }
153     static if(hasStaticMember!(T,"__janetPut"))
154     {
155         newType.get = &T.__janetPut;
156     }
157     else
158     {
159         newType.put = defaultPut!T;
160     }
161     static if(hasStaticMember!(T,"__janetMarshal"))
162     {
163         newType.marshal = &T.__janetMarshal;
164     }
165     static if(hasStaticMember!(T,"__janetUnmarshal"))
166     {
167         newType.unmarshal = &T.__janetUnmarshal;
168     }
169     static if(hasStaticMember!(T,"__janetGC"))
170     {
171         newType.gc = &T.__janetGC;
172     }
173     static if(hasStaticMember!(T,"__janetGCMark"))
174     {
175         newType.gcmark = &T.__janetGCMark;
176     }
177     static if(hasStaticMember!(T,"__janetToString"))
178     {
179         newType.tostring = &T.__janetToString;
180     }
181     janet_register_abstract_type(newType);
182     return newType;
183 }
184 
185 unittest
186 {
187     struct Bar
188     {
189         int foo;
190     }
191     class TestClass
192     {
193         static string janetPackage="tests";
194         int a;
195         string b;
196         Bar bar;
197         int testFunc(int b)
198         {
199             return b + 2;
200         }
201     }
202     import std.file;
203     import std.parallelism : task;
204     auto fileTask = task!read("./source/tests/dtests/register.janet");
205     fileTask.executeInNewThread();
206     initJanet();
207     scope(exit) janet_deinit();
208     import std.stdio : writeln;
209     writeln("Performing class register test.");
210     TestClass baz = new TestClass();
211     baz.a = 5;
212     baz.b = "foobar";
213     baz.bar.foo = 10;
214     auto abstractInstance = janetWrap(baz);
215     assert(baz.a == 5);
216     assert(baz.b == "foobar");
217     assert(baz.bar.foo == 10);
218     import std.string : toStringz;
219     janet_def(coreEnv,toStringz("abstractTest"),abstractInstance,toStringz("abstractTest"));
220     Janet* j;
221     Janet testJanet;
222     const ubyte[] file = cast(const(ubyte[]))(fileTask.spinForce);
223     const(ubyte)* realFile = cast(const(ubyte)*)file;
224     int realFileLength = cast(int)(file.length);
225     assert(janet_dobytes(coreEnv,realFile,realFileLength,
226         cast(const(char)*)("./source/tests/dtests/register.janet"),&testJanet)==0,"Abstract test errored!");
227     writeln("Success.");
228     writeln("Performing class wrapping access violation test.");
229     foreach(int i;0..10000)
230     {
231         TestClass boo = new TestClass();
232         auto abst = janetWrap(boo);
233     }
234     writeln("Success.");
235 }