# HG changeset patch # User sundar # Date 1440413740 -19800 # Node ID 2b653e4e7d65449faca329d760170f00949b2cc0 # Parent 5d7dd8dc772985cbbacd6b07f32d890b84706b28 8134279: jjs should support multiple line input to complete incomplete code Reviewed-by: attila, hannesw diff -r 5d7dd8dc7729 -r 2b653e4e7d65 nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java Mon Aug 24 09:12:35 2015 +0200 +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java Mon Aug 24 16:25:40 2015 +0530 @@ -30,6 +30,7 @@ import java.util.HashSet; import java.util.Set; import java.util.function.Function; +import java.util.function.Supplier; import jdk.internal.jline.console.history.FileHistory; import jdk.internal.jline.console.history.History; import jdk.nashorn.api.scripting.AbstractJSObject; @@ -48,6 +49,7 @@ s.add("forEach"); s.add("print"); s.add("size"); + s.add("toString"); props = Collections.unmodifiableSet(s); } @@ -68,6 +70,8 @@ return (Runnable)this::print; case "size": return hist.size(); + case "toString": + return (Supplier)this::toString; } return UNDEFINED; } @@ -82,7 +86,11 @@ @Override public String toString() { - return "[object history]"; + final StringBuilder buf = new StringBuilder(); + for (History.Entry e : hist) { + buf.append(e.value()).append('\n'); + } + return buf.toString(); } @Override diff -r 5d7dd8dc7729 -r 2b653e4e7d65 nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java Mon Aug 24 09:12:35 2015 +0200 +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java Mon Aug 24 16:25:40 2015 +0530 @@ -99,11 +99,12 @@ protected int readEvalPrint(final Context context, final Global global) { final ScriptEnvironment env = context.getEnv(); final String prompt = bundle.getString("shell.prompt"); + final String prompt2 = bundle.getString("shell.prompt2"); final PrintWriter err = context.getErr(); final Global oldGlobal = Context.getGlobal(); final boolean globalChanged = (oldGlobal != global); final PropertiesHelper propsHelper = new PropertiesHelper(env._classpath); - final Completer completer = new NashornCompleter(context, global, this, propsHelper); + final NashornCompleter completer = new NashornCompleter(context, global, this, propsHelper); try (final Console in = new Console(System.in, System.out, HIST_FILE, completer)) { if (globalChanged) { @@ -153,7 +154,32 @@ continue; } - evalImpl(context, global, source, err, env._dump_on_error); + try { + final Object res = context.eval(global, source, global, ""); + if (res != ScriptRuntime.UNDEFINED) { + err.println(JSType.toString(res)); + } + } catch (final Exception exp) { + // Is this a ECMAScript SyntaxError at last column (of the single line)? + // If so, it is because parser expected more input but got EOF. Try to + // to more lines from the user (multiline edit support). + + if (completer.isSyntaxErrorAt(exp, 1, source.length())) { + final String fullSrc = completer.readMoreLines(source, exp, in, prompt2, err); + + // check if we succeeded in getting complete code. + if (fullSrc != null && !fullSrc.isEmpty()) { + evalImpl(context, global, fullSrc, err, env._dump_on_error); + } // else ignore, error reported already by 'completer.readMoreLines' + } else { + + // can't read more lines to have parseable/complete code. + err.println(exp); + if (env._dump_on_error) { + exp.printStackTrace(err); + } + } + } } } catch (final Exception e) { err.println(e); diff -r 5d7dd8dc7729 -r 2b653e4e7d65 nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java Mon Aug 24 09:12:35 2015 +0200 +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java Mon Aug 24 16:25:40 2015 +0530 @@ -25,9 +25,12 @@ package jdk.nashorn.tools.jjs; +import java.io.PrintWriter; +import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; import jdk.internal.jline.console.completer.Completer; +import jdk.internal.jline.console.UserInterruptException; import jdk.nashorn.api.tree.AssignmentTree; import jdk.nashorn.api.tree.BinaryTree; import jdk.nashorn.api.tree.CompilationUnitTree; @@ -46,14 +49,21 @@ import jdk.nashorn.api.tree.Parser; import jdk.nashorn.api.scripting.NashornException; import jdk.nashorn.tools.PartialParser; +import jdk.nashorn.internal.objects.NativeSyntaxError; import jdk.nashorn.internal.objects.Global; +import jdk.nashorn.internal.runtime.ECMAException; import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.ScriptRuntime; -// A simple source completer for nashorn +/** + * A simple source completer for nashorn. Handles code completion for + * expressions as well as handles incomplete single line code. + */ final class NashornCompleter implements Completer { private final Context context; private final Global global; + private final ScriptEnvironment env; private final PartialParser partialParser; private final PropertiesHelper propsHelper; private final Parser parser; @@ -62,9 +72,110 @@ final PartialParser partialParser, final PropertiesHelper propsHelper) { this.context = context; this.global = global; + this.env = context.getEnv(); this.partialParser = partialParser; this.propsHelper = propsHelper; - this.parser = Parser.create(); + this.parser = createParser(env); + } + + + /** + * Is this a ECMAScript SyntaxError thrown for parse issue at the given line and column? + * + * @param exp Throwable to check + * @param line line number to check + * @param column column number to check + * + * @return true if the given Throwable is a ECMAScript SyntaxError at given line, column + */ + boolean isSyntaxErrorAt(final Throwable exp, final int line, final int column) { + if (exp instanceof ECMAException) { + final ECMAException eexp = (ECMAException)exp; + if (eexp.getThrown() instanceof NativeSyntaxError) { + return isParseErrorAt(eexp.getCause(), line, column); + } + } + + return false; + } + + /** + * Is this a parse error at the given line and column? + * + * @param exp Throwable to check + * @param line line number to check + * @param column column number to check + * + * @return true if the given Throwable is a parser error at given line, column + */ + boolean isParseErrorAt(final Throwable exp, final int line, final int column) { + if (exp instanceof NashornException) { + final NashornException nexp = (NashornException)exp; + return nexp.getLineNumber() == line && nexp.getColumnNumber() == column; + } + return false; + } + + + /** + * Read more lines of code if we got SyntaxError at EOF and we can it fine by + * by reading more lines of code from the user. This is used for multiline editing. + * + * @param firstLine First line of code from the user + * @param exp Exception thrown by evaluting first line code + * @param in Console to get read more lines from the user + * @param prompt Prompt to be printed to read more lines from the user + * @param err PrintWriter to print any errors in the proecess of reading + * + * @return Complete code read from the user including the first line. This is null + * if any error or the user discarded multiline editing by Ctrl-C. + */ + String readMoreLines(final String firstLine, final Exception exp, final Console in, + final String prompt, final PrintWriter err) { + int line = 1; + final StringBuilder buf = new StringBuilder(firstLine); + while (true) { + buf.append('\n'); + String curLine = null; + try { + curLine = in.readLine(prompt); + buf.append(curLine); + line++; + } catch (final Throwable th) { + if (th instanceof UserInterruptException) { + // Ctrl-C from user - discard the whole thing silently! + return null; + } else { + // print anything else -- but still discard the code + err.println(th); + if (env._dump_on_error) { + th.printStackTrace(err); + } + return null; + } + } + + final String allLines = buf.toString(); + try { + parser.parse("", allLines, null); + } catch (final Exception pexp) { + // Do we have a parse error at the end of current line? + // If so, read more lines from the console. + if (isParseErrorAt(pexp, line, curLine.length())) { + continue; + } else { + // print anything else and bail out! + err.println(pexp); + if (env._dump_on_error) { + pexp.printStackTrace(err); + } + return null; + } + } + + // We have complete parseable code! + return buf.toString(); + } } // Pattern to match a unfinished member selection expression. object part and "." @@ -116,6 +227,9 @@ } } + // Internals only below this point + + // fill properties of the incomplete member expression private int completeMemberSelect(final String exprStr, final int cursor, final List result, final MemberSelectTree select, final boolean endsWithDot) { final ExpressionTree objExpr = select.getExpression(); @@ -148,6 +262,7 @@ return cursor; } + // fill properties for the given (partial) identifer private int completeIdentifier(final String test, final int cursor, final List result, final IdentifierTree ident) { final String name = ident.getName(); @@ -175,6 +290,7 @@ return null; } + // get the right most expreesion of the given expression private Tree getRightMostExpression(final ExpressionTree expr) { return expr.accept(new SimpleTreeVisitorES5_1() { @Override @@ -234,4 +350,27 @@ } }, null); } + + // create a Parser instance that uses compatible command line options of the + // current ScriptEnvironment being used for REPL. + private static Parser createParser(final ScriptEnvironment env) { + final List args = new ArrayList<>(); + if (env._const_as_var) { + args.add("--const-as-var"); + } + + if (env._no_syntax_extensions) { + args.add("-nse"); + } + + if (env._scripting) { + args.add("-scripting"); + } + + if (env._strict) { + args.add("-strict"); + } + + return Parser.create(args.toArray(new String[0])); + } } diff -r 5d7dd8dc7729 -r 2b653e4e7d65 nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PackagesHelper.java --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PackagesHelper.java Mon Aug 24 09:12:35 2015 +0200 +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PackagesHelper.java Mon Aug 24 16:25:40 2015 +0530 @@ -86,7 +86,7 @@ // LRU cache for java package properties lists private final LinkedHashMap> propsCache = - new LinkedHashMap<>(32, 0.75f, true) { + new LinkedHashMap>(32, 0.75f, true) { private static final int CACHE_SIZE = 100; private static final long serialVersionUID = 1; diff -r 5d7dd8dc7729 -r 2b653e4e7d65 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/resources/Shell.properties --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/resources/Shell.properties Mon Aug 24 09:12:35 2015 +0200 +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/resources/Shell.properties Mon Aug 24 16:25:40 2015 +0530 @@ -29,4 +29,5 @@ shell.prompt=jjs> +shell.prompt2=...>