8134279: jjs should support multiple line input to complete incomplete code
Reviewed-by: attila, hannesw
This commit is contained in:
parent
373f5906d4
commit
3413347722
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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]));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -29,4 +29,5 @@ shell.usage=jjs [<options>] <files> [-- <arguments>]
|
||||
|
||||
shell.prompt=jjs>
|
||||
|
||||
shell.prompt2=...>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user