From 67e6d1bad04a2fdbc7003511ccdaa0d2b4d790f5 Mon Sep 17 00:00:00 2001 From: Athijegannathan Sundararajan Date: Fri, 14 Aug 2015 18:48:26 +0530 Subject: [PATCH 1/7] 8133624: Move JDK-8055034.js and JDK-8130127.js to currently-failing directory to exclude from test run Reviewed-by: jlaskey, hannesw --- .../test/script/{nosecurity => currently-failing}/JDK-8055034.js | 0 .../{nosecurity => currently-failing}/JDK-8055034.js.EXPECTED | 0 .../test/script/{nosecurity => currently-failing}/JDK-8130127.js | 0 .../{nosecurity => currently-failing}/JDK-8130127.js.EXPECTED | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename nashorn/test/script/{nosecurity => currently-failing}/JDK-8055034.js (100%) rename nashorn/test/script/{nosecurity => currently-failing}/JDK-8055034.js.EXPECTED (100%) rename nashorn/test/script/{nosecurity => currently-failing}/JDK-8130127.js (100%) rename nashorn/test/script/{nosecurity => currently-failing}/JDK-8130127.js.EXPECTED (100%) diff --git a/nashorn/test/script/nosecurity/JDK-8055034.js b/nashorn/test/script/currently-failing/JDK-8055034.js similarity index 100% rename from nashorn/test/script/nosecurity/JDK-8055034.js rename to nashorn/test/script/currently-failing/JDK-8055034.js diff --git a/nashorn/test/script/nosecurity/JDK-8055034.js.EXPECTED b/nashorn/test/script/currently-failing/JDK-8055034.js.EXPECTED similarity index 100% rename from nashorn/test/script/nosecurity/JDK-8055034.js.EXPECTED rename to nashorn/test/script/currently-failing/JDK-8055034.js.EXPECTED diff --git a/nashorn/test/script/nosecurity/JDK-8130127.js b/nashorn/test/script/currently-failing/JDK-8130127.js similarity index 100% rename from nashorn/test/script/nosecurity/JDK-8130127.js rename to nashorn/test/script/currently-failing/JDK-8130127.js diff --git a/nashorn/test/script/nosecurity/JDK-8130127.js.EXPECTED b/nashorn/test/script/currently-failing/JDK-8130127.js.EXPECTED similarity index 100% rename from nashorn/test/script/nosecurity/JDK-8130127.js.EXPECTED rename to nashorn/test/script/currently-failing/JDK-8130127.js.EXPECTED From a45bb1ba66cd3e3b48be6c2aeefba7f71ea42c53 Mon Sep 17 00:00:00 2001 From: Athijegannathan Sundararajan Date: Mon, 17 Aug 2015 13:17:25 +0530 Subject: [PATCH 2/7] 8133652: Implement tab-completion for member select expressions Reviewed-by: jlaskey, attila --- .../jdk/nashorn/tools/jjs/Console.java | 12 +- .../classes/jdk/nashorn/tools/jjs/Main.java | 146 +++++++++++++++++- .../nashorn/tools/jjs/PropertiesHelper.java | 104 +++++++++++++ .../nashorn/internal/objects/NativeJava.java | 44 ++++++ .../internal/runtime/ScriptObject.java | 15 ++ 5 files changed, 316 insertions(+), 5 deletions(-) create mode 100644 nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PropertiesHelper.java diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java index 932eb7ab055..22caba84179 100644 --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java @@ -36,6 +36,7 @@ import java.util.List; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import jdk.internal.jline.console.ConsoleReader; +import jdk.internal.jline.console.completer.Completer; import jdk.internal.jline.console.history.History.Entry; import jdk.internal.jline.console.history.MemoryHistory; @@ -43,15 +44,18 @@ class Console implements AutoCloseable { private final ConsoleReader in; private final PersistentHistory history; - Console(InputStream cmdin, PrintStream cmdout, Preferences prefs) throws IOException { + Console(final InputStream cmdin, final PrintStream cmdout, final Preferences prefs, + final Completer completer) throws IOException { in = new ConsoleReader(cmdin, cmdout); in.setExpandEvents(false); in.setHandleUserInterrupt(true); + in.setBellEnabled(true); in.setHistory(history = new PersistentHistory(prefs)); + in.addCompleter(completer); Runtime.getRuntime().addShutdownHook(new Thread(()->close())); } - String readLine(String prompt) throws IOException { + String readLine(final String prompt) throws IOException { return in.readLine(prompt); } @@ -65,7 +69,7 @@ class Console implements AutoCloseable { private final Preferences prefs; - protected PersistentHistory(Preferences prefs) { + protected PersistentHistory(final Preferences prefs) { this.prefs = prefs; load(); } @@ -74,7 +78,7 @@ class Console implements AutoCloseable { public final void load() { try { - List keys = new ArrayList<>(Arrays.asList(prefs.keys())); + final List keys = new ArrayList<>(Arrays.asList(prefs.keys())); Collections.sort(keys); for (String key : keys) { if (!key.startsWith(HISTORY_LINE_PREFIX)) diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java index 9bc4b56638e..57a58b5de29 100644 --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java @@ -31,14 +31,32 @@ import java.io.InputStreamReader; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; +import java.util.Iterator; +import java.util.List; import java.util.prefs.Preferences; +import jdk.nashorn.api.tree.AssignmentTree; +import jdk.nashorn.api.tree.BinaryTree; +import jdk.nashorn.api.tree.CompilationUnitTree; +import jdk.nashorn.api.tree.CompoundAssignmentTree; +import jdk.nashorn.api.tree.ConditionalExpressionTree; +import jdk.nashorn.api.tree.ExpressionTree; +import jdk.nashorn.api.tree.ExpressionStatementTree; +import jdk.nashorn.api.tree.InstanceOfTree; +import jdk.nashorn.api.tree.MemberSelectTree; +import jdk.nashorn.api.tree.SimpleTreeVisitorES5_1; +import jdk.nashorn.api.tree.Tree; +import jdk.nashorn.api.tree.UnaryTree; +import jdk.nashorn.api.tree.Parser; +import jdk.nashorn.api.scripting.NashornException; import jdk.nashorn.internal.objects.Global; import jdk.nashorn.internal.runtime.Context; import jdk.nashorn.internal.runtime.ErrorManager; import jdk.nashorn.internal.runtime.JSType; import jdk.nashorn.internal.runtime.ScriptEnvironment; +import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.tools.Shell; +import jdk.internal.jline.console.completer.Completer; import jdk.internal.jline.console.UserInterruptException; /** @@ -96,8 +114,72 @@ public final class Main extends Shell { final PrintWriter err = context.getErr(); final Global oldGlobal = Context.getGlobal(); final boolean globalChanged = (oldGlobal != global); + final Parser parser = Parser.create(); - try (final Console in = new Console(System.in, System.out, PREFS)) { + // simple source "tab completer" for nashorn + final Completer completer = new Completer() { + @Override + public int complete(final String test, final int cursor, final List result) { + // check that cursor is at the end of test string. Do not complete in the middle! + if (cursor != test.length()) { + return cursor; + } + + // if it has a ".", then assume it is a member selection expression + final int idx = test.lastIndexOf('.'); + if (idx == -1) { + return cursor; + } + + // stuff before the last "." + final String exprBeforeDot = test.substring(0, idx); + + // Make sure that completed code will have a member expression! Adding ".x" as a + // random property/field name selected to make it possible to be a proper member select + final ExpressionTree topExpr = getTopLevelExpression(parser, exprBeforeDot + ".x"); + if (topExpr == null) { + // did not parse to be a top level expression, no suggestions! + return cursor; + } + + + // Find 'right most' member select expression's start position + final int startPosition = (int) getStartOfMemberSelect(topExpr); + if (startPosition == -1) { + // not a member expression that we can handle for completion + return cursor; + } + + // The part of the right most member select expression before the "." + final String objExpr = test.substring(startPosition, idx); + + // try to evaluate the object expression part as a script + Object obj = null; + try { + obj = context.eval(global, objExpr, global, ""); + } catch (Exception ignored) { + // throw the exception - this is during tab-completion + } + + if (obj != null && obj != ScriptRuntime.UNDEFINED) { + // where is the last dot? Is there a partial property name specified? + final String prefix = test.substring(idx + 1); + if (prefix.isEmpty()) { + // no user specified "prefix". List all properties of the object + result.addAll(PropertiesHelper.getProperties(obj)); + return cursor; + } else { + // list of properties matching the user specified prefix + result.addAll(PropertiesHelper.getProperties(obj, prefix)); + return idx + 1; + } + } + + return cursor; + } + }; + + try (final Console in = new Console(System.in, System.out, PREFS, completer)) { if (globalChanged) { Context.setGlobal(global); } @@ -147,4 +229,66 @@ public final class Main extends Shell { return SUCCESS; } + + // returns ExpressionTree if the given code parses to a top level expression. + // Or else returns null. + private ExpressionTree getTopLevelExpression(final Parser parser, final String code) { + try { + final CompilationUnitTree cut = parser.parse("", code, null); + final List stats = cut.getSourceElements(); + if (stats.size() == 1) { + final Tree stat = stats.get(0); + if (stat instanceof ExpressionStatementTree) { + return ((ExpressionStatementTree)stat).getExpression(); + } + } + } catch (final NashornException ignored) { + // ignore any parser error. This is for completion anyway! + // And user will get that error later when the expression is evaluated. + } + + return null; + } + + + private long getStartOfMemberSelect(final ExpressionTree expr) { + if (expr instanceof MemberSelectTree) { + return ((MemberSelectTree)expr).getStartPosition(); + } + + final Tree rightMostExpr = expr.accept(new SimpleTreeVisitorES5_1() { + @Override + public Tree visitAssignment(final AssignmentTree at, final Void v) { + return at.getExpression(); + } + + @Override + public Tree visitCompoundAssignment(final CompoundAssignmentTree cat, final Void v) { + return cat.getExpression(); + } + + @Override + public Tree visitConditionalExpression(final ConditionalExpressionTree cet, final Void v) { + return cet.getFalseExpression(); + } + + @Override + public Tree visitBinary(final BinaryTree bt, final Void v) { + return bt.getRightOperand(); + } + + @Override + public Tree visitInstanceOf(final InstanceOfTree it, final Void v) { + return it.getType(); + } + + @Override + public Tree visitUnary(final UnaryTree ut, final Void v) { + return ut.getExpression(); + } + }, null); + + return (rightMostExpr instanceof MemberSelectTree)? + rightMostExpr.getStartPosition() : -1L; + } } diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PropertiesHelper.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PropertiesHelper.java new file mode 100644 index 00000000000..aada9223bf9 --- /dev/null +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PropertiesHelper.java @@ -0,0 +1,104 @@ +/* + * 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.jjs; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.WeakHashMap; +import java.util.stream.Collectors; +import jdk.nashorn.internal.runtime.JSType; +import jdk.nashorn.internal.runtime.PropertyMap; +import jdk.nashorn.internal.runtime.ScriptObject; +import jdk.nashorn.internal.runtime.ScriptRuntime; +import jdk.nashorn.internal.objects.NativeJava; + +/* + * A helper class to get properties of a given object for source code completion. + */ +final class PropertiesHelper { + private PropertiesHelper() {} + + // cached properties list + private static final WeakHashMap> propsCache = new WeakHashMap<>(); + + // returns the list of properties of the given object + static List getProperties(final Object obj) { + assert obj != null && obj != ScriptRuntime.UNDEFINED; + + if (JSType.isPrimitive(obj)) { + return getProperties(JSType.toScriptObject(obj)); + } + + if (obj instanceof ScriptObject) { + final ScriptObject sobj = (ScriptObject)obj; + final PropertyMap pmap = sobj.getMap(); + if (propsCache.containsKey(pmap)) { + return propsCache.get(pmap); + } + final String[] keys = sobj.getAllKeys(); + List props = Arrays.asList(keys); + props = props.stream() + .filter(s -> Character.isJavaIdentifierStart(s.charAt(0))) + .collect(Collectors.toList()); + Collections.sort(props); + // cache properties against the PropertyMap + propsCache.put(pmap, props); + return props; + } + + if (NativeJava.isType(ScriptRuntime.UNDEFINED, obj)) { + if (propsCache.containsKey(obj)) { + return propsCache.get(obj); + } + final List props = NativeJava.getProperties(obj); + Collections.sort(props); + // cache properties against the StaticClass representing the class + propsCache.put(obj, props); + return props; + } + + final Class clazz = obj.getClass(); + if (propsCache.containsKey(clazz)) { + return propsCache.get(clazz); + } + + final List props = NativeJava.getProperties(obj); + Collections.sort(props); + // cache properties against the Class object + propsCache.put(clazz, props); + return props; + } + + // returns the list of properties of the given object that start with the given prefix + static List getProperties(final Object obj, final String prefix) { + assert prefix != null && !prefix.isEmpty(); + return getProperties(obj).stream() + .filter(s -> s.startsWith(prefix)) + .collect(Collectors.toList()); + } +} diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/NativeJava.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/NativeJava.java index 743f055cfab..9d08a9bd4ca 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/NativeJava.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/NativeJava.java @@ -30,11 +30,14 @@ import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; import java.lang.invoke.MethodHandles; import java.lang.reflect.Array; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Deque; import java.util.List; import java.util.Map; import java.util.Queue; +import jdk.internal.dynalink.beans.BeansLinker; import jdk.internal.dynalink.beans.StaticClass; import jdk.internal.dynalink.support.TypeUtilities; import jdk.nashorn.api.scripting.JSObject; @@ -443,6 +446,47 @@ public final class NativeJava { throw typeError("cant.convert.to.javascript.array", objArray.getClass().getName()); } + /** + * Return properties of the given object. Properties also include "method names". + * This is meant for source code completion in interactive shells or editors. + * + * @param object the object whose properties are returned. + * @return list of properties + */ + public static List getProperties(final Object object) { + if (object instanceof StaticClass) { + // static properties of the given class + final Class clazz = ((StaticClass)object).getRepresentedClass(); + final ArrayList props = new ArrayList<>(); + try { + Bootstrap.checkReflectionAccess(clazz, true); + // Usually writable properties are a subset as 'write-only' properties are rare + props.addAll(BeansLinker.getReadableStaticPropertyNames(clazz)); + props.addAll(BeansLinker.getStaticMethodNames(clazz)); + } catch (Exception ignored) {} + return props; + } else if (object instanceof JSObject) { + final JSObject jsObj = ((JSObject)object); + final ArrayList props = new ArrayList<>(); + props.addAll(jsObj.keySet()); + return props; + } else if (object != null && object != UNDEFINED) { + // instance properties of the given object + final Class clazz = object.getClass(); + final ArrayList props = new ArrayList<>(); + try { + Bootstrap.checkReflectionAccess(clazz, false); + // Usually writable properties are a subset as 'write-only' properties are rare + props.addAll(BeansLinker.getReadableInstancePropertyNames(clazz)); + props.addAll(BeansLinker.getInstanceMethodNames(clazz)); + } catch (Exception ignored) {} + return props; + } + + // don't know about that object + return Collections.emptyList(); + } + private static int[] copyArray(final byte[] in) { final int[] out = new int[in.length]; for(int i = 0; i < in.length; ++i) { diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java index 946604ffce2..69b84167f83 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java @@ -1339,6 +1339,21 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable { } } + /** + * return an array of all property keys - all inherited, non-enumerable included. + * This is meant for source code completion by interactive shells or editors. + * + * @return Array of keys, order of properties is undefined. + */ + public String[] getAllKeys() { + final Set keys = new HashSet<>(); + final Set nonEnumerable = new HashSet<>(); + for (ScriptObject self = this; self != null; self = self.getProto()) { + keys.addAll(Arrays.asList(self.getOwnKeys(true, nonEnumerable))); + } + return keys.toArray(new String[keys.size()]); + } + /** * return an array of own property keys associated with the object. * From a8ae0d78d864940122f3b1cb66e6f2e37e9e426e Mon Sep 17 00:00:00 2001 From: Athijegannathan Sundararajan Date: Mon, 17 Aug 2015 18:36:28 +0530 Subject: [PATCH 3/7] 8133695: Implement tab-completion for identifiers Reviewed-by: attila, jlaskey --- .../classes/jdk/nashorn/tools/jjs/Main.java | 148 +------------ .../nashorn/tools/jjs/NashornCompleter.java | 207 ++++++++++++++++++ 2 files changed, 210 insertions(+), 145 deletions(-) create mode 100644 nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java index 57a58b5de29..43a2fd517a4 100644 --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java @@ -31,33 +31,16 @@ import java.io.InputStreamReader; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; -import java.util.Iterator; -import java.util.List; import java.util.prefs.Preferences; -import jdk.nashorn.api.tree.AssignmentTree; -import jdk.nashorn.api.tree.BinaryTree; -import jdk.nashorn.api.tree.CompilationUnitTree; -import jdk.nashorn.api.tree.CompoundAssignmentTree; -import jdk.nashorn.api.tree.ConditionalExpressionTree; -import jdk.nashorn.api.tree.ExpressionTree; -import jdk.nashorn.api.tree.ExpressionStatementTree; -import jdk.nashorn.api.tree.InstanceOfTree; -import jdk.nashorn.api.tree.MemberSelectTree; -import jdk.nashorn.api.tree.SimpleTreeVisitorES5_1; -import jdk.nashorn.api.tree.Tree; -import jdk.nashorn.api.tree.UnaryTree; -import jdk.nashorn.api.tree.Parser; +import jdk.internal.jline.console.completer.Completer; +import jdk.internal.jline.console.UserInterruptException; import jdk.nashorn.api.scripting.NashornException; import jdk.nashorn.internal.objects.Global; import jdk.nashorn.internal.runtime.Context; -import jdk.nashorn.internal.runtime.ErrorManager; import jdk.nashorn.internal.runtime.JSType; import jdk.nashorn.internal.runtime.ScriptEnvironment; -import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.tools.Shell; -import jdk.internal.jline.console.completer.Completer; -import jdk.internal.jline.console.UserInterruptException; /** * Interactive command line Shell for Nashorn. @@ -114,70 +97,7 @@ public final class Main extends Shell { final PrintWriter err = context.getErr(); final Global oldGlobal = Context.getGlobal(); final boolean globalChanged = (oldGlobal != global); - final Parser parser = Parser.create(); - - // simple source "tab completer" for nashorn - final Completer completer = new Completer() { - @Override - public int complete(final String test, final int cursor, final List result) { - // check that cursor is at the end of test string. Do not complete in the middle! - if (cursor != test.length()) { - return cursor; - } - - // if it has a ".", then assume it is a member selection expression - final int idx = test.lastIndexOf('.'); - if (idx == -1) { - return cursor; - } - - // stuff before the last "." - final String exprBeforeDot = test.substring(0, idx); - - // Make sure that completed code will have a member expression! Adding ".x" as a - // random property/field name selected to make it possible to be a proper member select - final ExpressionTree topExpr = getTopLevelExpression(parser, exprBeforeDot + ".x"); - if (topExpr == null) { - // did not parse to be a top level expression, no suggestions! - return cursor; - } - - - // Find 'right most' member select expression's start position - final int startPosition = (int) getStartOfMemberSelect(topExpr); - if (startPosition == -1) { - // not a member expression that we can handle for completion - return cursor; - } - - // The part of the right most member select expression before the "." - final String objExpr = test.substring(startPosition, idx); - - // try to evaluate the object expression part as a script - Object obj = null; - try { - obj = context.eval(global, objExpr, global, ""); - } catch (Exception ignored) { - // throw the exception - this is during tab-completion - } - - if (obj != null && obj != ScriptRuntime.UNDEFINED) { - // where is the last dot? Is there a partial property name specified? - final String prefix = test.substring(idx + 1); - if (prefix.isEmpty()) { - // no user specified "prefix". List all properties of the object - result.addAll(PropertiesHelper.getProperties(obj)); - return cursor; - } else { - // list of properties matching the user specified prefix - result.addAll(PropertiesHelper.getProperties(obj, prefix)); - return idx + 1; - } - } - - return cursor; - } - }; + final Completer completer = new NashornCompleter(context, global); try (final Console in = new Console(System.in, System.out, PREFS, completer)) { if (globalChanged) { @@ -229,66 +149,4 @@ public final class Main extends Shell { return SUCCESS; } - - // returns ExpressionTree if the given code parses to a top level expression. - // Or else returns null. - private ExpressionTree getTopLevelExpression(final Parser parser, final String code) { - try { - final CompilationUnitTree cut = parser.parse("", code, null); - final List stats = cut.getSourceElements(); - if (stats.size() == 1) { - final Tree stat = stats.get(0); - if (stat instanceof ExpressionStatementTree) { - return ((ExpressionStatementTree)stat).getExpression(); - } - } - } catch (final NashornException ignored) { - // ignore any parser error. This is for completion anyway! - // And user will get that error later when the expression is evaluated. - } - - return null; - } - - - private long getStartOfMemberSelect(final ExpressionTree expr) { - if (expr instanceof MemberSelectTree) { - return ((MemberSelectTree)expr).getStartPosition(); - } - - final Tree rightMostExpr = expr.accept(new SimpleTreeVisitorES5_1() { - @Override - public Tree visitAssignment(final AssignmentTree at, final Void v) { - return at.getExpression(); - } - - @Override - public Tree visitCompoundAssignment(final CompoundAssignmentTree cat, final Void v) { - return cat.getExpression(); - } - - @Override - public Tree visitConditionalExpression(final ConditionalExpressionTree cet, final Void v) { - return cet.getFalseExpression(); - } - - @Override - public Tree visitBinary(final BinaryTree bt, final Void v) { - return bt.getRightOperand(); - } - - @Override - public Tree visitInstanceOf(final InstanceOfTree it, final Void v) { - return it.getType(); - } - - @Override - public Tree visitUnary(final UnaryTree ut, final Void v) { - return ut.getExpression(); - } - }, null); - - return (rightMostExpr instanceof MemberSelectTree)? - rightMostExpr.getStartPosition() : -1L; - } } diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java new file mode 100644 index 00000000000..c3cb5927f7b --- /dev/null +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java @@ -0,0 +1,207 @@ +/* + * 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.jjs; + +import java.util.List; +import jdk.internal.jline.console.completer.Completer; +import jdk.nashorn.api.tree.AssignmentTree; +import jdk.nashorn.api.tree.BinaryTree; +import jdk.nashorn.api.tree.CompilationUnitTree; +import jdk.nashorn.api.tree.CompoundAssignmentTree; +import jdk.nashorn.api.tree.ConditionalExpressionTree; +import jdk.nashorn.api.tree.ExpressionTree; +import jdk.nashorn.api.tree.ExpressionStatementTree; +import jdk.nashorn.api.tree.InstanceOfTree; +import jdk.nashorn.api.tree.MemberSelectTree; +import jdk.nashorn.api.tree.SimpleTreeVisitorES5_1; +import jdk.nashorn.api.tree.Tree; +import jdk.nashorn.api.tree.UnaryTree; +import jdk.nashorn.api.tree.Parser; +import jdk.nashorn.api.scripting.NashornException; +import jdk.nashorn.internal.objects.Global; +import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.ScriptRuntime; + +// A simple source completer for nashorn +final class NashornCompleter implements Completer { + private final Context context; + private final Global global; + private final Parser parser; + + NashornCompleter(final Context context, final Global global) { + this.context = context; + this.global = global; + this.parser = Parser.create(); + } + + @Override + public int complete(final String test, final int cursor, final List result) { + // check that cursor is at the end of test string. Do not complete in the middle! + if (cursor != test.length()) { + return cursor; + } + + // if it has a ".", then assume it is a member selection expression + final int idx = test.lastIndexOf('.'); + if (idx == -1) { + if (isIdentifier(test)) { + // identifier - return matching global variable names, if any + result.addAll(PropertiesHelper.getProperties(global, test)); + return idx + 1; + } + + return cursor; + } + + // stuff before the last "." + final String exprBeforeDot = test.substring(0, idx); + + // Make sure that completed code will have a member expression! Adding ".x" as a + // random property/field name selected to make it possible to be a proper member select + final ExpressionTree topExpr = getTopLevelExpression(parser, exprBeforeDot + ".x"); + if (topExpr == null) { + // did not parse to be a top level expression, no suggestions! + return cursor; + } + + + // Find 'right most' member select expression's start position + final int startPosition = (int) getStartOfMemberSelect(topExpr); + if (startPosition == -1) { + // not a member expression that we can handle for completion + return cursor; + } + + // The part of the right most member select expression before the "." + final String objExpr = test.substring(startPosition, idx); + + // try to evaluate the object expression part as a script + Object obj = null; + try { + obj = context.eval(global, objExpr, global, ""); + } catch (Exception ignored) { + // throw the exception - this is during tab-completion + } + + if (obj != null && obj != ScriptRuntime.UNDEFINED) { + // where is the last dot? Is there a partial property name specified? + final String prefix = test.substring(idx + 1); + if (prefix.isEmpty()) { + // no user specified "prefix". List all properties of the object + result.addAll(PropertiesHelper.getProperties(obj)); + return cursor; + } else { + // list of properties matching the user specified prefix + result.addAll(PropertiesHelper.getProperties(obj, prefix)); + return idx + 1; + } + } + + return cursor; + } + + // returns ExpressionTree if the given code parses to a top level expression. + // Or else returns null. + private ExpressionTree getTopLevelExpression(final Parser parser, final String code) { + try { + final CompilationUnitTree cut = parser.parse("", code, null); + final List stats = cut.getSourceElements(); + if (stats.size() == 1) { + final Tree stat = stats.get(0); + if (stat instanceof ExpressionStatementTree) { + return ((ExpressionStatementTree)stat).getExpression(); + } + } + } catch (final NashornException ignored) { + // ignore any parser error. This is for completion anyway! + // And user will get that error later when the expression is evaluated. + } + + return null; + } + + + private long getStartOfMemberSelect(final ExpressionTree expr) { + if (expr instanceof MemberSelectTree) { + return ((MemberSelectTree)expr).getStartPosition(); + } + + final Tree rightMostExpr = expr.accept(new SimpleTreeVisitorES5_1() { + @Override + public Tree visitAssignment(final AssignmentTree at, final Void v) { + return at.getExpression(); + } + + @Override + public Tree visitCompoundAssignment(final CompoundAssignmentTree cat, final Void v) { + return cat.getExpression(); + } + + @Override + public Tree visitConditionalExpression(final ConditionalExpressionTree cet, final Void v) { + return cet.getFalseExpression(); + } + + @Override + public Tree visitBinary(final BinaryTree bt, final Void v) { + return bt.getRightOperand(); + } + + @Override + public Tree visitInstanceOf(final InstanceOfTree it, final Void v) { + return it.getType(); + } + + @Override + public Tree visitUnary(final UnaryTree ut, final Void v) { + return ut.getExpression(); + } + }, null); + + return (rightMostExpr instanceof MemberSelectTree)? + rightMostExpr.getStartPosition() : -1L; + } + + // return if the given String is a valid identifier name or not + private boolean isIdentifier(final String test) { + if (test.isEmpty()) { + return false; + } + + final char[] buf = test.toCharArray(); + if (! Character.isJavaIdentifierStart(buf[0])) { + return false; + } + + for (int idx = 1; idx < buf.length; idx++) { + if (! Character.isJavaIdentifierPart(buf[idx])) { + return false; + } + } + + return true; + } +} From 65c7459351728da1ec9b56c18cb7a5556ad2b919 Mon Sep 17 00:00:00 2001 From: Athijegannathan Sundararajan Date: Tue, 18 Aug 2015 11:40:18 +0530 Subject: [PATCH 4/7] 8133777: Use file based persistence for history instead of preferences Reviewed-by: attila, mhaupt --- .../jdk/nashorn/tools/jjs/Console.java | 61 +++---------- .../jdk/nashorn/tools/jjs/HistoryObject.java | 89 +++++++++++++++++++ .../classes/jdk/nashorn/tools/jjs/Main.java | 9 +- 3 files changed, 109 insertions(+), 50 deletions(-) create mode 100644 nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java index 22caba84179..3ecee8c7d11 100644 --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java @@ -25,6 +25,7 @@ package jdk.nashorn.tools.jjs; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; @@ -33,75 +34,41 @@ import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.prefs.BackingStoreException; -import java.util.prefs.Preferences; import jdk.internal.jline.console.ConsoleReader; import jdk.internal.jline.console.completer.Completer; -import jdk.internal.jline.console.history.History.Entry; -import jdk.internal.jline.console.history.MemoryHistory; +import jdk.internal.jline.console.history.FileHistory; class Console implements AutoCloseable { private final ConsoleReader in; - private final PersistentHistory history; + private final FileHistory history; - Console(final InputStream cmdin, final PrintStream cmdout, final Preferences prefs, + Console(final InputStream cmdin, final PrintStream cmdout, final File historyFile, final Completer completer) throws IOException { in = new ConsoleReader(cmdin, cmdout); in.setExpandEvents(false); in.setHandleUserInterrupt(true); in.setBellEnabled(true); - in.setHistory(history = new PersistentHistory(prefs)); + in.setHistory(history = new FileHistory(historyFile)); in.addCompleter(completer); - Runtime.getRuntime().addShutdownHook(new Thread(()->close())); + Runtime.getRuntime().addShutdownHook(new Thread((Runnable)this::saveHistory)); } String readLine(final String prompt) throws IOException { return in.readLine(prompt); } - @Override public void close() { - history.save(); + saveHistory(); } - public static class PersistentHistory extends MemoryHistory { - - private final Preferences prefs; - - protected PersistentHistory(final Preferences prefs) { - this.prefs = prefs; - load(); - } - - private static final String HISTORY_LINE_PREFIX = "HISTORY_LINE_"; - - public final void load() { - try { - final List keys = new ArrayList<>(Arrays.asList(prefs.keys())); - Collections.sort(keys); - for (String key : keys) { - if (!key.startsWith(HISTORY_LINE_PREFIX)) - continue; - CharSequence line = prefs.get(key, ""); - add(line); - } - } catch (BackingStoreException ex) { - throw new IllegalStateException(ex); - } - } - - public void save() { - Iterator entries = iterator(); - if (entries.hasNext()) { - int len = (int) Math.ceil(Math.log10(size()+1)); - String format = HISTORY_LINE_PREFIX + "%0" + len + "d"; - while (entries.hasNext()) { - Entry entry = entries.next(); - prefs.put(String.format(format, entry.index()), entry.value().toString()); - } - } - } + private void saveHistory() { + try { + getHistory().flush(); + } catch (final IOException exp) {} + } + FileHistory getHistory() { + return (FileHistory) in.getHistory(); } } diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java new file mode 100644 index 00000000000..162ba4e0b23 --- /dev/null +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java @@ -0,0 +1,89 @@ +/* + * 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.jjs; + +import java.io.IOException; +import java.util.function.Function; +import jdk.internal.jline.console.history.FileHistory; +import jdk.internal.jline.console.history.History; +import jdk.nashorn.api.scripting.AbstractJSObject; +import jdk.nashorn.api.scripting.JSObject; +import jdk.nashorn.internal.runtime.JSType; +import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; + +/* + * A script friendly object that exposes history of commands to scripts. + */ +final class HistoryObject extends AbstractJSObject { + private final FileHistory hist; + + HistoryObject(final FileHistory hist) { + this.hist = hist; + } + + @Override + public Object getMember(final String name) { + switch (name) { + case "clear": + return (Runnable)hist::clear; + case "forEach": + return (Function)this::iterate; + case "print": + return (Runnable)this::print; + case "size": + return hist.size(); + } + return UNDEFINED; + } + + @Override + public Object getDefaultValue(final Class hint) { + if (hint == String.class) { + return toString(); + } + return UNDEFINED; + } + + @Override + public String toString() { + return "[object history]"; + } + + private void print() { + for (History.Entry e : hist) { + System.out.println(e.value()); + } + } + + private Object iterate(final JSObject func) { + for (History.Entry e : hist) { + if (JSType.toBoolean(func.call(this, e.value().toString()))) { + break; // return true from callback to skip iteration + } + } + return UNDEFINED; + } +} diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java index 43a2fd517a4..d7b4892790d 100644 --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java @@ -26,12 +26,12 @@ package jdk.nashorn.tools.jjs; import java.io.BufferedReader; +import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; -import java.util.prefs.Preferences; import jdk.internal.jline.console.completer.Completer; import jdk.internal.jline.console.UserInterruptException; import jdk.nashorn.api.scripting.NashornException; @@ -48,7 +48,8 @@ import jdk.nashorn.tools.Shell; public final class Main extends Shell { private Main() {} - static final Preferences PREFS = Preferences.userRoot().node("tool/jjs"); + // file where history is persisted. + private static final File HIST_FILE = new File(new File(System.getProperty("user.home")), ".jjs.history"); /** * Main entry point with the default input, output and error streams. @@ -99,12 +100,14 @@ public final class Main extends Shell { final boolean globalChanged = (oldGlobal != global); final Completer completer = new NashornCompleter(context, global); - try (final Console in = new Console(System.in, System.out, PREFS, completer)) { + try (final Console in = new Console(System.in, System.out, HIST_FILE, completer)) { if (globalChanged) { Context.setGlobal(global); } global.addShellBuiltins(); + // expose history object for reflecting on command line history + global.put("history", new HistoryObject(in.getHistory()), false); while (true) { String source = ""; From 19a69d8cbfb7a22b4b14b99fc4d4da285642147b Mon Sep 17 00:00:00 2001 From: Athijegannathan Sundararajan Date: Tue, 18 Aug 2015 18:53:13 +0530 Subject: [PATCH 5/7] 8133812: identifier and member expression completion handling is not uniform Reviewed-by: attila, hannesw --- .../nashorn/tools/jjs/NashornCompleter.java | 133 ++++++++++-------- 1 file changed, 71 insertions(+), 62 deletions(-) diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java index c3cb5927f7b..bc5dee274c5 100644 --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java @@ -26,6 +26,7 @@ package jdk.nashorn.tools.jjs; import java.util.List; +import java.util.regex.Pattern; import jdk.internal.jline.console.completer.Completer; import jdk.nashorn.api.tree.AssignmentTree; import jdk.nashorn.api.tree.BinaryTree; @@ -34,8 +35,11 @@ import jdk.nashorn.api.tree.CompoundAssignmentTree; import jdk.nashorn.api.tree.ConditionalExpressionTree; import jdk.nashorn.api.tree.ExpressionTree; import jdk.nashorn.api.tree.ExpressionStatementTree; +import jdk.nashorn.api.tree.FunctionCallTree; +import jdk.nashorn.api.tree.IdentifierTree; import jdk.nashorn.api.tree.InstanceOfTree; import jdk.nashorn.api.tree.MemberSelectTree; +import jdk.nashorn.api.tree.NewTree; import jdk.nashorn.api.tree.SimpleTreeVisitorES5_1; import jdk.nashorn.api.tree.Tree; import jdk.nashorn.api.tree.UnaryTree; @@ -57,6 +61,10 @@ final class NashornCompleter implements Completer { this.parser = Parser.create(); } + // Pattern to match a unfinished member selection expression. object part and "." + // but property name missing pattern. + private static final Pattern SELECT_PROP_MISSING = Pattern.compile(".*\\.\\s*"); + @Override public int complete(final String test, final int cursor, final List result) { // check that cursor is at the end of test string. Do not complete in the middle! @@ -64,65 +72,68 @@ final class NashornCompleter implements Completer { return cursor; } - // if it has a ".", then assume it is a member selection expression - final int idx = test.lastIndexOf('.'); - if (idx == -1) { - if (isIdentifier(test)) { - // identifier - return matching global variable names, if any - result.addAll(PropertiesHelper.getProperties(global, test)); - return idx + 1; - } + // do we have an incomplete member selection expression that misses property name? + final boolean endsWithDot = SELECT_PROP_MISSING.matcher(test).matches(); - return cursor; - } + // 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; - // stuff before the last "." - final String exprBeforeDot = test.substring(0, idx); - - // Make sure that completed code will have a member expression! Adding ".x" as a - // random property/field name selected to make it possible to be a proper member select - final ExpressionTree topExpr = getTopLevelExpression(parser, exprBeforeDot + ".x"); + final ExpressionTree topExpr = getTopLevelExpression(parser, exprToEval); if (topExpr == null) { // did not parse to be a top level expression, no suggestions! return cursor; } - // Find 'right most' member select expression's start position - final int startPosition = (int) getStartOfMemberSelect(topExpr); - if (startPosition == -1) { - // not a member expression that we can handle for completion + // 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); + } else if (rightMostExpr instanceof IdentifierTree) { + return completeIdentifier(test, cursor, result, (IdentifierTree)rightMostExpr); + } else { + // expression that we cannot handle for completion return cursor; } + } - // The part of the right most member select expression before the "." - final String objExpr = test.substring(startPosition, idx); + private int completeMemberSelect(final String test, final int cursor, final List result, + final MemberSelectTree select, final boolean endsWithDot) { + final ExpressionTree objExpr = select.getExpression(); + final String objExprCode = test.substring((int)objExpr.getStartPosition(), (int)objExpr.getEndPosition()); // try to evaluate the object expression part as a script Object obj = null; try { - obj = context.eval(global, objExpr, global, ""); + obj = context.eval(global, objExprCode, global, ""); } catch (Exception ignored) { // throw the exception - this is during tab-completion } if (obj != null && obj != ScriptRuntime.UNDEFINED) { - // where is the last dot? Is there a partial property name specified? - final String prefix = test.substring(idx + 1); - if (prefix.isEmpty()) { + if (endsWithDot) { // no user specified "prefix". List all properties of the object result.addAll(PropertiesHelper.getProperties(obj)); return cursor; } else { // list of properties matching the user specified prefix + final String prefix = select.getIdentifier(); result.addAll(PropertiesHelper.getProperties(obj, prefix)); - return idx + 1; + return cursor - prefix.length(); } } return cursor; } + private int completeIdentifier(final String test, final int cursor, final List result, + final IdentifierTree ident) { + final String name = ident.getName(); + result.addAll(PropertiesHelper.getProperties(global, name)); + return cursor - name.length(); + } + // returns ExpressionTree if the given code parses to a top level expression. // Or else returns null. private ExpressionTree getTopLevelExpression(final Parser parser, final String code) { @@ -143,65 +154,63 @@ final class NashornCompleter implements Completer { return null; } - - private long getStartOfMemberSelect(final ExpressionTree expr) { - if (expr instanceof MemberSelectTree) { - return ((MemberSelectTree)expr).getStartPosition(); - } - - final Tree rightMostExpr = expr.accept(new SimpleTreeVisitorES5_1() { + private Tree getRightMostExpression(final ExpressionTree expr) { + return expr.accept(new SimpleTreeVisitorES5_1() { @Override public Tree visitAssignment(final AssignmentTree at, final Void v) { - return at.getExpression(); + return getRightMostExpression(at.getExpression()); } @Override public Tree visitCompoundAssignment(final CompoundAssignmentTree cat, final Void v) { - return cat.getExpression(); + return getRightMostExpression(cat.getExpression()); } @Override public Tree visitConditionalExpression(final ConditionalExpressionTree cet, final Void v) { - return cet.getFalseExpression(); + return getRightMostExpression(cet.getFalseExpression()); } @Override public Tree visitBinary(final BinaryTree bt, final Void v) { - return bt.getRightOperand(); + return getRightMostExpression(bt.getRightOperand()); } + @Override + public Tree visitIdentifier(final IdentifierTree ident, final Void v) { + return ident; + } + + @Override public Tree visitInstanceOf(final InstanceOfTree it, final Void v) { return it.getType(); } + + @Override + public Tree visitMemberSelect(final MemberSelectTree select, final Void v) { + return select; + } + + @Override + public Tree visitNew(final NewTree nt, final Void v) { + final ExpressionTree call = nt.getConstructorExpression(); + if (call instanceof FunctionCallTree) { + final ExpressionTree func = ((FunctionCallTree)call).getFunctionSelect(); + // Is this "new Foo" or "new obj.Foo" with no user arguments? + // If so, we may be able to do completion of constructor name. + if (func.getEndPosition() == nt.getEndPosition()) { + return func; + } + } + return null; + } + @Override public Tree visitUnary(final UnaryTree ut, final Void v) { - return ut.getExpression(); + return getRightMostExpression(ut.getExpression()); } }, null); - - return (rightMostExpr instanceof MemberSelectTree)? - rightMostExpr.getStartPosition() : -1L; - } - - // return if the given String is a valid identifier name or not - private boolean isIdentifier(final String test) { - if (test.isEmpty()) { - return false; - } - - final char[] buf = test.toCharArray(); - if (! Character.isJavaIdentifierStart(buf[0])) { - return false; - } - - for (int idx = 1; idx < buf.length; idx++) { - if (! Character.isJavaIdentifierPart(buf[idx])) { - return false; - } - } - - return true; } } From 5899d6fbda602b8625a090e7abc271ae5ee3fad2 Mon Sep 17 00:00:00 2001 From: Michael Haupt Date: Tue, 18 Aug 2015 09:13:46 -0700 Subject: [PATCH 6/7] 8077168: CodeStoreAndPathTest.java fails in jtreg mode on Mac Use correct path on JRT file system Reviewed-by: attila, sundar --- .../internal/codegen/OptimisticTypesPersistence.java | 8 +++++--- .../internal/runtime/test/CodeStoreAndPathTest.java | 7 ++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java index 86339d81fd0..dfaecb2411b 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesPersistence.java @@ -398,7 +398,7 @@ public final class OptimisticTypesPersistence { } else if(protocol.equals("jrt")) { return getJrtVersionDirName(); } else { - throw new AssertionError(); + throw new AssertionError("unknown protocol"); } } @@ -556,13 +556,15 @@ public final class OptimisticTypesPersistence { return Math.max(0, Integer.parseInt(str)); } + private static final String JRT_NASHORN_DIR = "/modules/jdk.scripting.nashorn"; + // version directory name if nashorn is loaded from jrt:/ URL private static String getJrtVersionDirName() throws Exception { final FileSystem fs = getJrtFileSystem(); // consider all .class resources under nashorn module to compute checksum - final Path nashorn = fs.getPath("/jdk.scripting.nashorn"); + final Path nashorn = fs.getPath(JRT_NASHORN_DIR); if (! Files.isDirectory(nashorn)) { - throw new FileNotFoundException("missing /jdk.scripting.nashorn dir in jrt fs"); + throw new FileNotFoundException("missing " + JRT_NASHORN_DIR + " dir in jrt fs"); } final MessageDigest digest = MessageDigest.getInstance("SHA-1"); Files.walk(nashorn).forEach(new Consumer() { diff --git a/nashorn/test/src/jdk/nashorn/internal/runtime/test/CodeStoreAndPathTest.java b/nashorn/test/src/jdk/nashorn/internal/runtime/test/CodeStoreAndPathTest.java index 46f57fd059a..4b8944099a9 100644 --- a/nashorn/test/src/jdk/nashorn/internal/runtime/test/CodeStoreAndPathTest.java +++ b/nashorn/test/src/jdk/nashorn/internal/runtime/test/CodeStoreAndPathTest.java @@ -26,6 +26,7 @@ package jdk.nashorn.internal.runtime.test; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; import java.io.File; import java.io.IOException; import java.nio.file.DirectoryStream; @@ -38,7 +39,6 @@ import jdk.nashorn.api.scripting.NashornScriptEngineFactory; import org.testng.annotations.Test; /** - * @ignore Fails with jtreg, but passes with ant test run. Ignore for now. * @test * @bug 8039185 8039403 * @summary Test for persistent code cache and path handling @@ -113,7 +113,8 @@ public class CodeStoreAndPathTest { assertEquals(actualCodeCachePath, expectedCodeCachePath); // Check that code cache dir exists and it's not empty final File file = new File(actualCodeCachePath.toUri()); - assertFalse(!file.isDirectory(), "No code cache directory was created!"); + assertTrue(file.exists(), "No code cache directory was created!"); + assertTrue(file.isDirectory(), "Code cache location is not a directory!"); assertFalse(file.list().length == 0, "Code cache directory is empty!"); } @@ -174,7 +175,7 @@ public class CodeStoreAndPathTest { return codeCachePath.resolve(file); } } - throw new AssertionError("Code cache path not found"); + throw new AssertionError("Code cache path not found: " + codeCachePath.toString()); } private static void checkCompiledScripts(final DirectoryStream stream, final int numberOfScripts) throws IOException { From 656a9f516c62915f2c4955cb180fd5efe0e23d05 Mon Sep 17 00:00:00 2001 From: Athijegannathan Sundararajan Date: Wed, 19 Aug 2015 16:35:03 +0530 Subject: [PATCH 7/7] 8133872: Expression completion should work on contexts where an expression is accepted Reviewed-by: hannesw, mhaupt --- .../jdk/nashorn/tools/jjs/HistoryObject.java | 18 ++++++++ .../classes/jdk/nashorn/tools/jjs/Main.java | 3 +- .../nashorn/tools/jjs/NashornCompleter.java | 33 +++++++++++---- .../jdk/nashorn/internal/parser/Parser.java | 12 +++++- .../jdk/nashorn/tools/PartialParser.java | 42 +++++++++++++++++++ .../classes/jdk/nashorn/tools/Shell.java | 39 ++++++++++++++++- 6 files changed, 134 insertions(+), 13 deletions(-) create mode 100644 nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/PartialParser.java diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java index 162ba4e0b23..590108ff015 100644 --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java @@ -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 @@ import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; * A script friendly object that exposes history of commands to scripts. */ final class HistoryObject extends AbstractJSObject { + private static final Set props; + static { + final HashSet 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 @@ final class HistoryObject extends AbstractJSObject { return "[object history]"; } + @Override + public Set keySet() { + return props; + } + private void print() { for (History.Entry e : hist) { System.out.println(e.value()); diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java index d7b4892790d..e0edd4dad5d 100644 --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java @@ -85,6 +85,7 @@ public final class Main extends Shell { return new Main().run(in, out, err, args); } + /** * read-eval-print loop for Nashorn shell. * @@ -98,7 +99,7 @@ public final class Main extends Shell { 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) { diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java index bc5dee274c5..5a676da4d89 100644 --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java @@ -45,6 +45,7 @@ import jdk.nashorn.api.tree.Tree; 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 @@ import jdk.nashorn.internal.runtime.ScriptRuntime; 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 @@ final class NashornCompleter implements Completer { return cursor; } + // 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; + } + + + // 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(test).matches(); + final boolean endsWithDot = SELECT_PROP_MISSING.matcher(exprStr).matches(); - // If this is an incomplete member selection, then it is not legal code + // 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 @@ final class NashornCompleter implements Completer { // 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 result, + private int completeMemberSelect(final String exprStr, final int cursor, final List 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; diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java index bc7028683c2..4a84e4ea9bf 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java @@ -3237,6 +3237,7 @@ loop: } /** + * {@code * MultiplicativeExpression : * UnaryExpression * MultiplicativeExpression * UnaryExpression @@ -3323,11 +3324,15 @@ loop: * 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 @@ loop: 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); diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/PartialParser.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/PartialParser.java new file mode 100644 index 00000000000..db87140c2c9 --- /dev/null +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/PartialParser.java @@ -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); +} diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/Shell.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/Shell.java index ec0397a7b37..1629762a55f 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/Shell.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/Shell.java @@ -43,6 +43,7 @@ import jdk.nashorn.api.scripting.NashornException; 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 @@ import jdk.nashorn.internal.runtime.options.Options; /** * Command line Shell for processing JavaScript files. */ -public class Shell { +public class Shell implements PartialParser { /** * Resource name for properties file @@ -396,6 +397,42 @@ public class Shell { return ScriptRuntime.apply(target, self); } + /** + * 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("", 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. *