View Javadoc

1   /*
2    $Id: InteractiveShell.java,v 1.30 2005/07/13 19:28:07 cstein 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  
51  import java.io.IOException;
52  import java.io.InputStream;
53  import java.io.PrintStream;
54  import java.lang.reflect.Method;
55  import java.util.HashMap;
56  import java.util.Iterator;
57  import java.util.Map;
58  import java.util.Set;
59  
60  import org.codehaus.groovy.control.CompilationFailedException;
61  import org.codehaus.groovy.control.SourceUnit;
62  import org.codehaus.groovy.runtime.InvokerHelper;
63  import org.codehaus.groovy.runtime.InvokerInvocationException;
64  import org.codehaus.groovy.sandbox.ui.Prompt;
65  import org.codehaus.groovy.sandbox.ui.PromptFactory;
66  import org.codehaus.groovy.tools.ErrorReporter;
67  
68  /***
69   * A simple interactive shell for evaluating groovy expressions
70   * on the command line
71   *
72   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
73   * @author <a href="mailto:cpoirier@dreaming.org"   >Chris Poirier</a>
74   * @author Yuri Schimke
75   * @author Brian McCallistair
76   * @author Guillaume Laforge
77   * @author Dierk Koenig, include the inspect command, June 2005
78   * @version $Revision: 1.30 $
79   */
80  public class InteractiveShell {
81      private final GroovyShell shell;
82      private final Prompt prompt;
83      private final InputStream in;
84      private final PrintStream out;
85      private final PrintStream err;
86      private Object lastResult;
87  
88  
89      /***
90       * Entry point when called directly.
91       */
92      public static void main(String args[]) {
93          try {
94              final InteractiveShell groovy = new InteractiveShell();
95              groovy.run(args);
96          }
97          catch (Exception e) {
98              System.err.println("Caught: " + e);
99              e.printStackTrace();
100         }
101     }
102 
103 
104     /***
105      * Default constructor.
106      */
107     public InteractiveShell() {
108         this(System.in, System.out, System.err);
109     }
110 
111 
112     public InteractiveShell(final InputStream in, final PrintStream out, final PrintStream err) {
113         this(new Binding(), in, out, err);
114     }
115 
116     public InteractiveShell(Binding binding, final InputStream in, final PrintStream out, final PrintStream err) {
117         this.in = in;
118         this.out = out;
119         this.err = err;
120         prompt = PromptFactory.buildPrompt(in, out, err);
121         prompt.setPrompt("groovy> ");
122         shell = new GroovyShell(binding);
123         Map map = shell.getContext().getVariables();
124         if (map.get("shell") != null) {
125             map.put("shell", shell);
126         }
127     }
128 
129     //---------------------------------------------------------------------------
130     // COMMAND LINE PROCESSING LOOP
131 
132     /***
133      * Reads commands and statements from input stream and processes them.
134      */
135     public void run(String[] args) throws Exception {
136         final String version = InvokerHelper.getVersion();
137 
138         out.println("Lets get Groovy!");
139         out.println("================");
140         out.println("Version: " + version + " JVM: " + System.getProperty("java.vm.version"));
141         out.println("Type 'exit' to terminate the shell");
142         out.println("Type 'help' for command help");
143         out.println("Type 'go' to execute the statements");
144 
145         boolean running = true;
146         while (running) {
147             // Read a single top-level statement from the command line,
148             // trapping errors as they happen.  We quit on null.
149             final String command = read();
150             if (command == null) {
151                 close();
152                 break;
153             }
154 
155             reset();
156 
157             if (command.length() > 0) {
158                 // We have a command that parses, so evaluate it.
159                 try {
160                     lastResult = shell.evaluate(command, "CommandLine.groovy");
161                 } catch (CompilationFailedException e) {
162                     err.println(e);
163                 } catch (Throwable e) {
164                     if (e instanceof InvokerInvocationException) {
165                         InvokerInvocationException iie = (InvokerInvocationException) e;
166                         e = iie.getCause();
167                     }
168                     err.println("Caught: " + e);
169                     StackTraceElement[] stackTrace = e.getStackTrace();
170                     for (int i = 0; i < stackTrace.length; i++) {
171                         StackTraceElement element = stackTrace[i];
172                         String fileName = element.getFileName();
173                         if (fileName==null || (!fileName.endsWith(".java"))) {
174                             err.println("\tat " + element);
175                         }
176                     }
177                 }
178             }
179         }
180     }
181 
182 
183     protected void close() {
184         prompt.close();
185     }
186 
187 
188     //---------------------------------------------------------------------------
189     // COMMAND LINE PROCESSING MACHINERY
190 
191 
192     private StringBuffer accepted = new StringBuffer(); // The statement text accepted to date
193     private String pending = null;                      // A line of statement text not yet accepted
194     private int line = 1;                               // The current line number
195 
196     private boolean stale = false;                      // Set to force clear of accepted
197 
198     private SourceUnit parser = null;                   // A SourceUnit used to check the statement
199     private Exception error = null;                     // Any actual syntax error caught during parsing
200 
201 
202     /***
203      * Resets the command-line processing machinery after use.
204      */
205 
206     protected void reset() {
207         stale = true;
208         pending = null;
209         line = 1;
210 
211         parser = null;
212         error = null;
213     }
214 
215 
216     /***
217      * Reads a single statement from the command line.  Also identifies
218      * and processes command shell commands.  Returns the command text
219      * on success, or null when command processing is complete.
220      * <p/>
221      * NOTE: Changed, for now, to read until 'execute' is issued.  At
222      * 'execute', the statement must be complete.
223      */
224 
225     protected String read() {
226         reset();
227         out.println("");
228 
229         boolean complete = false;
230         boolean done = false;
231 
232         while (/* !complete && */ !done) {
233 
234             // Read a line.  If IOException or null, or command "exit", terminate
235             // processing.
236 
237             try {
238                 pending = prompt.readLine();
239             }
240             catch (IOException e) {
241             }
242 
243             if (pending == null || (COMMAND_MAPPINGS.containsKey(pending) && ((Integer) COMMAND_MAPPINGS.get(pending)).intValue() == COMMAND_ID_EXIT)) {
244                 return null;                                  // <<<< FLOW CONTROL <<<<<<<<
245             }
246 
247             // First up, try to process the line as a command and proceed accordingly.
248             if (COMMAND_MAPPINGS.containsKey(pending)) {
249                 int code = ((Integer) COMMAND_MAPPINGS.get(pending)).intValue();
250                 switch (code) {
251                     case COMMAND_ID_HELP:
252                         displayHelp();
253                         break;
254 
255                     case COMMAND_ID_DISCARD:
256                         reset();
257                         done = true;
258                         break;
259 
260                     case COMMAND_ID_DISPLAY:
261                         displayStatement();
262                         break;
263 
264                     case COMMAND_ID_EXPLAIN:
265                         explainStatement();
266                         break;
267 
268                     case COMMAND_ID_BINDING:
269                         displayBinding();
270                         break;
271 
272                     case COMMAND_ID_EXECUTE:
273                         if (complete) {
274                             done = true;
275                         }
276                         else {
277                             err.println("statement not complete");
278                         }
279                         break;
280                     case COMMAND_ID_DISCARD_LOADED_CLASSES:
281                         resetLoadedClasses();
282                         break;
283                     case COMMAND_ID_INSPECT:
284                         inspect();
285                         break;
286                 }
287 
288                 continue;                                     // <<<< LOOP CONTROL <<<<<<<<
289             }
290 
291             // Otherwise, it's part of a statement.  If it's just whitespace,
292             // we'll just accept it and move on.  Otherwise, parsing is attempted
293             // on the cumulated statement text, and errors are reported.  The
294             // pending input is accepted or rejected based on that parsing.
295 
296             freshen();
297 
298             if (pending.trim().equals("")) {
299                 accept();
300                 continue;                                     // <<<< LOOP CONTROL <<<<<<<<
301             }
302 
303             final String code = current();
304 
305             if (parse(code, 1)) {
306                 accept();
307                 complete = true;
308             }
309             else if (error == null) {
310                 accept();
311             }
312             else {
313                 report();
314             }
315 
316         }
317 
318         // Get and return the statement.
319         return accepted(complete);
320     }
321 
322     private void inspect() {
323         if (null == lastResult){
324             err.println("nothing to inspect (preceding \"go\" missing?)");
325             return;
326         }
327         // this should read: groovy.inspect.swingui.ObjectBrowser.inspect(lastResult)
328         // but this doesnt compile since ObjectBrowser.groovy is compiled after this class.
329         try {
330             Class browserClass = Class.forName("groovy.inspect.swingui.ObjectBrowser");
331             Method inspectMethod = browserClass.getMethod("inspect", new Class[]{Object.class});
332             inspectMethod.invoke(browserClass, new Object[]{lastResult});
333         } catch (Exception e) {
334             err.println("cannot invoke ObjectBrowser");
335             e.printStackTrace();
336         }
337     }
338 
339 
340     /***
341      * Returns the accepted statement as a string.  If not <code>complete</code>,
342      * returns the empty string.
343      */
344     private String accepted(boolean complete) {
345         if (complete) {
346             return accepted.toString();
347         }
348         return "";
349     }
350 
351 
352     /***
353      * Returns the current statement, including pending text.
354      */
355     private String current() {
356         return accepted.toString() + pending + "\n";
357     }
358 
359 
360     /***
361      * Accepts the pending text into the statement.
362      */
363     private void accept() {
364         accepted.append(pending).append("\n");
365         line += 1;
366     }
367 
368 
369     /***
370      * Clears accepted if stale.
371      */
372     private void freshen() {
373         if (stale) {
374             accepted.setLength(0);
375             stale = false;
376         }
377     }
378 
379 
380     //---------------------------------------------------------------------------
381     // SUPPORT ROUTINES
382 
383 
384     /***
385      * Attempts to parse the specified code with the specified tolerance.
386      * Updates the <code>parser</code> and <code>error</code> members
387      * appropriately.  Returns true if the text parsed, false otherwise.
388      * The attempts to identify and suppress errors resulting from the
389      * unfinished source text.
390      */
391     private boolean parse(String code, int tolerance) {
392         boolean parsed = false;
393 
394         parser = null;
395         error = null;
396 
397         // Create the parser and attempt to parse the text as a top-level statement.
398         try {
399             parser = SourceUnit.create("groovysh script", code, tolerance);
400             parser.parse();
401 
402             /* see note on read():
403              * tree = parser.topLevelStatement();
404              *
405              * if( stream.atEnd() ) {
406              *     parsed = true;
407              * }
408              */
409             parsed = true;
410         }
411 
412         // We report errors other than unexpected EOF to the user.
413         catch (CompilationFailedException e) {
414             if (parser.getErrorCollector().getErrorCount() > 1 || !parser.failedWithUnexpectedEOF()) {
415                 error = e;
416             }
417         }
418         catch (Exception e) {
419             error = e;
420         }
421 
422         return parsed;
423     }
424 
425 
426     /***
427      * Reports the last parsing error to the user.
428      */
429 
430     private void report() {
431         err.println("Discarding invalid text:");
432         new ErrorReporter(error, false).write(err);
433     }
434 
435     //-----------------------------------------------------------------------
436     // COMMANDS
437 
438     private static final int COMMAND_ID_EXIT = 0;
439     private static final int COMMAND_ID_HELP = 1;
440     private static final int COMMAND_ID_DISCARD = 2;
441     private static final int COMMAND_ID_DISPLAY = 3;
442     private static final int COMMAND_ID_EXPLAIN = 4;
443     private static final int COMMAND_ID_EXECUTE = 5;
444     private static final int COMMAND_ID_BINDING = 6;
445     private static final int COMMAND_ID_DISCARD_LOADED_CLASSES = 7;
446     private static final int COMMAND_ID_INSPECT = 8;
447 
448     private static final int LAST_COMMAND_ID = 8;
449 
450     private static final String[] COMMANDS = {"exit", "help", "discard", "display", "explain", "execute", "binding", "discardclasses", "inspect"};
451 
452     private static final Map COMMAND_MAPPINGS = new HashMap();
453 
454     static {
455         for (int i = 0; i <= LAST_COMMAND_ID; i++) {
456             COMMAND_MAPPINGS.put(COMMANDS[i], new Integer(i));
457         }
458 
459         // A few synonyms
460 
461         COMMAND_MAPPINGS.put("quit", new Integer(COMMAND_ID_EXIT));
462         COMMAND_MAPPINGS.put("go", new Integer(COMMAND_ID_EXECUTE));
463     }
464 
465     private static final Map COMMAND_HELP = new HashMap();
466 
467     static {
468         COMMAND_HELP.put(COMMANDS[COMMAND_ID_EXIT], "exit/quit        - terminates processing");
469         COMMAND_HELP.put(COMMANDS[COMMAND_ID_HELP], "help             - displays this help text");
470         COMMAND_HELP.put(COMMANDS[COMMAND_ID_DISCARD], "discard           - discards the current statement");
471         COMMAND_HELP.put(COMMANDS[COMMAND_ID_DISPLAY], "display           - displays the current statement");
472         COMMAND_HELP.put(COMMANDS[COMMAND_ID_EXPLAIN], "explain           - explains the parsing of the current statement (currently disabled)");
473         COMMAND_HELP.put(COMMANDS[COMMAND_ID_EXECUTE], "execute/go        - temporary command to cause statement execution");
474         COMMAND_HELP.put(COMMANDS[COMMAND_ID_BINDING], "binding           - shows the binding used by this interactive shell");
475         COMMAND_HELP.put(COMMANDS[COMMAND_ID_DISCARD_LOADED_CLASSES], "discardclasses    - discards all former unbound class definitions");
476         COMMAND_HELP.put(COMMANDS[COMMAND_ID_INSPECT], "inspect           - opens ObjectBrowser on expression returned from previous \"go\"");
477     }
478 
479 
480     /***
481      * Displays help text about available commands.
482      */
483     private void displayHelp() {
484         out.println("Available commands (must be entered without extraneous characters):");
485         for (int i = 0; i <= LAST_COMMAND_ID; i++) {
486             out.println((String) COMMAND_HELP.get(COMMANDS[i]));
487         }
488     }
489 
490 
491     /***
492      * Displays the accepted statement.
493      */
494     private void displayStatement() {
495         final String[] lines = accepted.toString().split("\n");
496         for (int i = 0; i < lines.length; i++) {
497             out.println((i + 1) + "> " + lines[i]);
498         }
499     }
500 
501     /***
502      * Displays the current binding used when instanciating the shell.
503      */
504     private void displayBinding() {
505         out.println("Available variables in the current binding");
506         Binding context = shell.getContext();
507         Map variables = context.getVariables();
508         Set set = variables.keySet();
509         if (set.isEmpty()) {
510             out.println("The current binding is empty.");
511         }
512         else {
513             for (Iterator it = set.iterator(); it.hasNext();) {
514                 String key = (String) it.next();
515                 out.println(key + " = " + variables.get(key));
516             }
517         }
518     }
519 
520 
521     /***
522      * Attempts to parse the accepted statement and display the
523      * parse tree for it.
524      */
525     private void explainStatement() {
526         if (parse(accepted(true), 10) || error == null) {
527             out.println("Parse tree:");
528             //out.println(tree);
529         }
530         else {
531             out.println("Statement does not parse");
532         }
533     }
534 
535     private void resetLoadedClasses() {
536         shell.resetLoadedClasses();
537         out.println("all former unbound class definitions are discarded");
538     }
539 }
540