View Javadoc

1   /*
2    $Id: InteractiveShell.java,v 1.14 2004/09/28 20:08:34 yuri 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.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   // COMMAND LINE PROCESSING LOOP
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             // 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               close();
132                 break;
133             }
134 
135             reset();
136 
137             if( command.length() > 0 ) {
138               
139                 //
140                 // We have a command that parses, so evaluate it.
141 
142                 try {
143                     Object answer = shell.evaluate(command, "CommandLine" + counter++ +".groovy");
144                     // System.out.println(InvokerHelper.inspect(answer));
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   // COMMAND LINE PROCESSING MACHINERY
166 
167 
168 
169 
170 
171     private StringBuffer accepted = new StringBuffer();  // The statement text accepted to date
172     private String       pending  = null;                // A line of statement text not yet accepted
173     private int          line     = 1;                   // The current line number
174 
175     private boolean      stale    = false;               // Set to force clear of accepted
176 
177     private SourceUnit   parser   = null;                // A SourceUnit used to check the statement
178     private TokenStream  stream   = null;                // The TokenStream that backs the Parser
179     private Exception    error    = null;                // Any actual syntax error caught during parsing
180     private CSTNode      tree     = null;                // The top-level statement when parsed
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( /* !complete && */ !done ) {
219 
220             //
221             // Read a line.  If IOException or null, or command "exit", terminate
222             // processing.  
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;                                  // <<<< FLOW CONTROL <<<<<<<<
228             }
229 
230 
231             //
232             // First up, try to process the line as a command and proceed accordingly.
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;                                     // <<<< LOOP CONTROL <<<<<<<<
267 
268             }
269 
270 
271             //
272             // Otherwise, it's part of a statement.  If it's just whitespace,
273             // we'll just accept it and move on.  Otherwise, parsing is attempted
274             // on the cumulated statement text, and errors are reported.  The
275             // pending input is accepted or rejected based on that parsing.
276 
277             freshen();
278 
279             if( pending.trim() == "" ) {
280                 accept();
281                 continue;                                     // <<<< LOOP CONTROL <<<<<<<<
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         // Get and return the statement.
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   // SUPPORT ROUTINES
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         // Create the parser and attempt to parse the text as a top-level statement.
376 
377         try {
378             
379             parser = SourceUnit.create( "groovysh script", code, tolerance );
380             parser.parse();
381             tree = parser.getCST();
382 
383             /* see note on read(): 
384              * tree = parser.topLevelStatement();
385              *
386              * if( stream.atEnd() ) {
387              *     parsed = true;
388              * }
389              */
390             
391             parsed = true;
392         }
393 
394         //
395         // We report errors other than unexpected EOF to the user.
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   // COMMANDS
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         // A few synonyms
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