1 module command; 2 public import command.grammar; 3 public import command.uda; 4 import pegged.grammar; 5 import std.stdio, std..string, std.format, std.traits; 6 7 __gshared CommandInterpreter gCommandInterpreter; 8 9 class CommandInterpreter { 10 version(cli) import command.cli : CommandReaderThread; 11 private { 12 version(cli) CommandReaderThread reader; 13 bool debug_ = false; 14 CmdTableEntry[string][string] commandTable; 15 } 16 17 void interpret(string line) { 18 auto tree = CommandParser(line); 19 20 if (debug_) writeln(tree); 21 if (!tree.successful) { 22 writeln("unable to parse"); 23 return; 24 } 25 26 bool caughtException = false; 27 import std.algorithm.iteration : joiner, each; 28 29 // XXX: this is subject to change 30 string parseToChild(ParseTree c) { 31 switch (c.name) { 32 case "CommandParser": 33 case "CommandParser.Primary": 34 case "CommandParser.ArgTypes": 35 return parseToChild(c.children[0]); 36 case "CommandParser.FunctionCall": 37 string[] args; 38 string ns = "global", func; 39 auto id = c.children[0]; 40 if (id.children[0].name == "CommandParser.FunctionNamespace") { 41 ns = parseToChild(id.children[0]); 42 func = parseToChild(id.children[1]); 43 } else { 44 func = parseToChild(id.children[0]); 45 } 46 if (c.children.length == 2 && c.children[1].name == "CommandParser.Args") { 47 string[] argsFromParseTree(ParseTree c) { 48 string[] vals; 49 foreach(item; c.children[0].children) { 50 vals ~= parseToChild(item); 51 } 52 return vals; 53 } 54 args = argsFromParseTree(c.children[1]); 55 } 56 57 // bailout, avoid execution 58 if (caughtException) { 59 return ""; 60 } 61 62 if (debug_) writeln("Hit func in namespace ", ns, " called ", func, " with args ", args); 63 64 try { 65 return eval(ns, func, args); 66 } catch (Exception e) { 67 writeln("Caught exception: ", e.msg); 68 caughtException = true; 69 return ""; 70 } 71 72 case "CommandParser.Function": 73 case "CommandParser.FunctionNamespace": 74 case "CommandParser.Number": 75 case "CommandParser.String": 76 case "CommandParser.Bool": 77 case "CommandParser.Float": 78 return c.matches[0]; 79 case "CommandParser.HexLiteral": 80 return c.matches[0] ~ c.matches[1]; 81 case "CommandParser.EmptyArgs": 82 return ""; 83 case "CommandParser.Args": 84 // ugh, I hate this, but I have to join them 85 import std.conv : text; 86 87 auto list = c.children[0].children; 88 string[] vals; 89 // TODO: Optimize 90 list.each!((p) => vals ~= parseToChild(p)); 91 return vals.joiner("\0").text; 92 default: 93 assert(0, "Unhandled " ~ c.name); 94 } 95 } 96 97 98 99 100 parseToChild(tree); 101 } 102 103 string eval(string func, string[] args) { 104 return eval("global", func, args); 105 } 106 107 string eval(string ns, string func, string[] args) { 108 if (auto possible = ns in commandTable) { 109 // safe deref since assignment checks if it's null or not 110 if (auto _f = func in *possible) { 111 // ditto 112 auto f = *_f; 113 if (args.length < f.minArgs) { 114 throw new Exception(format!"Expected at least %d arguments, got %d"(f.minArgs, args.length)); 115 } 116 if (args.length > f.maxArgs) { 117 throw new Exception(format!"Expected at most %d arguments, got %d"(f.maxArgs, args.length)); 118 } 119 120 return f.cmd(args); 121 } 122 throw new Exception(format!"Could not find command %s"(func)); 123 } 124 throw new Exception(format!"Could not find namespace %s"(ns)); 125 } 126 127 void registerTypedCommand(string ns, TypedCommand info, CommandType shim) { 128 Command _i = Command(info.tupleof); 129 registerCommand(ns, _i, shim); 130 } 131 132 void registerTypedCommand(TypedCommand info, CommandType shim) { 133 registerTypedCommand("global", info, shim); 134 } 135 136 void registerCommand(Command info, CommandType cmd) { 137 registerCommand("global", info, cmd); 138 } 139 140 void registerCommand(string ns, Command info, CommandType cmd) { 141 synchronized { 142 commandTable[ns][info.name] = CmdTableEntry(cmd, info.desc, info.minArgs, info.maxArgs); 143 } 144 } 145 146 /+ built-in functions +/ 147 string help(string[] args) { 148 import std.algorithm.sorting; 149 void printEntry(string name, CmdTableEntry cmd) { 150 writef("\t%s:\n", name); 151 if (cmd.desc != "") 152 writef("\t\t%s\n", cmd.desc); 153 writef("\t\tmin args: %d, max args: %d\n", cmd.minArgs, cmd.maxArgs); 154 } 155 156 void printNamespace(string ns) { 157 writeln("namespace ", ns, ":"); 158 foreach(entryName; commandTable[ns].keys.sort!("a < b")) { 159 auto entry = commandTable[ns][entryName]; 160 printEntry(entryName, entry); 161 } 162 } 163 164 // global is always first 165 printNamespace("global"); 166 foreach(ns; commandTable.keys.sort!("a < b")) { 167 if (ns == "global") continue; 168 printNamespace(ns); 169 } 170 return ""; 171 } 172 173 string enableDebug(string[] args) { 174 if (args[0] == "true") { 175 debug_ = true; 176 } else if (args[0] == "false") { 177 debug_ = false; 178 } 179 return ""; 180 } 181 182 183 string print(string[] args) { 184 import std.algorithm.iteration : each; 185 args.each!((a) => writeln(a)); 186 return ""; 187 } 188 189 version(cli) { 190 string quit(string[] args) { 191 reader.terminate = true; 192 stdin.close(); 193 return ""; 194 } 195 void run() { 196 reader.run(); 197 } 198 199 void fork() { 200 reader.start(); 201 } 202 } 203 204 this() { 205 version(cli) reader = new CommandReaderThread(); 206 } 207 208 ~this() { 209 version(cli) reader.terminate = true; 210 } 211 212 shared static this() { 213 import std.stdio; 214 gCommandInterpreter = new CommandInterpreter(); 215 version(cli) gCommandInterpreter.registerCommand(Command("quit", "Quit the REPL."), &gCommandInterpreter.quit); 216 debug gCommandInterpreter.registerCommand(Command("debug", "Enable increased verbosity of the interpreter", 1, 1), &gCommandInterpreter.enableDebug); 217 gCommandInterpreter.registerCommand(Command("help", "Display a listing of every registered function"), &gCommandInterpreter.help); 218 gCommandInterpreter.registerCommand(Command("print", "Write something to the console.", 0, 99), &gCommandInterpreter.print); 219 } 220 } 221 222 223