View Javadoc

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