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