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