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