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