8134279: jjs should support multiple line input to complete incomplete code

Reviewed-by: attila, hannesw
This commit is contained in:
Athijegannathan Sundararajan 2015-08-24 16:25:40 +05:30
parent 373f5906d4
commit 3413347722
5 changed files with 180 additions and 6 deletions

View File

@ -30,6 +30,7 @@ import java.util.Collections;
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 @@ final class HistoryObject extends AbstractJSObject {
s.add("forEach");
s.add("print");
s.add("size");
s.add("toString");
props = Collections.unmodifiableSet(s);
}
@ -68,6 +70,8 @@ final class HistoryObject extends AbstractJSObject {
return (Runnable)this::print;
case "size":
return hist.size();
case "toString":
return (Supplier<String>)this::toString;
}
return UNDEFINED;
}
@ -82,7 +86,11 @@ final class HistoryObject extends AbstractJSObject {
@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

View File

@ -99,11 +99,12 @@ public final class Main extends Shell {
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 @@ public final class Main extends Shell {
continue;
}
evalImpl(context, global, source, err, env._dump_on_error);
try {
final Object res = context.eval(global, source, global, "<shell>");
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);

View File

@ -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.UnaryTree;
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 class NashornCompleter implements Completer {
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("<shell>", 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 @@ final class NashornCompleter implements Completer {
}
}
// Internals only below this point
// fill properties of the incomplete member expression
private int completeMemberSelect(final String exprStr, final int cursor, final List<CharSequence> result,
final MemberSelectTree select, final boolean endsWithDot) {
final ExpressionTree objExpr = select.getExpression();
@ -148,6 +262,7 @@ final class NashornCompleter implements Completer {
return cursor;
}
// fill properties for the given (partial) identifer
private int completeIdentifier(final String test, final int cursor, final List<CharSequence> result,
final IdentifierTree ident) {
final String name = ident.getName();
@ -175,6 +290,7 @@ final class NashornCompleter implements Completer {
return null;
}
// get the right most expreesion of the given expression
private Tree getRightMostExpression(final ExpressionTree expr) {
return expr.accept(new SimpleTreeVisitorES5_1<Tree, Void>() {
@Override
@ -234,4 +350,27 @@ final class NashornCompleter implements Completer {
}
}, 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<String> 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]));
}
}

View File

@ -86,7 +86,7 @@ final class PackagesHelper {
// LRU cache for java package properties lists
private final LinkedHashMap<String, List<String>> propsCache =
new LinkedHashMap<>(32, 0.75f, true) {
new LinkedHashMap<String, List<String>>(32, 0.75f, true) {
private static final int CACHE_SIZE = 100;
private static final long serialVersionUID = 1;

View File

@ -29,4 +29,5 @@ shell.usage=jjs [<options>] <files> [-- <arguments>]
shell.prompt=jjs>
shell.prompt2=...>