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 }