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