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