| /* |
| $Id$ |
| |
| Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved. |
| |
| Redistribution and use of this software and associated documentation |
| ("Software"), with or without modification, are permitted provided |
| that the following conditions are met: |
| |
| 1. Redistributions of source code must retain copyright |
| statements and notices. Redistributions must also contain a |
| copy of this document. |
| |
| 2. Redistributions in binary form must reproduce the |
| above copyright notice, this list of conditions and the |
| following disclaimer in the documentation and/or other |
| materials provided with the distribution. |
| |
| 3. The name "groovy" must not be used to endorse or promote |
| products derived from this Software without prior written |
| permission of The Codehaus. For written permission, |
| please contact info@codehaus.org. |
| |
| 4. Products derived from this Software may not be called "groovy" |
| nor may "groovy" appear in their names without prior written |
| permission of The Codehaus. "groovy" is a registered |
| trademark of The Codehaus. |
| |
| 5. Due credit should be given to The Codehaus - |
| http://groovy.codehaus.org/ |
| |
| THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS |
| ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT |
| NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
| FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL |
| THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, |
| INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED |
| OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| */ |
| package groovy.ui; |
| |
| import groovy.lang.Binding; |
| import groovy.lang.GroovyShell; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintStream; |
| import java.lang.reflect.Method; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.codehaus.groovy.control.CompilationFailedException; |
| import org.codehaus.groovy.control.SourceUnit; |
| import org.codehaus.groovy.runtime.InvokerHelper; |
| import org.codehaus.groovy.runtime.InvokerInvocationException; |
| import org.codehaus.groovy.sandbox.ui.Prompt; |
| import org.codehaus.groovy.sandbox.ui.PromptFactory; |
| import org.codehaus.groovy.tools.ErrorReporter; |
| |
| /** |
| * A simple interactive shell for evaluating groovy expressions |
| * on the command line |
| * |
| * @author <a href="mailto:james@coredevelopers.net">James Strachan</a> |
| * @author <a href="mailto:cpoirier@dreaming.org" >Chris Poirier</a> |
| * @author Yuri Schimke |
| * @author Brian McCallistair |
| * @author Guillaume Laforge |
| * @author Dierk Koenig, include the inspect command, June 2005 |
| * @version $Revision$ |
| */ |
| public class InteractiveShell { |
| private final GroovyShell shell; |
| private final Prompt prompt; |
| private final InputStream in; |
| private final PrintStream out; |
| private final PrintStream err; |
| private Object lastResult; |
| |
| |
| /** |
| * Entry point when called directly. |
| */ |
| public static void main(String args[]) { |
| try { |
| final InteractiveShell groovy = new InteractiveShell(); |
| groovy.run(args); |
| } |
| catch (Exception e) { |
| System.err.println("Caught: " + e); |
| e.printStackTrace(); |
| } |
| } |
| |
| |
| /** |
| * Default constructor. |
| */ |
| public InteractiveShell() { |
| this(System.in, System.out, System.err); |
| } |
| |
| |
| public InteractiveShell(final InputStream in, final PrintStream out, final PrintStream err) { |
| this(null,new Binding(), in, out, err); |
| } |
| |
| /** |
| * Constructs a new InteractiveShell instance |
| * |
| * @param binding The binding instance |
| * @param in The input stream to use |
| * @param out The output stream to use |
| * @param err The error stream to use |
| */ |
| public InteractiveShell(Binding binding, final InputStream in, final PrintStream out, final PrintStream err) { |
| this(null,binding,in,out,err); |
| } |
| |
| /** |
| * Constructs a new InteractiveShell instance |
| * |
| * @param parent The parent ClassLoader |
| * @param binding The binding instance |
| * @param in The input stream to use |
| * @param out The output stream to use |
| * @param err The error stream to use |
| */ |
| public InteractiveShell(ClassLoader parent,Binding binding, final InputStream in, final PrintStream out, final PrintStream err) { |
| this.in = in; |
| this.out = out; |
| this.err = err; |
| prompt = PromptFactory.buildPrompt(in, out, err); |
| prompt.setPrompt("groovy> "); |
| if(parent!= null) { |
| shell = new GroovyShell(parent,binding); |
| } |
| else { |
| shell = new GroovyShell(binding); |
| } |
| Map map = shell.getContext().getVariables(); |
| if (map.get("shell") != null) { |
| map.put("shell", shell); |
| } |
| } |
| |
| //--------------------------------------------------------------------------- |
| // COMMAND LINE PROCESSING LOOP |
| |
| /** |
| * Reads commands and statements from input stream and processes them. |
| */ |
| public void run(String[] args) throws Exception { |
| final String version = InvokerHelper.getVersion(); |
| |
| out.println("Let's get Groovy!"); |
| out.println("================"); |
| out.println("Version: " + version + " JVM: " + System.getProperty("java.vm.version")); |
| out.println("Type 'exit' to terminate the shell"); |
| out.println("Type 'help' for command help"); |
| out.println("Type 'go' to execute the statements"); |
| |
| boolean running = true; |
| while (running) { |
| // Read a single top-level statement from the command line, |
| // trapping errors as they happen. We quit on null. |
| final String command = read(); |
| if (command == null) { |
| close(); |
| break; |
| } |
| |
| reset(); |
| |
| if (command.length() > 0) { |
| // We have a command that parses, so evaluate it. |
| try { |
| lastResult = shell.evaluate(command, "CommandLine.groovy"); |
| out.println("\n===> " + lastResult); |
| } catch (CompilationFailedException e) { |
| err.println(e); |
| } catch (Throwable e) { |
| if (e instanceof InvokerInvocationException) { |
| InvokerInvocationException iie = (InvokerInvocationException) e; |
| e = iie.getCause(); |
| } |
| filterAndPrintStackTrace(e); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Filter stacktraces to show only relevant lines of the exception thrown. |
| * |
| * @param e the throwable whose stacktrace needs to be filtered |
| */ |
| private void filterAndPrintStackTrace(Throwable e) { |
| err.println("Caught: " + e); |
| StackTraceElement[] stackTrace = e.getStackTrace(); |
| for (int i = 0; i < stackTrace.length; i++) { |
| StackTraceElement element = stackTrace[i]; |
| String fileName = element.getFileName(); |
| if ((fileName==null || (!fileName.endsWith(".java")) && (!element.getClassName().startsWith("gjdk")))) { |
| err.println("\tat " + element); |
| } |
| } |
| } |
| |
| protected void close() { |
| prompt.close(); |
| } |
| |
| |
| //--------------------------------------------------------------------------- |
| // COMMAND LINE PROCESSING MACHINERY |
| |
| |
| private StringBuffer accepted = new StringBuffer(); // The statement text accepted to date |
| private String pending = null; // A line of statement text not yet accepted |
| private int line = 1; // The current line number |
| |
| private boolean stale = false; // Set to force clear of accepted |
| |
| private SourceUnit parser = null; // A SourceUnit used to check the statement |
| private Exception error = null; // Any actual syntax error caught during parsing |
| |
| |
| /** |
| * Resets the command-line processing machinery after use. |
| */ |
| |
| protected void reset() { |
| stale = true; |
| pending = null; |
| line = 1; |
| |
| parser = null; |
| error = null; |
| } |
| |
| |
| /** |
| * Reads a single statement from the command line. Also identifies |
| * and processes command shell commands. Returns the command text |
| * on success, or null when command processing is complete. |
| * <p/> |
| * NOTE: Changed, for now, to read until 'execute' is issued. At |
| * 'execute', the statement must be complete. |
| */ |
| |
| protected String read() { |
| reset(); |
| out.println(""); |
| |
| boolean complete = false; |
| boolean done = false; |
| |
| while (/* !complete && */ !done) { |
| |
| // Read a line. If IOException or null, or command "exit", terminate |
| // processing. |
| |
| try { |
| pending = prompt.readLine(); |
| } |
| catch (IOException e) { |
| } |
| |
| if (pending == null || (COMMAND_MAPPINGS.containsKey(pending) && ((Integer) COMMAND_MAPPINGS.get(pending)).intValue() == COMMAND_ID_EXIT)) { |
| return null; // <<<< FLOW CONTROL <<<<<<<< |
| } |
| |
| // First up, try to process the line as a command and proceed accordingly. |
| if (COMMAND_MAPPINGS.containsKey(pending)) { |
| int code = ((Integer) COMMAND_MAPPINGS.get(pending)).intValue(); |
| switch (code) { |
| case COMMAND_ID_HELP: |
| displayHelp(); |
| break; |
| |
| case COMMAND_ID_DISCARD: |
| reset(); |
| done = true; |
| break; |
| |
| case COMMAND_ID_DISPLAY: |
| displayStatement(); |
| break; |
| |
| case COMMAND_ID_EXPLAIN: |
| explainStatement(); |
| break; |
| |
| case COMMAND_ID_BINDING: |
| displayBinding(); |
| break; |
| |
| case COMMAND_ID_EXECUTE: |
| if (complete) { |
| done = true; |
| } |
| else { |
| err.println("statement not complete"); |
| } |
| break; |
| case COMMAND_ID_DISCARD_LOADED_CLASSES: |
| resetLoadedClasses(); |
| break; |
| case COMMAND_ID_INSPECT: |
| inspect(); |
| break; |
| } |
| |
| continue; // <<<< LOOP CONTROL <<<<<<<< |
| } |
| |
| // Otherwise, it's part of a statement. If it's just whitespace, |
| // we'll just accept it and move on. Otherwise, parsing is attempted |
| // on the cumulated statement text, and errors are reported. The |
| // pending input is accepted or rejected based on that parsing. |
| |
| freshen(); |
| |
| if (pending.trim().length() == 0) { |
| accept(); |
| continue; // <<<< LOOP CONTROL <<<<<<<< |
| } |
| |
| final String code = current(); |
| |
| if (parse(code, 1)) { |
| accept(); |
| complete = true; |
| } |
| else if (error == null) { |
| accept(); |
| } |
| else { |
| report(); |
| } |
| |
| } |
| |
| // Get and return the statement. |
| return accepted(complete); |
| } |
| |
| private void inspect() { |
| if (null == lastResult){ |
| err.println("nothing to inspect (preceding \"go\" missing?)"); |
| return; |
| } |
| // this should read: groovy.inspect.swingui.ObjectBrowser.inspect(lastResult) |
| // but this doesnt compile since ObjectBrowser.groovy is compiled after this class. |
| try { |
| Class browserClass = Class.forName("groovy.inspect.swingui.ObjectBrowser"); |
| Method inspectMethod = browserClass.getMethod("inspect", new Class[]{Object.class}); |
| inspectMethod.invoke(browserClass, new Object[]{lastResult}); |
| } catch (Exception e) { |
| err.println("cannot invoke ObjectBrowser"); |
| e.printStackTrace(); |
| } |
| } |
| |
| |
| /** |
| * Returns the accepted statement as a string. If not <code>complete</code>, |
| * returns the empty string. |
| */ |
| private String accepted(boolean complete) { |
| if (complete) { |
| return accepted.toString(); |
| } |
| return ""; |
| } |
| |
| |
| /** |
| * Returns the current statement, including pending text. |
| */ |
| private String current() { |
| return accepted.toString() + pending + "\n"; |
| } |
| |
| |
| /** |
| * Accepts the pending text into the statement. |
| */ |
| private void accept() { |
| accepted.append(pending).append("\n"); |
| line += 1; |
| } |
| |
| |
| /** |
| * Clears accepted if stale. |
| */ |
| private void freshen() { |
| if (stale) { |
| accepted.setLength(0); |
| stale = false; |
| } |
| } |
| |
| |
| //--------------------------------------------------------------------------- |
| // SUPPORT ROUTINES |
| |
| |
| /** |
| * Attempts to parse the specified code with the specified tolerance. |
| * Updates the <code>parser</code> and <code>error</code> members |
| * appropriately. Returns true if the text parsed, false otherwise. |
| * The attempts to identify and suppress errors resulting from the |
| * unfinished source text. |
| */ |
| private boolean parse(String code, int tolerance) { |
| boolean parsed = false; |
| |
| parser = null; |
| error = null; |
| |
| // Create the parser and attempt to parse the text as a top-level statement. |
| try { |
| parser = SourceUnit.create("groovysh script", code, tolerance); |
| parser.parse(); |
| |
| parsed = true; |
| } |
| |
| // We report errors other than unexpected EOF to the user. |
| catch (CompilationFailedException e) { |
| if (parser.getErrorCollector().getErrorCount() > 1 || !parser.failedWithUnexpectedEOF()) { |
| error = e; |
| } |
| } |
| catch (Exception e) { |
| error = e; |
| } |
| |
| return parsed; |
| } |
| |
| |
| /** |
| * Reports the last parsing error to the user. |
| */ |
| |
| private void report() { |
| err.println("Discarding invalid text:"); |
| new ErrorReporter(error, false).write(err); |
| } |
| |
| //----------------------------------------------------------------------- |
| // COMMANDS |
| |
| private static final int COMMAND_ID_EXIT = 0; |
| private static final int COMMAND_ID_HELP = 1; |
| private static final int COMMAND_ID_DISCARD = 2; |
| private static final int COMMAND_ID_DISPLAY = 3; |
| private static final int COMMAND_ID_EXPLAIN = 4; |
| private static final int COMMAND_ID_EXECUTE = 5; |
| private static final int COMMAND_ID_BINDING = 6; |
| private static final int COMMAND_ID_DISCARD_LOADED_CLASSES = 7; |
| private static final int COMMAND_ID_INSPECT = 8; |
| |
| private static final int LAST_COMMAND_ID = 8; |
| |
| private static final String[] COMMANDS = { "exit", "help", "discard", "display", "explain", "execute", "binding", "discardclasses", "inspect" }; |
| |
| private static final Map COMMAND_MAPPINGS = new HashMap(); |
| |
| static { |
| for (int i = 0; i <= LAST_COMMAND_ID; i++) { |
| COMMAND_MAPPINGS.put(COMMANDS[i], new Integer(i)); |
| } |
| |
| // A few synonyms |
| |
| COMMAND_MAPPINGS.put("quit", new Integer(COMMAND_ID_EXIT)); |
| COMMAND_MAPPINGS.put("go", new Integer(COMMAND_ID_EXECUTE)); |
| } |
| |
| private static final Map COMMAND_HELP = new HashMap(); |
| |
| static { |
| COMMAND_HELP.put(COMMANDS[COMMAND_ID_EXIT], "exit/quit - terminates processing"); |
| COMMAND_HELP.put(COMMANDS[COMMAND_ID_HELP], "help - displays this help text"); |
| COMMAND_HELP.put(COMMANDS[COMMAND_ID_DISCARD], "discard - discards the current statement"); |
| COMMAND_HELP.put(COMMANDS[COMMAND_ID_DISPLAY], "display - displays the current statement"); |
| COMMAND_HELP.put(COMMANDS[COMMAND_ID_EXPLAIN], "explain - explains the parsing of the current statement (currently disabled)"); |
| COMMAND_HELP.put(COMMANDS[COMMAND_ID_EXECUTE], "execute/go - temporary command to cause statement execution"); |
| COMMAND_HELP.put(COMMANDS[COMMAND_ID_BINDING], "binding - shows the binding used by this interactive shell"); |
| COMMAND_HELP.put(COMMANDS[COMMAND_ID_DISCARD_LOADED_CLASSES], |
| "discardclasses - discards all former unbound class definitions"); |
| COMMAND_HELP.put(COMMANDS[COMMAND_ID_INSPECT], "inspect - opens ObjectBrowser on expression returned from previous \"go\""); |
| } |
| |
| |
| /** |
| * Displays help text about available commands. |
| */ |
| private void displayHelp() { |
| out.println("Available commands (must be entered without extraneous characters):"); |
| for (int i = 0; i <= LAST_COMMAND_ID; i++) { |
| out.println((String) COMMAND_HELP.get(COMMANDS[i])); |
| } |
| } |
| |
| |
| /** |
| * Displays the accepted statement. |
| */ |
| private void displayStatement() { |
| final String[] lines = accepted.toString().split("\n"); |
| for (int i = 0; i < lines.length; i++) { |
| out.println((i + 1) + "> " + lines[i]); |
| } |
| } |
| |
| /** |
| * Displays the current binding used when instanciating the shell. |
| */ |
| private void displayBinding() { |
| out.println("Available variables in the current binding"); |
| Binding context = shell.getContext(); |
| Map variables = context.getVariables(); |
| Set set = variables.keySet(); |
| if (set.isEmpty()) { |
| out.println("The current binding is empty."); |
| } |
| else { |
| for (Iterator it = set.iterator(); it.hasNext();) { |
| String key = (String) it.next(); |
| out.println(key + " = " + variables.get(key)); |
| } |
| } |
| } |
| |
| |
| /** |
| * Attempts to parse the accepted statement and display the |
| * parse tree for it. |
| */ |
| private void explainStatement() { |
| if (parse(accepted(true), 10) || error == null) { |
| out.println("Parse tree:"); |
| //out.println(tree); |
| } |
| else { |
| out.println("Statement does not parse"); |
| } |
| } |
| |
| private void resetLoadedClasses() { |
| shell.resetLoadedClasses(); |
| out.println("all former unbound class definitions are discarded"); |
| } |
| } |
| |