1 module dynamic_loader.dynamic_class; 2 3 public import std.variant; 4 public import std.conv; 5 public import std.traits; 6 public import std.typecons; 7 8 enum DynamicallyAvailable; 9 alias Helper(alias T) = T; 10 11 interface DynamicClass { 12 // Variant is a weird type that says, "This can return 13 // variable types". Weird. 14 Variant __send__(string method, string[] arguments); 15 Variant __send__(string method); 16 } 17 18 bool isDynamicallyAvailable(alias member)() { 19 foreach(annotation; __traits(getAttributes, member)) 20 static if(is(annotation == DynamicallyAvailable)) 21 return true; 22 23 return false; 24 } 25 26 // TODO: Figure out a way to store the method here so that we can call it 27 // again later without having to look it up again. 28 mixin template DynamicClassImplementation() { 29 override Variant __send__(string method, string[] arguments) { 30 // Get all the members for the current type 31 foreach(memberName; __traits(allMembers, typeof(this))) { 32 33 // if the member name is not a match, skip to the next 34 if (memberName != method) 35 continue; 36 37 // I don't know why, but you have to check the compiles trait before you 38 // can get the member 39 static if (__traits(compiles, __traits(getMember, this, memberName))) { 40 41 // setup shorthand for the getMember call 42 alias member = Helper!(__traits(getMember, this, memberName)); 43 44 // make sure this member is dynamicall callable and is a function 45 static if (is(typeof(member) == function) && isDynamicallyAvailable!member) { 46 47 // I honestly don't really understand how this argument stuff works 48 ParameterTypeTuple!member functionArguments; 49 50 foreach(index, ref arg; functionArguments) { 51 if (index >= arguments.length) 52 throw new Exception("Not enough arguments to call " ~ method); 53 54 arg = to!(typeof(arg))(arguments[index]); 55 } 56 57 Variant returnValue; 58 59 // setup the return value 60 static if (is(ReturnType!member == void)) 61 member(functionArguments); 62 else 63 returnValue = member(functionArguments); 64 65 return returnValue; 66 } 67 } 68 } 69 throw new Exception("No such method " ~ method); 70 } 71 72 override Variant __send__(string method) { 73 return __send__(method, []); 74 } 75 } 76 77 string[] getAllDynamicClasses() { 78 string[] list; 79 80 // ModuleInfo is a class defined in the globally-available object.d 81 // that gives info about all the modules. It can be looped over and inspected. 82 foreach(mod; ModuleInfo) { 83 classList: foreach(classInfo; mod.localClasses) { 84 // this is info about all the top-level classes in the program 85 if(doesClassMatch(classInfo)) 86 list ~= classInfo.name; 87 } 88 } 89 90 return list; 91 } 92 93 // this is runtime info, so we can't use the compile time __traits 94 // reflection on it, but there's some info available through its methods. 95 bool doesClassMatch(ClassInfo classInfo) { 96 foreach(iface; classInfo.interfaces) { 97 // the name is the fully-qualified name, so it includes the module name too 98 if(iface.classinfo.name == "dynamic_loader.dynamic_class.DynamicClass") { 99 return true; 100 } 101 } 102 103 // if we haven't found it yet, the interface might still be implemented, 104 // just on the base class instead. Redo the check on the base class, if there 105 // is one. 106 if(classInfo.base !is null) 107 return doesClassMatch(classInfo.base); 108 return false; 109 } 110 111 unittest { 112 import dunit.toolkit; 113 114 class Foo : DynamicClass { 115 mixin DynamicClassImplementation!(); 116 117 @DynamicallyAvailable { 118 string bar() { 119 return "Bar"; 120 } 121 122 string fizzbuzzer(int num) { 123 if (num % 15 == 0) { 124 return "fizzbuzz"; 125 } else if (num % 5 == 0) { 126 return "buzz"; 127 } else if (num % 3 == 0) { 128 return "fizz"; 129 } else { 130 return to!string(num); 131 } 132 } 133 } 134 } 135 136 auto foo = new Foo; 137 foo.__send__("bar").assertEqual("Bar"); 138 foo.__send__("fizzbuzzer", ["1"]).assertEqual("1"); 139 foo.__send__("fizzbuzzer", ["15"]).assertEqual("fizzbuzz"); 140 }