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.30 $
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(new Binding(), in, out, err);
114 }
115
116 public InteractiveShell(Binding binding, final InputStream in, final PrintStream out, final PrintStream err) {
117 this.in = in;
118 this.out = out;
119 this.err = err;
120 prompt = PromptFactory.buildPrompt(in, out, err);
121 prompt.setPrompt("groovy> ");
122 shell = new GroovyShell(binding);
123 Map map = shell.getContext().getVariables();
124 if (map.get("shell") != null) {
125 map.put("shell", shell);
126 }
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 final String version = InvokerHelper.getVersion();
137
138 out.println("Lets get Groovy!");
139 out.println("================");
140 out.println("Version: " + version + " JVM: " + System.getProperty("java.vm.version"));
141 out.println("Type 'exit' to terminate the shell");
142 out.println("Type 'help' for command help");
143 out.println("Type 'go' to execute the statements");
144
145 boolean running = true;
146 while (running) {
147
148
149 final String command = read();
150 if (command == null) {
151 close();
152 break;
153 }
154
155 reset();
156
157 if (command.length() > 0) {
158
159 try {
160 lastResult = shell.evaluate(command, "CommandLine.groovy");
161 } catch (CompilationFailedException e) {
162 err.println(e);
163 } catch (Throwable e) {
164 if (e instanceof InvokerInvocationException) {
165 InvokerInvocationException iie = (InvokerInvocationException) e;
166 e = iie.getCause();
167 }
168 err.println("Caught: " + e);
169 StackTraceElement[] stackTrace = e.getStackTrace();
170 for (int i = 0; i < stackTrace.length; i++) {
171 StackTraceElement element = stackTrace[i];
172 String fileName = element.getFileName();
173 if (fileName==null || (!fileName.endsWith(".java"))) {
174 err.println("\tat " + element);
175 }
176 }
177 }
178 }
179 }
180 }
181
182
183 protected void close() {
184 prompt.close();
185 }
186
187
188
189
190
191
192 private StringBuffer accepted = new StringBuffer();
193 private String pending = null;
194 private int line = 1;
195
196 private boolean stale = false;
197
198 private SourceUnit parser = null;
199 private Exception error = null;
200
201
202 /***
203 * Resets the command-line processing machinery after use.
204 */
205
206 protected void reset() {
207 stale = true;
208 pending = null;
209 line = 1;
210
211 parser = null;
212 error = null;
213 }
214
215
216 /***
217 * Reads a single statement from the command line. Also identifies
218 * and processes command shell commands. Returns the command text
219 * on success, or null when command processing is complete.
220 * <p/>
221 * NOTE: Changed, for now, to read until 'execute' is issued. At
222 * 'execute', the statement must be complete.
223 */
224
225 protected String read() {
226 reset();
227 out.println("");
228
229 boolean complete = false;
230 boolean done = false;
231
232 while (
233
234
235
236
237 try {
238 pending = prompt.readLine();
239 }
240 catch (IOException e) {
241 }
242
243 if (pending == null || (COMMAND_MAPPINGS.containsKey(pending) && ((Integer) COMMAND_MAPPINGS.get(pending)).intValue() == COMMAND_ID_EXIT)) {
244 return null;
245 }
246
247
248 if (COMMAND_MAPPINGS.containsKey(pending)) {
249 int code = ((Integer) COMMAND_MAPPINGS.get(pending)).intValue();
250 switch (code) {
251 case COMMAND_ID_HELP:
252 displayHelp();
253 break;
254
255 case COMMAND_ID_DISCARD:
256 reset();
257 done = true;
258 break;
259
260 case COMMAND_ID_DISPLAY:
261 displayStatement();
262 break;
263
264 case COMMAND_ID_EXPLAIN:
265 explainStatement();
266 break;
267
268 case COMMAND_ID_BINDING:
269 displayBinding();
270 break;
271
272 case COMMAND_ID_EXECUTE:
273 if (complete) {
274 done = true;
275 }
276 else {
277 err.println("statement not complete");
278 }
279 break;
280 case COMMAND_ID_DISCARD_LOADED_CLASSES:
281 resetLoadedClasses();
282 break;
283 case COMMAND_ID_INSPECT:
284 inspect();
285 break;
286 }
287
288 continue;
289 }
290
291
292
293
294
295
296 freshen();
297
298 if (pending.trim().equals("")) {
299 accept();
300 continue;
301 }
302
303 final String code = current();
304
305 if (parse(code, 1)) {
306 accept();
307 complete = true;
308 }
309 else if (error == null) {
310 accept();
311 }
312 else {
313 report();
314 }
315
316 }
317
318
319 return accepted(complete);
320 }
321
322 private void inspect() {
323 if (null == lastResult){
324 err.println("nothing to inspect (preceding \"go\" missing?)");
325 return;
326 }
327
328
329 try {
330 Class browserClass = Class.forName("groovy.inspect.swingui.ObjectBrowser");
331 Method inspectMethod = browserClass.getMethod("inspect", new Class[]{Object.class});
332 inspectMethod.invoke(browserClass, new Object[]{lastResult});
333 } catch (Exception e) {
334 err.println("cannot invoke ObjectBrowser");
335 e.printStackTrace();
336 }
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 if (complete) {
346 return accepted.toString();
347 }
348 return "";
349 }
350
351
352 /***
353 * Returns the current statement, including pending text.
354 */
355 private String current() {
356 return accepted.toString() + pending + "\n";
357 }
358
359
360 /***
361 * Accepts the pending text into the statement.
362 */
363 private void accept() {
364 accepted.append(pending).append("\n");
365 line += 1;
366 }
367
368
369 /***
370 * Clears accepted if stale.
371 */
372 private void freshen() {
373 if (stale) {
374 accepted.setLength(0);
375 stale = false;
376 }
377 }
378
379
380
381
382
383
384 /***
385 * Attempts to parse the specified code with the specified tolerance.
386 * Updates the <code>parser</code> and <code>error</code> members
387 * appropriately. Returns true if the text parsed, false otherwise.
388 * The attempts to identify and suppress errors resulting from the
389 * unfinished source text.
390 */
391 private boolean parse(String code, int tolerance) {
392 boolean parsed = false;
393
394 parser = null;
395 error = null;
396
397
398 try {
399 parser = SourceUnit.create("groovysh script", code, tolerance);
400 parser.parse();
401
402
403
404
405
406
407
408
409 parsed = true;
410 }
411
412
413 catch (CompilationFailedException e) {
414 if (parser.getErrorCollector().getErrorCount() > 1 || !parser.failedWithUnexpectedEOF()) {
415 error = e;
416 }
417 }
418 catch (Exception e) {
419 error = e;
420 }
421
422 return parsed;
423 }
424
425
426 /***
427 * Reports the last parsing error to the user.
428 */
429
430 private void report() {
431 err.println("Discarding invalid text:");
432 new ErrorReporter(error, false).write(err);
433 }
434
435
436
437
438 private static final int COMMAND_ID_EXIT = 0;
439 private static final int COMMAND_ID_HELP = 1;
440 private static final int COMMAND_ID_DISCARD = 2;
441 private static final int COMMAND_ID_DISPLAY = 3;
442 private static final int COMMAND_ID_EXPLAIN = 4;
443 private static final int COMMAND_ID_EXECUTE = 5;
444 private static final int COMMAND_ID_BINDING = 6;
445 private static final int COMMAND_ID_DISCARD_LOADED_CLASSES = 7;
446 private static final int COMMAND_ID_INSPECT = 8;
447
448 private static final int LAST_COMMAND_ID = 8;
449
450 private static final String[] COMMANDS = {"exit", "help", "discard", "display", "explain", "execute", "binding", "discardclasses", "inspect"};
451
452 private static final Map COMMAND_MAPPINGS = new HashMap();
453
454 static {
455 for (int i = 0; i <= LAST_COMMAND_ID; i++) {
456 COMMAND_MAPPINGS.put(COMMANDS[i], new Integer(i));
457 }
458
459
460
461 COMMAND_MAPPINGS.put("quit", new Integer(COMMAND_ID_EXIT));
462 COMMAND_MAPPINGS.put("go", new Integer(COMMAND_ID_EXECUTE));
463 }
464
465 private static final Map COMMAND_HELP = new HashMap();
466
467 static {
468 COMMAND_HELP.put(COMMANDS[COMMAND_ID_EXIT], "exit/quit - terminates processing");
469 COMMAND_HELP.put(COMMANDS[COMMAND_ID_HELP], "help - displays this help text");
470 COMMAND_HELP.put(COMMANDS[COMMAND_ID_DISCARD], "discard - discards the current statement");
471 COMMAND_HELP.put(COMMANDS[COMMAND_ID_DISPLAY], "display - displays the current statement");
472 COMMAND_HELP.put(COMMANDS[COMMAND_ID_EXPLAIN], "explain - explains the parsing of the current statement (currently disabled)");
473 COMMAND_HELP.put(COMMANDS[COMMAND_ID_EXECUTE], "execute/go - temporary command to cause statement execution");
474 COMMAND_HELP.put(COMMANDS[COMMAND_ID_BINDING], "binding - shows the binding used by this interactive shell");
475 COMMAND_HELP.put(COMMANDS[COMMAND_ID_DISCARD_LOADED_CLASSES], "discardclasses - discards all former unbound class definitions");
476 COMMAND_HELP.put(COMMANDS[COMMAND_ID_INSPECT], "inspect - opens ObjectBrowser on expression returned from previous \"go\"");
477 }
478
479
480 /***
481 * Displays help text about available commands.
482 */
483 private void displayHelp() {
484 out.println("Available commands (must be entered without extraneous characters):");
485 for (int i = 0; i <= LAST_COMMAND_ID; i++) {
486 out.println((String) COMMAND_HELP.get(COMMANDS[i]));
487 }
488 }
489
490
491 /***
492 * Displays the accepted statement.
493 */
494 private void displayStatement() {
495 final String[] lines = accepted.toString().split("\n");
496 for (int i = 0; i < lines.length; i++) {
497 out.println((i + 1) + "> " + lines[i]);
498 }
499 }
500
501 /***
502 * Displays the current binding used when instanciating the shell.
503 */
504 private void displayBinding() {
505 out.println("Available variables in the current binding");
506 Binding context = shell.getContext();
507 Map variables = context.getVariables();
508 Set set = variables.keySet();
509 if (set.isEmpty()) {
510 out.println("The current binding is empty.");
511 }
512 else {
513 for (Iterator it = set.iterator(); it.hasNext();) {
514 String key = (String) it.next();
515 out.println(key + " = " + variables.get(key));
516 }
517 }
518 }
519
520
521 /***
522 * Attempts to parse the accepted statement and display the
523 * parse tree for it.
524 */
525 private void explainStatement() {
526 if (parse(accepted(true), 10) || error == null) {
527 out.println("Parse tree:");
528
529 }
530 else {
531 out.println("Statement does not parse");
532 }
533 }
534
535 private void resetLoadedClasses() {
536 shell.resetLoadedClasses();
537 out.println("all former unbound class definitions are discarded");
538 }
539 }
540