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