KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > groovy > ui > InteractiveShell


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 JavaDoc;
60 import java.io.InputStream JavaDoc;
61 import java.io.PrintStream JavaDoc;
62 import java.util.HashMap JavaDoc;
63 import java.util.Iterator JavaDoc;
64 import java.util.Map JavaDoc;
65 import java.util.Set JavaDoc;
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 JavaDoc in;
82     private final PrintStream JavaDoc out;
83     private final PrintStream JavaDoc err;
84
85
86     /**
87      * Entry point when called directly.
88      */

89     public static void main(String JavaDoc args[]) {
90         try {
91             final InteractiveShell groovy = new InteractiveShell();
92             groovy.run(args);
93         } catch (Exception JavaDoc 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 JavaDoc in, final PrintStream JavaDoc out, final PrintStream JavaDoc err) {
109         this(new Binding(), in, out, err);
110     }
111
112     public InteractiveShell(Binding binding, final InputStream JavaDoc in, final PrintStream JavaDoc out, final PrintStream JavaDoc 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 JavaDoc[] args) throws Exception JavaDoc {
128         final String JavaDoc 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 JavaDoc 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 JavaDoc e) {
155                     err.println("Exception: " + e.getMessage());
156                     e.printStackTrace(err);
157                     new ErrorReporter(e, false).write(err);
158                 } catch (Throwable JavaDoc 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 JavaDoc accepted = new StringBuffer JavaDoc(); // The statement text accepted to date
182
private String JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc e) {
233             }
234
235             if (pending == null || (COMMAND_MAPPINGS.containsKey(pending) && ((Integer JavaDoc) 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 JavaDoc) 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc[] COMMANDS = {"exit", "help", "discard", "display", "explain", "execute", "binding"};
417
418     private static final Map JavaDoc COMMAND_MAPPINGS = new HashMap JavaDoc();
419
420     static {
421         for (int i = 0; i <= LAST_COMMAND_ID; i++) {
422             COMMAND_MAPPINGS.put(COMMANDS[i], new Integer JavaDoc(i));
423         }
424
425         // A few synonyms
426

427         COMMAND_MAPPINGS.put("quit", new Integer JavaDoc(COMMAND_ID_EXIT));
428         COMMAND_MAPPINGS.put("go", new Integer JavaDoc(COMMAND_ID_EXECUTE));
429     }
430
431     private static final Map JavaDoc COMMAND_HELP = new HashMap JavaDoc();
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 JavaDoc) COMMAND_HELP.get(COMMANDS[i]));
451         }
452     }
453
454
455     /**
456      * Displays the accepted statement.
457      */

458     private void displayStatement() {
459         final String JavaDoc[] 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 JavaDoc variables = context.getVariables();
472         Set JavaDoc set = variables.keySet();
473         if (set.isEmpty()) {
474             out.println("The current binding is empty.");
475         } else {
476             for (Iterator JavaDoc it = set.iterator(); it.hasNext();) {
477                 String JavaDoc key = (String JavaDoc) 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
498
Popular Tags