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