8133872: Expression completion should work on contexts where an expression is accepted
authorsundar
Wed, 19 Aug 2015 16:35:03 +0530
changeset 32245 80164edf8a10
parent 32244 956c3ae61be6
child 32246 ffea646fc05f
8133872: Expression completion should work on contexts where an expression is accepted Reviewed-by: hannesw, mhaupt
nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java
nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java
nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/PartialParser.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/Shell.java
--- 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