root/trunk/minid/commandline.d

Revision 420, 11.1 kB (checked in by JarrettBillingsley, 4 weeks ago)

We have class-based OO, once again.

Line 
1 /******************************************************************************
2 License:
3 Copyright (c) 2008 Jarrett Billingsley
4
5 This software is provided 'as-is', without any express or implied warranty.
6 In no event will the authors be held liable for any damages arising from the
7 use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it freely,
11 subject to the following restrictions:
12
13      1. The origin of this software must not be misrepresented; you must not
14     claim that you wrote the original software. If you use this software in a
15     product, an acknowledgment in the product documentation would be
16     appreciated but is not required.
17
18      2. Altered source versions must be plainly marked as such, and must not
19     be misrepresented as being the original software.
20
21      3. This notice may not be removed or altered from any source distribution.
22 ******************************************************************************/
23
24 module minid.commandline;
25
26 import tango.io.Console;
27 import tango.io.device.FileConduit;
28 import tango.io.model.IConduit;
29 import tango.io.Print;
30 import tango.io.protocol.Reader;
31 import tango.io.Stdout;
32 import tango.stdc.ctype;
33 import tango.stdc.signal;
34 import tango.text.convert.Layout;
35 import tango.text.stream.LineIterator;
36 import tango.text.Util;
37 import Uni = tango.text.Unicode;
38 import Utf = tango.text.convert.Utf;
39
40 import minid.compiler;
41 import minid.ex;
42 import minid.interpreter;
43 import minid.serialization;
44 import minid.types;
45 import minid.utils;
46
47 /**
48 This struct encapsulates a MiniD command-line interpreter.  This is the CLI that
49 MDCL uses, so you can actually embed an MDCL-like interpreter in your application
50 by using this struct.
51
52 This struct installs a signal handler that catches Ctrl+C (SIGINT) signals.  It restores
53 the old signal handler when it exits.
54 */
55 struct CommandLine
56 {
57     const char[] Prompt1 = ">>> ";
58     const char[] Prompt2 = "... ";
59     const char[] Usage =
60 "
61 Flags:
62     -i        Enter interactive mode, after executing any script file.
63     -v        Print the version of the CLI.
64     -h        Print this message and end.
65     -I path   Specifies an import path to search when importing modules.
66
67 If mdcl is called without any arguments, it will be as if you passed it
68 the -v and -i arguments (it will print the version and enter interactive
69 mode).
70
71 If the filename has no extension, it will be treated as a MiniD import-
72 style module name.  So \"a.b\" will look for a module named b in the a
73 directory.  The -I flag also affects the search paths used for this.
74
75 When passing a filename followed by args, all the args will be available
76 to the script by using the vararg expression.  The arguments will all be
77 strings.
78
79 In interactive mode, you will be given a >>> prompt.  When you hit enter,
80 you may be given a ... prompt.  That means you need to type more to make
81 the code complete.  Once you enter enough code to make it complete, the
82 code will be run.  If there is an error, the code buffer is cleared.
83 To end interactive mode, use the \"exit()\" function.
84 ";
85 /+ Stupid editor has issues with multiline strings. "+/
86
87     private Print!(char) mOutput;
88     private LineIterator!(char) mInput;
89
90     /**
91     Construct an instance of the CLI.
92     
93     Params:
94         output = The Print object to which output will be sent.
95         input = The InputStream from which user input will be gathered.
96         
97     Returns:
98         An initialized instance of CommandLine.
99     */
100     public static CommandLine opCall(Print!(char) output, InputStream input)
101     {
102         CommandLine ret;
103         ret.mOutput = output;
104         ret.mInput = new LineIterator!(char)(input);
105         return ret;
106     }
107    
108     /**
109     Same as above, but takes a raw OutputStream for the output instead of
110     a Print instance.
111     */
112     public static CommandLine opCall(OutputStream output, InputStream input)
113     {
114         return opCall(new Print!(char)(new Layout!(char), output), input);
115     }
116    
117     /**
118     Constructs a default instance of CommandLine which uses stdin for the input
119     and stdout for the output.
120     */
121     public static CommandLine opCall()
122     {
123         return opCall(Stdout, Cin.stream);
124     }
125
126     private void printVersion()
127     {
128         mOutput("MiniD Command-Line interpreter 2.0 beta").newline;
129     }
130
131     private void printUsage(char[] progname)
132     {
133         printVersion();
134         mOutput("Usage:").newline;
135         mOutput("\t")(progname)(" [flags] [filename [args]]").newline;
136         mOutput(Usage);
137     }
138
139     /**
140     After you've created an instance of this struct, call this method on it.  The arguments
141     are the arguments that would be passed to MDCL, including the program name as item 0.
142     This function returns when the user exits the interpreter (by using the exit() function,
143     usually).
144     
145     Params:
146         t = The thread to use for this CLI.
147         args = The arguments to the interpreter.  Defaults to null, in which case no arguments
148             will be passed.
149     */
150     public void run(MDThread* t, char[][] args = null)
151     {
152         bool printedVersion = false;
153         bool printedUsage = false;
154         bool interactive = false;
155         char[] inputFile;
156         char[] progname = (args.length > 0) ? args[0] : "";
157
158         if(args.length == 1 || args == null)
159             interactive = true;
160
161         _argLoop: for(int i = 1; i < args.length; i++)
162         {
163             switch(args[i])
164             {
165                 case "-i":
166                     interactive = true;
167                     break;
168    
169                 case "-v":
170                     if(!printedVersion)
171                     {
172                         printedVersion = true;
173                         printVersion();
174                     }
175                     break;
176                    
177                 case "-h":
178                     if(!printedUsage)
179                     {
180                         printedUsage = true;
181                         printUsage(progname);
182                     }
183                     return;
184                    
185                 case "-I":
186                     i++;
187                    
188                     if(i >= args.length)
189                     {
190                         mOutput("-I must be followed by a path").newline;
191                         printUsage(progname);
192                         return;
193                     }
194                    
195                     pushGlobal(t, "modules");
196                     field(t, -1, "path");
197                     pushChar(t, ';');
198                     pushString(t, args[i]);
199                     cateq(t, -3, 2);
200                     fielda(t, -2, "path");
201                     pop(t);
202                     break;
203
204                 default:
205                     if(args[i][0] == '-')
206                     {
207                         mOutput("Unknown flag '{}'.", args[i]);
208                         return;
209                     }
210    
211                     inputFile = args[i];
212                     args = args[i + 1 .. $];
213                     break _argLoop;
214             }
215         }
216
217         if(inputFile.length > 0)
218         {
219             word reg;
220
221             try
222             {
223                 if(!inputFile.endsWith(".md") && !inputFile.endsWith(".mdm"))
224                     reg = importModule(t, inputFile);
225                 else
226                 {
227                     if(inputFile.endsWith(".md"))
228                     {
229                         scope c = new Compiler(t);
230                         c.compileModule(inputFile);
231                     }
232                     else
233                     {
234                         scope fc = new FileConduit(inputFile, FileConduit.ReadExisting);
235                         scope r = new Reader(fc);
236                         deserializeModule(t, r);
237                     }
238
239                     lookup(t, "modules.initModule");
240                     swap(t);
241                     pushNull(t);
242                     swap(t);
243                     pushString(t, funcName(t, -1));
244                     rawCall(t, -4, 1);
245                     reg = stackSize(t) - 1;
246                 }
247
248                 pushNull(t);
249                 lookup(t, "modules.runMain");
250                 swap(t, -3);
251
252                 foreach(a; args)
253                     pushString(t, a);
254
255                 rawCall(t, reg, 0);
256             }
257             catch(MDException e)
258             {
259                 catchException(t);
260                 pop(t);
261
262                 mOutput.formatln("Error: {}", e);
263
264                 auto tb = getTraceback(t);
265                 mOutput.formatln("{}", getString(t, tb));
266                 pop(t);
267                 mOutput.newline;
268             }
269         }
270
271         if(interactive)
272         {
273             if(!printedVersion)
274                 printVersion();
275
276             char[] buffer;
277
278             // static so exit can access it.
279             static bool run;
280             run = true;
281
282             newFunction(t, function uword(MDThread* t, uword numParams)
283                 {
284                     run = false;
285                     return 0;
286                 }, "exit");
287             newGlobal(t, "exit");
288
289             mOutput("Use the \"exit()\" function to end.").newline;
290             mOutput(Prompt1)();
291
292             // static so the interrupt handler can access it.
293             static bool didHalt = false;
294             didHalt = false;
295             static MDThread* thread;
296             thread = t;
297
298             static extern(C) void interruptHandler(int s)
299             {
300                 pendingHalt(thread);
301                 didHalt = true;
302                 signal(s, &interruptHandler);
303             }
304
305             auto oldInterrupt = signal(SIGINT, &interruptHandler);
306
307             scope(exit)
308                 signal(SIGINT, oldInterrupt);
309
310             bool couldBeDecl()
311             {
312                 auto temp = buffer.triml();
313                 return temp.startsWith("function") || temp.startsWith("class") || temp.startsWith("namespace") || temp.startsWith("@");
314             }
315
316             bool tryAsStatement(Exception e = null)
317             {
318                 scope c = new Compiler(t);
319                 word reg;
320
321                 try
322                     reg = c.compileStatements(buffer, "stdin");
323                 catch(MDException e2)
324                 {
325                     catchException(t);
326                     pop(t);
327
328                     if(c.isEof())
329                     {
330                         mOutput(Prompt2).flush;
331                         return true;
332                     }
333                     else if(c.isLoneStmt())
334                     {
335                         if(e)
336                         {
337                             mOutput.formatln("When attempting to evaluate as an expression:");
338                             mOutput.formatln("Error: {}", e);
339                             mOutput.formatln("When attempting to evaluate as a statement:");
340                         }
341                     }
342
343                     mOutput.formatln("Error: {}", e2).newline;
344                    
345                     return false;
346                 }
347
348                 try
349                 {
350                     pushNull(t);
351                     rawCall(t, reg, 0);
352                 }
353                 catch(MDException e2)
354                 {
355                     catchException(t);
356                     pop(t);
357
358                     mOutput.formatln("Error: {}", e2);
359                    
360                     auto tb = getTraceback(t);
361                     mOutput.formatln("{}", getString(t, tb));
362                     pop(t);
363                     mOutput.newline;
364                 }
365
366                 return false;
367             }
368
369             bool tryAsExpression()
370             {
371                 scope c = new Compiler(t);
372                 word reg;
373
374                 try
375                     reg = c.compileExpression(buffer, "stdin");
376                 catch(MDException e)
377                 {
378                     catchException(t);
379                     pop(t);
380
381                     if(c.isEof())
382                     {
383                         mOutput(Prompt2)();
384                         return true;
385                     }
386                     else
387                         return tryAsStatement(e);
388                 }
389
390                 try
391                 {
392                     pushNull(t);
393                     auto numRets = rawCall(t, reg, -1);
394
395                     if(numRets > 0)
396                     {
397                         mOutput(" => ");
398
399                         bool first = true;
400
401                         for(word i = stackSize(t) - numRets; i < stackSize(t); i++)
402                         {
403                             if(first)
404                                 first = false;
405                             else
406                                 mOutput(", ");
407
408                             reg = pushGlobal(t, "dumpVal");
409                             pushNull(t);
410                             dup(t, i);
411                             pushBool(t, false);
412                             rawCall(t, reg, 0);
413                         }
414
415                         mOutput.newline;
416                     }
417                 }
418                 catch(MDException e)
419                 {
420                     catchException(t);
421                     pop(t);
422
423                     mOutput.formatln("Error: {}", e);
424                    
425                     auto tb = getTraceback(t);
426                     mOutput.formatln("{}", getString(t, tb));
427                     pop(t);
428                     mOutput.newline;
429                 }
430
431                 return false;
432             }
433
434             auto stackIdx = stackSize(t);
435
436             while(run)
437             {
438                 if(auto diff = stackSize(t) - stackIdx)
439                     pop(t, diff);
440
441                 auto line = mInput.next();
442
443                 if(line.ptr is null)
444                 {
445                     if(didHalt)
446                     {
447                         didHalt = false;
448                         mOutput.newline;
449                     }
450                     else
451                         break;
452                 }
453
454                 if(buffer.length is 0 && line.trim().length is 0)
455                 {
456                     mOutput(Prompt1)();
457                     continue;
458                 }
459
460                 if(buffer.length == 0)
461                     buffer ~= line;
462                 else
463                     buffer ~= '\n' ~ line;
464
465                 try
466                 {
467                     if(couldBeDecl())
468                     {
469                         if(tryAsStatement())
470                             continue;
471                     }
472                     else
473                     {
474                         if(tryAsExpression())
475                             continue;
476                     }
477                 }
478                 catch(MDHaltException e)
479                 {
480                     mOutput.formatln("Halted by keyboard interrupt.");
481                     didHalt = false;
482                 }
483
484                 mOutput(Prompt1)();
485                 buffer.length = 0;
486             }
487         }
488     }
489 }
Note: See TracBrowser for help on using the browser.