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