View Javadoc

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