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.IOException;
51 import java.util.HashMap;
52 import java.util.Map;
53
54 import org.codehaus.groovy.control.CompilationFailedException;
55 import org.codehaus.groovy.control.SourceUnit;
56 import org.codehaus.groovy.runtime.InvokerHelper;
57 import org.codehaus.groovy.sandbox.ui.Prompt;
58 import org.codehaus.groovy.sandbox.ui.PromptFactory;
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 * @author Yuri Schimke
71 * @version $Revision: 1.14 $
72 */
73 public class InteractiveShell {
74 protected GroovyShell shell = new GroovyShell();
75 protected Prompt prompt;
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 * @throws IOException if there was a problem with the history file.
97 */
98 public InteractiveShell() throws IOException {
99 prompt = PromptFactory.buildPrompt();
100 prompt.setPrompt("groovy> ");
101 }
102
103
104
105
106
107
108
109 /***
110 * Reads commands and statements from input stream and processes them.
111 */
112
113 public void run(String[] args) throws Exception {
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 close();
132 break;
133 }
134
135 reset();
136
137 if( command.length() > 0 ) {
138
139
140
141
142 try {
143 Object answer = shell.evaluate(command, "CommandLine" + counter++ +".groovy");
144
145 }
146 catch( Exception e ) {
147 new ErrorReporter( e, false ).write( System.err );
148 }
149 catch( Throwable e ) {
150 new ErrorReporter( e, false ).write( System.err );
151 System.err.println( ">>> exiting" );
152 System.exit(1);
153 }
154 }
155 }
156 }
157
158
159 protected void close() {
160 prompt.close();
161 }
162
163
164
165
166
167
168
169
170
171 private StringBuffer accepted = new StringBuffer();
172 private String pending = null;
173 private int line = 1;
174
175 private boolean stale = false;
176
177 private SourceUnit parser = null;
178 private TokenStream stream = null;
179 private Exception error = null;
180 private CSTNode tree = null;
181
182
183
184 /***
185 * Resets the command-line processing machinery after use.
186 */
187
188 protected void reset() {
189 stale = true;
190 pending = null;
191 line = 1;
192
193 parser = null;
194 stream = null;
195 error = null;
196 tree = null;
197 }
198
199
200
201 /***
202 * Reads a single statement from the command line. Also identifies
203 * and processes command shell commands. Returns the command text
204 * on success, or null when command processing is complete.
205 * <p>
206 * NOTE: Changed, for now, to read until 'execute' is issued. At
207 * 'execute', the statement must be complete.
208 */
209
210 protected String read() {
211
212 reset();
213 System.out.println( "" );
214
215 boolean complete = false;
216 boolean done = false;
217
218 while(
219
220
221
222
223
224 try { pending = prompt.readLine(); } catch( IOException e ) { }
225
226 if( pending == null || (COMMAND_MAPPINGS.containsKey(pending) && ((Integer)COMMAND_MAPPINGS.get(pending)).intValue() == COMMAND_ID_EXIT) ) {
227 return null;
228 }
229
230
231
232
233
234 if( COMMAND_MAPPINGS.containsKey(pending) ) {
235
236 int code = ((Integer)COMMAND_MAPPINGS.get(pending)).intValue();
237 switch( code ) {
238
239 case COMMAND_ID_HELP:
240 displayHelp();
241 break;
242
243 case COMMAND_ID_DISCARD:
244 reset();
245 done = true;
246 break;
247
248 case COMMAND_ID_DISPLAY:
249 displayStatement();
250 break;
251
252 case COMMAND_ID_EXPLAIN:
253 explainStatement();
254 break;
255
256 case COMMAND_ID_EXECUTE:
257 if( complete ) {
258 done = true;
259 }
260 else {
261 System.err.println( "statement not complete" );
262 }
263 break;
264 }
265
266 continue;
267
268 }
269
270
271
272
273
274
275
276
277 freshen();
278
279 if( pending.trim() == "" ) {
280 accept();
281 continue;
282 }
283
284 String code = current();
285
286 if( parse(code, 1) ) {
287 accept();
288 complete = true;
289 }
290 else if( error == null ) {
291 accept();
292 }
293 else {
294 report( );
295 }
296
297 }
298
299
300
301
302
303 return accepted( complete );
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 * Attempts to parse the specified code with the specified tolerance.
359 * Updates the <code>parser</code> and <code>error</code> members
360 * appropriately. Returns true if the text parsed, false otherwise.
361 * The attempts to identify and suppress errors resulting from the
362 * unfinished source text.
363 */
364
365 private boolean parse( String code, int tolerance ) {
366
367 boolean parsed = false;
368
369 parser = null;
370 stream = null;
371 error = null;
372 tree = null;
373
374
375
376
377 try {
378
379 parser = SourceUnit.create( "groovysh script", code, tolerance );
380 parser.parse();
381 tree = parser.getCST();
382
383
384
385
386
387
388
389
390
391 parsed = true;
392 }
393
394
395
396
397 catch( CompilationFailedException e ) {
398 if( parser.getErrorCount() > 1 || !parser.failedWithUnexpectedEOF() ) {
399 error = e;
400 }
401 }
402
403 catch( Exception e ) {
404 error = e;
405 }
406
407 return parsed;
408 }
409
410
411
412 /***
413 * Reports the last parsing error to the user.
414 */
415
416 private void report() {
417 System.err.println( "discarding invalid text:" );
418 new ErrorReporter( error, false ).write( System.err );
419 }
420
421
422
423
424
425
426
427
428 private static final int COMMAND_ID_EXIT = 0;
429 private static final int COMMAND_ID_HELP = 1;
430 private static final int COMMAND_ID_DISCARD = 2;
431 private static final int COMMAND_ID_DISPLAY = 3;
432 private static final int COMMAND_ID_EXPLAIN = 4;
433 private static final int COMMAND_ID_EXECUTE = 5;
434 private static final int LAST_COMMAND_ID = 5;
435
436 private static final String[] COMMANDS = { "exit", "help", "discard", "display", "explain", "execute" };
437
438 private static final Map COMMAND_MAPPINGS = new HashMap();
439
440 static {
441 for( int i = 0; i <= LAST_COMMAND_ID; i++ ) {
442 COMMAND_MAPPINGS.put( COMMANDS[i], new Integer(i) );
443 }
444
445
446
447 COMMAND_MAPPINGS.put( "quit", new Integer(COMMAND_ID_EXIT) );
448 COMMAND_MAPPINGS.put( "go" , new Integer(COMMAND_ID_EXECUTE) );
449 }
450
451 private static final Map COMMAND_HELP = new HashMap();
452
453 static {
454 COMMAND_HELP.put( COMMANDS[COMMAND_ID_EXIT ], "exit/quit - terminates processing" );
455 COMMAND_HELP.put( COMMANDS[COMMAND_ID_HELP ], "help - displays this help text" );
456 COMMAND_HELP.put( COMMANDS[COMMAND_ID_DISCARD], "discard - discards the current statement" );
457 COMMAND_HELP.put( COMMANDS[COMMAND_ID_DISPLAY], "display - displays the current statement" );
458 COMMAND_HELP.put( COMMANDS[COMMAND_ID_EXPLAIN], "explain - explains the parsing of the current statement" );
459 COMMAND_HELP.put( COMMANDS[COMMAND_ID_EXECUTE], "execute/go - temporary command to cause statement execution" );
460 }
461
462
463
464 /***
465 * Displays help text about available commands.
466 */
467
468 private void displayHelp() {
469 System.out.println( "Available commands (must be entered without extraneous characters):" );
470 for( int i = 0; i <= LAST_COMMAND_ID; i++ ) {
471 System.out.println( (String)COMMAND_HELP.get(COMMANDS[i]) );
472 }
473 }
474
475
476
477 /***
478 * Displays the accepted statement.
479 */
480
481 private void displayStatement() {
482 String[] lines = accepted.toString().split("\n");
483 for( int i = 0; i < lines.length; i++ ) {
484 System.out.println( (i+1) + "> " + lines[i] );
485 }
486 }
487
488
489
490 /***
491 * Attempts to parse the accepted statement and display the
492 * parse tree for it.
493 */
494
495 private void explainStatement() {
496
497 if( parse(accepted(true), 10) || error == null ) {
498 System.out.println( "parse tree:" );
499 System.out.println( tree );
500 }
501 else {
502 System.out.println( "statement does not parse" );
503 }
504 }
505 }
506