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 }