8133872: Expression completion should work on contexts where an expression is accepted
Reviewed-by: hannesw, mhaupt
--- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java Tue Aug 18 09:13:46 2015 -0700
+++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java Wed Aug 19 16:35:03 2015 +0530
@@ -26,6 +26,9 @@
package jdk.nashorn.tools.jjs;
import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
import java.util.function.Function;
import jdk.internal.jline.console.history.FileHistory;
import jdk.internal.jline.console.history.History;
@@ -38,6 +41,16 @@
* A script friendly object that exposes history of commands to scripts.
*/
final class HistoryObject extends AbstractJSObject {
+ private static final Set<String> props;
+ static {
+ final HashSet<String> s = new HashSet<>();
+ s.add("clear");
+ s.add("forEach");
+ s.add("print");
+ s.add("size");
+ props = Collections.unmodifiableSet(s);
+ }
+
private final FileHistory hist;
HistoryObject(final FileHistory hist) {
@@ -72,6 +85,11 @@
return "[object history]";
}
+ @Override
+ public Set<String> keySet() {
+ return props;
+ }
+
private void print() {
for (History.Entry e : hist) {
System.out.println(e.value());
--- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java Tue Aug 18 09:13:46 2015 -0700
+++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java Wed Aug 19 16:35:03 2015 +0530
@@ -85,6 +85,7 @@
return new Main().run(in, out, err, args);
}
+
/**
* read-eval-print loop for Nashorn shell.
*
@@ -98,7 +99,7 @@
final PrintWriter err = context.getErr();
final Global oldGlobal = Context.getGlobal();
final boolean globalChanged = (oldGlobal != global);
- final Completer completer = new NashornCompleter(context, global);
+ final Completer completer = new NashornCompleter(context, global, this);
try (final Console in = new Console(System.in, System.out, HIST_FILE, completer)) {
if (globalChanged) {
--- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java Tue Aug 18 09:13:46 2015 -0700
+++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java Wed Aug 19 16:35:03 2015 +0530
@@ -45,6 +45,7 @@
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.Global;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.ScriptRuntime;
@@ -53,11 +54,13 @@
final class NashornCompleter implements Completer {
private final Context context;
private final Global global;
+ private final PartialParser partialParser;
private final Parser parser;
- NashornCompleter(final Context context, final Global global) {
+ NashornCompleter(final Context context, final Global global, final PartialParser partialParser) {
this.context = context;
this.global = global;
+ this.partialParser = partialParser;
this.parser = Parser.create();
}
@@ -72,14 +75,26 @@
return cursor;
}
- // do we have an incomplete member selection expression that misses property name?
- final boolean endsWithDot = SELECT_PROP_MISSING.matcher(test).matches();
+ // get the start of the last expression embedded in the given code
+ // using the partial parsing support - so that we can complete expressions
+ // inside statements, function call argument lists, array index etc.
+ final int exprStart = partialParser.getLastExpressionStart(context, test);
+ if (exprStart == -1) {
+ return cursor;
+ }
+
- // If this is an incomplete member selection, then it is not legal code
+ // extract the last expression string
+ final String exprStr = test.substring(exprStart);
+
+ // do we have an incomplete member selection expression that misses property name?
+ final boolean endsWithDot = SELECT_PROP_MISSING.matcher(exprStr).matches();
+
+ // If this is an incomplete member selection, then it is not legal code.
// Make it legal by adding a random property name "x" to it.
- final String exprToEval = endsWithDot? test + "x" : test;
+ final String completeExpr = endsWithDot? exprStr + "x" : exprStr;
- final ExpressionTree topExpr = getTopLevelExpression(parser, exprToEval);
+ final ExpressionTree topExpr = getTopLevelExpression(parser, completeExpr);
if (topExpr == null) {
// did not parse to be a top level expression, no suggestions!
return cursor;
@@ -89,19 +104,19 @@
// Find 'right most' expression of the top level expression
final Tree rightMostExpr = getRightMostExpression(topExpr);
if (rightMostExpr instanceof MemberSelectTree) {
- return completeMemberSelect(test, cursor, result, (MemberSelectTree)rightMostExpr, endsWithDot);
+ return completeMemberSelect(exprStr, cursor, result, (MemberSelectTree)rightMostExpr, endsWithDot);
} else if (rightMostExpr instanceof IdentifierTree) {
- return completeIdentifier(test, cursor, result, (IdentifierTree)rightMostExpr);
+ return completeIdentifier(exprStr, cursor, result, (IdentifierTree)rightMostExpr);
} else {
// expression that we cannot handle for completion
return cursor;
}
}
- private int completeMemberSelect(final String test, final int cursor, final List<CharSequence> result,
+ private int completeMemberSelect(final String exprStr, final int cursor, final List<CharSequence> result,
final MemberSelectTree select, final boolean endsWithDot) {
final ExpressionTree objExpr = select.getExpression();
- final String objExprCode = test.substring((int)objExpr.getStartPosition(), (int)objExpr.getEndPosition());
+ final String objExprCode = exprStr.substring((int)objExpr.getStartPosition(), (int)objExpr.getEndPosition());
// try to evaluate the object expression part as a script
Object obj = null;
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java Tue Aug 18 09:13:46 2015 -0700
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java Wed Aug 19 16:35:03 2015 +0530
@@ -3237,6 +3237,7 @@
}
/**
+ * {@code
* MultiplicativeExpression :
* UnaryExpression
* MultiplicativeExpression * UnaryExpression
@@ -3323,11 +3324,15 @@
* Expression , AssignmentExpression
*
* See 11.14
+ * }
*
* Parse expression.
* @return Expression node.
*/
- private Expression expression() {
+ protected Expression expression() {
+ // This method is protected so that subclass can get details
+ // at expression start point!
+
// TODO - Destructuring array.
// Include commas in expression parsing.
return expression(unaryExpression(), COMMARIGHT.getPrecedence(), false);
@@ -3398,7 +3403,10 @@
return lhs;
}
- private Expression assignmentExpression(final boolean noIn) {
+ protected Expression assignmentExpression(final boolean noIn) {
+ // This method is protected so that subclass can get details
+ // at assignment expression start point!
+
// TODO - Handle decompose.
// Exclude commas in expression parsing.
return expression(unaryExpression(), ASSIGN.getPrecedence(), noIn);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/PartialParser.java Wed Aug 19 16:35:03 2015 +0530
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.tools;
+
+import jdk.nashorn.internal.runtime.Context;
+
+/**
+ * Partial parsing support for code completion of expressions.
+ */
+public interface PartialParser {
+ /**
+ * Parse potentially partial code and keep track of the start of last expression.
+ *
+ * @param context the nashorn context
+ * @param code code that is to be parsed
+ * @return the start index of the last expression parsed in the (incomplete) code.
+ */
+ public int getLastExpressionStart(final Context context, final String code);
+}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/Shell.java Tue Aug 18 09:13:46 2015 -0700
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/Shell.java Wed Aug 19 16:35:03 2015 +0530
@@ -43,6 +43,7 @@
import jdk.nashorn.internal.codegen.Compiler;
import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.Expression;
import jdk.nashorn.internal.ir.debug.ASTWriter;
import jdk.nashorn.internal.ir.debug.PrintVisitor;
import jdk.nashorn.internal.objects.Global;
@@ -59,7 +60,7 @@
/**
* Command line Shell for processing JavaScript files.
*/
-public class Shell {
+public class Shell implements PartialParser {
/**
* Resource name for properties file
@@ -397,6 +398,42 @@
}
/**
+ * Parse potentially partial code and keep track of the start of last expression.
+ * This 'partial' parsing support is meant to be used for code-completion.
+ *
+ * @param context the nashorn context
+ * @param code code that is to be parsed
+ * @return the start index of the last expression parsed in the (incomplete) code.
+ */
+ @Override
+ public final int getLastExpressionStart(final Context context, final String code) {
+ final int[] exprStart = { -1 };
+
+ final Parser p = new Parser(context.getEnv(), sourceFor("<partial_code>", code),new Context.ThrowErrorManager()) {
+ @Override
+ protected Expression expression() {
+ exprStart[0] = this.start;
+ return super.expression();
+ }
+
+ @Override
+ protected Expression assignmentExpression(final boolean noIn) {
+ exprStart[0] = this.start;
+ return super.expression();
+ }
+ };
+
+ try {
+ p.parse();
+ } catch (final Exception ignored) {
+ // throw any parser exception, but we are partial parsing anyway
+ }
+
+ return exprStart[0];
+ }
+
+
+ /**
* read-eval-print loop for Nashorn shell.
*
* @param context the nashorn context