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 }