Let me rewrite this to show you some cool tricks:
import std.stdio;
class Math {
static string noArgs(string[] s) { writeln(s); return ""; }
static string withOneArg(string[] s) { writeln(s); return ""; }
static string withTwoArgs(string[] s) { writeln(s); return ""; }
}
class String {
static string oneArg(string[] s) { return null; }
}
string execute(string what, string[] params) {
import std.string;
auto parts = what.split(".");
auto className = parts[0];
auto methodName = parts[1];
import std.typetuple;
switch(className) {
default: assert(0, "unknown class");
foreach(possibleClass; TypeTuple!(Math, String)) {
case possibleClass.stringof:
switch(methodName) {
default: assert(0, "unknown method");
foreach(memberName; __traits(derivedMembers, possibleClass)) {
case memberName:
return __traits(getMember, possibleClass, memberName)(params);
break;
}
}
break;
}
}
assert(0);
}
void main() {
execute("Math.withOneArg", ["cool"]);
execute("String.oneArg", ["cool"]);
}
Notice that there are no mixin
expressions used at all. Instead of getting an instance of the class from a string, I just made a TypeTuple
of all the classes I wanted to use. This is preferable to mixin
because then it is less likely to find name classes when used in different scopes; if possibleClasses
were a compile-time parameter to execute
from a different module, the list of classes would still work, whereas the list of strings would see undefined identifier errors because the library module doesn't import your user module.
Another mixin
I removed was the one to generate the cases. This looks insane, but is allowed in D: if you have a compile-time foreach
(that is, a foreach
over a built-in tuple of some sort, e.g. TypeTuple
, template argument lists, the results of __traits
...) you can actually put case
statements inside them!
So, all you have to do is write a regular switch
statement on the run time variable you want to compare against, put the foreach
inside it looping over the compile-time stuff you're searching for, case that_loop_var:
and boom, you're in business.
Similarly, I used __traits(getMember)
rather than a mixin
string to call the method. This solution will help avoid name clashes and IMO is cleaner code. It can also potentially handle overloads, if wanted (with __traits(getOverloads)
instead of __traits(getMember)
, you can loop over each one then and match the parameter types).
Finally, nesting switch
es inside other case
statements is allowed. If you need to break out of an outer loop or switch
and don't want ambiguity, you can label loops and switches and use break label_name_here;
to specify which one you want to break from. Ditto for continue
with nested loops.
BTW you could also automatically generate the wrapper functions that convert string[]
to other types of arguments if you dove into the std.traits
stuff. I wish my book was out already, I wrote about this at some length in there and don't feel like writing it all right now but if you look at std.traits.ParameterTypeTuple
and ReturnType
in the same module that will get you started if you wanna try it.