1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 package groovy.ui;
47
48 import groovy.lang.GroovyShell;
49 import groovy.lang.Binding;
50 import org.codehaus.groovy.control.CompilationFailedException;
51 import org.codehaus.groovy.control.SourceUnit;
52 import org.codehaus.groovy.runtime.InvokerHelper;
53 import org.codehaus.groovy.sandbox.ui.Prompt;
54 import org.codehaus.groovy.sandbox.ui.PromptFactory;
55 import org.codehaus.groovy.syntax.CSTNode;
56 import org.codehaus.groovy.syntax.TokenStream;
57 import org.codehaus.groovy.tools.ErrorReporter;
58
59 import java.io.IOException;
60 import java.io.InputStream;
61 import java.io.PrintStream;
62 import java.util.HashMap;
63 import java.util.Map;
64 import java.util.Set;
65 import java.util.Iterator;
66
67 /***
68 * A simple interactive shell for evaluating groovy expressions
69 * on the command line
70 *
71 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
72 * @author <a href="mailto:cpoirier@dreaming.org" >Chris Poirier</a>
73 * @author Yuri Schimke
74 * @author Brian McCallistair
75 * @author Guillaume Laforge
76 * @version $Revision: 1.16 $
77 */
78 public class InteractiveShell
79 {
80 private final GroovyShell shell;
81 private final Prompt prompt;
82 private final InputStream in;
83 private final PrintStream out;
84 private final PrintStream err;
85
86
87 /***
88 * Entry point when called directly.
89 */
90 public static void main(String args[])
91 {
92 try
93 {
94 final InteractiveShell groovy = new InteractiveShell();
95 groovy.run(args);
96 }
97 catch (Exception e)
98 {
99 System.err.println("Caught: " + e);
100 e.printStackTrace();
101 }
102 }
103
104
105 /***
106 * Default constructor.
107 */
108 public InteractiveShell()
109 {
110 this(System.in, System.out, System.err);
111 }
112
113
114 public InteractiveShell(final InputStream in, final PrintStream out, final PrintStream err)
115 {
116 this(new Binding(), in, out, err);
117 }
118
119 public InteractiveShell(Binding binding, final InputStream in, final PrintStream out, final PrintStream err)
120 {
121 this.in = in;
122 this.out = out;
123 this.err = err;
124 prompt = PromptFactory.buildPrompt(in, out, err);
125 prompt.setPrompt("groovy> ");
126 shell = new GroovyShell(binding);
127 }
128
129
130
131
132 /***
133 * Reads commands and statements from input stream and processes them.
134 */
135 public void run(String[] args) throws Exception
136 {
137 final String version = InvokerHelper.getVersion();
138
139 out.println("Lets get Groovy!");
140 out.println("================");
141 out.println("Version: " + version + " JVM: " + System.getProperty("java.vm.version"));
142 out.println("Type 'exit' to terminate the shell");
143 out.println("Type 'help' for command help");
144 out.println("Type 'go' to execute the statements");
145
146 int counter = 1;
147 boolean running = true;
148 while (running)
149 {
150
151
152 final String command = read();
153 if (command == null)
154 {
155 close();
156 break;
157 }
158
159 reset();
160
161 if (command.length() > 0)
162 {
163
164 try
165 {
166 shell.evaluate(command, "CommandLine" + counter++ + ".groovy");
167 }
168 catch (Exception e)
169 {
170 err.println("Exception: " + e.getMessage());
171 e.printStackTrace(err);
172 new ErrorReporter(e, false).write(err);
173 }
174 catch (Throwable e)
175 {
176 err.println("Unrecoverable Error: " + e.getMessage());
177 e.printStackTrace(err);
178 new ErrorReporter(e, false).write(err);
179 err.println(">>> exiting");
180
181
182 running = false;
183 }
184 }
185 }
186 }
187
188
189 protected void close()
190 {
191 prompt.close();
192 }
193
194
195
196
197
198
199 private StringBuffer accepted = new StringBuffer();
200 private String pending = null;
201 private int line = 1;
202
203 private boolean stale = false;
204
205 private SourceUnit parser = null;
206 private TokenStream stream = null;
207 private Exception error = null;
208 private CSTNode tree = null;
209
210
211 /***
212 * Resets the command-line processing machinery after use.
213 */
214
215 protected void reset()
216 {
217 stale = true;
218 pending = null;
219 line = 1;
220
221 parser = null;
222 stream = null;
223 error = null;
224 tree = null;
225 }
226
227
228 /***
229 * Reads a single statement from the command line. Also identifies
230 * and processes command shell commands. Returns the command text
231 * on success, or null when command processing is complete.
232 * <p/>
233 * NOTE: Changed, for now, to read until 'execute' is issued. At
234 * 'execute', the statement must be complete.
235 */
236
237 protected String read()
238 {
239 reset();
240 out.println("");
241
242 boolean complete = false;
243 boolean done = false;
244
245 while (
246 {
247
248
249
250
251 try
252 {
253 pending = prompt.readLine();
254 }
255 catch (IOException e) { }
256
257 if (pending == null || (COMMAND_MAPPINGS.containsKey(pending) && ((Integer) COMMAND_MAPPINGS.get(pending)).intValue() == COMMAND_ID_EXIT))
258 {
259 return null;
260 }
261
262
263 if (COMMAND_MAPPINGS.containsKey(pending))
264 {
265 int code = ((Integer) COMMAND_MAPPINGS.get(pending)).intValue();
266 switch (code)
267 {
268 case COMMAND_ID_HELP:
269 displayHelp();
270 break;
271
272 case COMMAND_ID_DISCARD:
273 reset();
274 done = true;
275 break;
276
277 case COMMAND_ID_DISPLAY:
278 displayStatement();
279 break;
280
281 case COMMAND_ID_EXPLAIN:
282 explainStatement();
283 break;
284
285 case COMMAND_ID_BINDING:
286 displayBinding();
287 break;
288
289 case COMMAND_ID_EXECUTE:
290 if (complete)
291 {
292 done = true;
293 }
294 else
295 {
296 err.println("statement not complete");
297 }
298 break;
299 }
300
301 continue;
302 }
303
304
305
306
307
308
309 freshen();
310
311 if (pending.trim() == "")
312 {
313 accept();
314 continue;
315 }
316
317 final String code = current();
318
319 if (parse(code, 1))
320 {
321 accept();
322 complete = true;
323 }
324 else if (error == null)
325 {
326 accept();
327 }
328 else
329 {
330 report();
331 }
332
333 }
334
335
336 return accepted(complete);
337 }
338
339
340 /***
341 * Returns the accepted statement as a string. If not <code>complete</code>,
342 * returns the empty string.
343 */
344 private String accepted(boolean complete)
345 {
346 if (complete)
347 {
348 return accepted.toString();
349 }
350 return "";
351 }
352
353
354 /***
355 * Returns the current statement, including pending text.
356 */
357 private String current()
358 {
359 return accepted.toString() + pending + "\n";
360 }
361
362
363 /***
364 * Accepts the pending text into the statement.
365 */
366 private void accept()
367 {
368 accepted.append(pending).append("\n");
369 line += 1;
370 }
371
372
373 /***
374 * Clears accepted if stale.
375 */
376 private void freshen()
377 {
378 if (stale)
379 {
380 accepted.setLength(0);
381 stale = false;
382 }
383 }
384
385
386
387
388
389
390 /***
391 * Attempts to parse the specified code with the specified tolerance.
392 * Updates the <code>parser</code> and <code>error</code> members
393 * appropriately. Returns true if the text parsed, false otherwise.
394 * The attempts to identify and suppress errors resulting from the
395 * unfinished source text.
396 */
397 private boolean parse(String code, int tolerance)
398 {
399 boolean parsed = false;
400
401 parser = null;
402 stream = null;
403 error = null;
404 tree = null;
405
406
407 try
408 {
409 parser = SourceUnit.create("groovysh script", code, tolerance);
410 parser.parse();
411 tree = parser.getCST();
412
413
414
415
416
417
418
419
420 parsed = true;
421 }
422
423
424 catch (CompilationFailedException e)
425 {
426 if (parser.getErrorCount() > 1 || !parser.failedWithUnexpectedEOF())
427 {
428 error = e;
429 }
430 }
431
432 catch (Exception e)
433 {
434 error = e;
435 }
436
437 return parsed;
438 }
439
440
441 /***
442 * Reports the last parsing error to the user.
443 */
444
445 private void report()
446 {
447 err.println("Discarding invalid text:");
448 new ErrorReporter(error, false).write(err);
449 }
450
451
452
453
454 private static final int COMMAND_ID_EXIT = 0;
455 private static final int COMMAND_ID_HELP = 1;
456 private static final int COMMAND_ID_DISCARD = 2;
457 private static final int COMMAND_ID_DISPLAY = 3;
458 private static final int COMMAND_ID_EXPLAIN = 4;
459 private static final int COMMAND_ID_EXECUTE = 5;
460 private static final int COMMAND_ID_BINDING = 6;
461
462 private static final int LAST_COMMAND_ID = 6;
463
464 private static final String[] COMMANDS = {"exit", "help", "discard", "display", "explain", "execute", "binding"};
465
466 private static final Map COMMAND_MAPPINGS = new HashMap();
467
468 static
469 {
470 for (int i = 0; i <= LAST_COMMAND_ID; i++)
471 {
472 COMMAND_MAPPINGS.put(COMMANDS[i], new Integer(i));
473 }
474
475
476
477 COMMAND_MAPPINGS.put("quit", new Integer(COMMAND_ID_EXIT));
478 COMMAND_MAPPINGS.put("go", new Integer(COMMAND_ID_EXECUTE));
479 }
480
481 private static final Map COMMAND_HELP = new HashMap();
482
483 static
484 {
485 COMMAND_HELP.put(COMMANDS[COMMAND_ID_EXIT], "exit/quit - terminates processing");
486 COMMAND_HELP.put(COMMANDS[COMMAND_ID_HELP], "help - displays this help text");
487 COMMAND_HELP.put(COMMANDS[COMMAND_ID_DISCARD], "discard - discards the current statement");
488 COMMAND_HELP.put(COMMANDS[COMMAND_ID_DISPLAY], "display - displays the current statement");
489 COMMAND_HELP.put(COMMANDS[COMMAND_ID_EXPLAIN], "explain - explains the parsing of the current statement");
490 COMMAND_HELP.put(COMMANDS[COMMAND_ID_EXECUTE], "execute/go - temporary command to cause statement execution");
491 COMMAND_HELP.put(COMMANDS[COMMAND_ID_BINDING], "binding - shows the binding used by this interactive shell");
492 }
493
494
495 /***
496 * Displays help text about available commands.
497 */
498 private void displayHelp()
499 {
500 out.println("Available commands (must be entered without extraneous characters):");
501 for (int i = 0; i <= LAST_COMMAND_ID; i++)
502 {
503 out.println((String) COMMAND_HELP.get(COMMANDS[i]));
504 }
505 }
506
507
508 /***
509 * Displays the accepted statement.
510 */
511 private void displayStatement()
512 {
513 final String[] lines = accepted.toString().split("\n");
514 for (int i = 0; i < lines.length; i++)
515 {
516 out.println((i + 1) + "> " + lines[i]);
517 }
518 }
519
520 /***
521 * Displays the current binding used when instanciating the shell.
522 */
523 private void displayBinding()
524 {
525 out.println("Avaialble variables in the current binding");
526 Binding context = shell.getContext();
527 Map variables = context.getVariables();
528 Set set = variables.keySet();
529 if (set.isEmpty())
530 {
531 out.println("The current binding is empty.");
532 }
533 else
534 {
535 for (Iterator it = set.iterator(); it.hasNext();)
536 {
537 String key = (String)it.next();
538 out.println(key + " = " + variables.get(key));
539 }
540 }
541 }
542
543
544 /***
545 * Attempts to parse the accepted statement and display the
546 * parse tree for it.
547 */
548 private void explainStatement()
549 {
550 if (parse(accepted(true), 10) || error == null)
551 {
552 out.println("Parse tree:");
553 out.println(tree);
554 }
555 else
556 {
557 out.println("Statement does not parse");
558 }
559 }
560 }
561