View Javadoc

1   /*
2    $Id: InteractiveShell.java,v 1.12 2004/04/19 07:29:43 cpoirier Exp $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
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   // COMMAND LINE PROCESSING LOOP
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             // Read a single top-level statement from the command line, 
127             // trapping errors as they happen.  We quit on null.
128 
129             String command = read();
130             if( command == null ) {
131                 break;
132             }
133 
134             reset();
135 
136             if( command.length() > 0 ) {
137               
138                 //
139                 // We have a command that parses, so evaluate it.
140 
141                 try {
142                     Object answer = shell.evaluate(command, "CommandLine" + counter++ +".groovy");
143                     // System.out.println(InvokerHelper.inspect(answer));
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   // COMMAND LINE PROCESSING MACHINERY
162 
163 
164     private StringBuffer accepted = new StringBuffer();  // The statement text accepted to date
165     private String       pending  = null;                // A line of statement text not yet accepted
166     private int          line     = 1;                   // The current line number
167 
168     private boolean      stale    = false;               // Set to force clear of accepted
169 
170     private SourceUnit   parser   = null;                // A SourceUnit used to check the statement
171     private TokenStream  stream   = null;                // The TokenStream that backs the Parser
172     private Exception    error    = null;                // Any actual syntax error caught during parsing
173     private CSTNode      tree     = null;                // The top-level statement when parsed
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( /* !complete && */ !done ) {
212 
213             //
214             // Prompt
215 
216             System.out.print( line + "> ");
217 
218 
219             //
220             // Read a line.  If IOException or null, or command "exit", terminate
221             // processing.  
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;                                  // <<<< FLOW CONTROL <<<<<<<<
227             }
228 
229 
230             //
231             // First up, try to process the line as a command and proceed accordingly.
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;                                     // <<<< LOOP CONTROL <<<<<<<<
266 
267             }
268 
269 
270             //
271             // Otherwise, it's part of a statement.  If it's just whitespace,
272             // we'll just accept it and move on.  Otherwise, parsing is attempted
273             // on the cumulated statement text, and errors are reported.  The
274             // pending input is accepted or rejected based on that parsing.
275 
276             freshen();
277 
278             if( pending.trim() == "" ) {
279                 accept();
280                 continue;                                     // <<<< LOOP CONTROL <<<<<<<<
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         // Get and return the statement.
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   // SUPPORT ROUTINES
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         // Create the parser and attempt to parse the text as a top-level statement.
379 
380         try {
381             
382             parser = SourceUnit.create( "groovysh script", code, tolerance );
383             parser.parse();
384             tree = parser.getCST();
385 
386             /* see note on read(): 
387              * tree = parser.topLevelStatement();
388              *
389              * if( stream.atEnd() ) {
390              *     parsed = true;
391              * }
392              */
393             
394             parsed = true;
395         }
396 
397         //
398         // We report errors other than unexpected EOF to the user.
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   // COMMANDS
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         // A few synonyms
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