NashornCompleter.java revision 1377:23f843804b05
1/* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package jdk.nashorn.tools.jjs; 27 28import java.util.List; 29import jdk.internal.jline.console.completer.Completer; 30import jdk.nashorn.api.tree.AssignmentTree; 31import jdk.nashorn.api.tree.BinaryTree; 32import jdk.nashorn.api.tree.CompilationUnitTree; 33import jdk.nashorn.api.tree.CompoundAssignmentTree; 34import jdk.nashorn.api.tree.ConditionalExpressionTree; 35import jdk.nashorn.api.tree.ExpressionTree; 36import jdk.nashorn.api.tree.ExpressionStatementTree; 37import jdk.nashorn.api.tree.InstanceOfTree; 38import jdk.nashorn.api.tree.MemberSelectTree; 39import jdk.nashorn.api.tree.SimpleTreeVisitorES5_1; 40import jdk.nashorn.api.tree.Tree; 41import jdk.nashorn.api.tree.UnaryTree; 42import jdk.nashorn.api.tree.Parser; 43import jdk.nashorn.api.scripting.NashornException; 44import jdk.nashorn.internal.objects.Global; 45import jdk.nashorn.internal.runtime.Context; 46import jdk.nashorn.internal.runtime.ScriptRuntime; 47 48// A simple source completer for nashorn 49final class NashornCompleter implements Completer { 50 private final Context context; 51 private final Global global; 52 private final Parser parser; 53 54 NashornCompleter(final Context context, final Global global) { 55 this.context = context; 56 this.global = global; 57 this.parser = Parser.create(); 58 } 59 60 @Override 61 public int complete(final String test, final int cursor, final List<CharSequence> result) { 62 // check that cursor is at the end of test string. Do not complete in the middle! 63 if (cursor != test.length()) { 64 return cursor; 65 } 66 67 // if it has a ".", then assume it is a member selection expression 68 final int idx = test.lastIndexOf('.'); 69 if (idx == -1) { 70 if (isIdentifier(test)) { 71 // identifier - return matching global variable names, if any 72 result.addAll(PropertiesHelper.getProperties(global, test)); 73 return idx + 1; 74 } 75 76 return cursor; 77 } 78 79 // stuff before the last "." 80 final String exprBeforeDot = test.substring(0, idx); 81 82 // Make sure that completed code will have a member expression! Adding ".x" as a 83 // random property/field name selected to make it possible to be a proper member select 84 final ExpressionTree topExpr = getTopLevelExpression(parser, exprBeforeDot + ".x"); 85 if (topExpr == null) { 86 // did not parse to be a top level expression, no suggestions! 87 return cursor; 88 } 89 90 91 // Find 'right most' member select expression's start position 92 final int startPosition = (int) getStartOfMemberSelect(topExpr); 93 if (startPosition == -1) { 94 // not a member expression that we can handle for completion 95 return cursor; 96 } 97 98 // The part of the right most member select expression before the "." 99 final String objExpr = test.substring(startPosition, idx); 100 101 // try to evaluate the object expression part as a script 102 Object obj = null; 103 try { 104 obj = context.eval(global, objExpr, global, "<suggestions>"); 105 } catch (Exception ignored) { 106 // throw the exception - this is during tab-completion 107 } 108 109 if (obj != null && obj != ScriptRuntime.UNDEFINED) { 110 // where is the last dot? Is there a partial property name specified? 111 final String prefix = test.substring(idx + 1); 112 if (prefix.isEmpty()) { 113 // no user specified "prefix". List all properties of the object 114 result.addAll(PropertiesHelper.getProperties(obj)); 115 return cursor; 116 } else { 117 // list of properties matching the user specified prefix 118 result.addAll(PropertiesHelper.getProperties(obj, prefix)); 119 return idx + 1; 120 } 121 } 122 123 return cursor; 124 } 125 126 // returns ExpressionTree if the given code parses to a top level expression. 127 // Or else returns null. 128 private ExpressionTree getTopLevelExpression(final Parser parser, final String code) { 129 try { 130 final CompilationUnitTree cut = parser.parse("<code>", code, null); 131 final List<? extends Tree> stats = cut.getSourceElements(); 132 if (stats.size() == 1) { 133 final Tree stat = stats.get(0); 134 if (stat instanceof ExpressionStatementTree) { 135 return ((ExpressionStatementTree)stat).getExpression(); 136 } 137 } 138 } catch (final NashornException ignored) { 139 // ignore any parser error. This is for completion anyway! 140 // And user will get that error later when the expression is evaluated. 141 } 142 143 return null; 144 } 145 146 147 private long getStartOfMemberSelect(final ExpressionTree expr) { 148 if (expr instanceof MemberSelectTree) { 149 return ((MemberSelectTree)expr).getStartPosition(); 150 } 151 152 final Tree rightMostExpr = expr.accept(new SimpleTreeVisitorES5_1<Tree, Void>() { 153 @Override 154 public Tree visitAssignment(final AssignmentTree at, final Void v) { 155 return at.getExpression(); 156 } 157 158 @Override 159 public Tree visitCompoundAssignment(final CompoundAssignmentTree cat, final Void v) { 160 return cat.getExpression(); 161 } 162 163 @Override 164 public Tree visitConditionalExpression(final ConditionalExpressionTree cet, final Void v) { 165 return cet.getFalseExpression(); 166 } 167 168 @Override 169 public Tree visitBinary(final BinaryTree bt, final Void v) { 170 return bt.getRightOperand(); 171 } 172 173 @Override 174 public Tree visitInstanceOf(final InstanceOfTree it, final Void v) { 175 return it.getType(); 176 } 177 178 @Override 179 public Tree visitUnary(final UnaryTree ut, final Void v) { 180 return ut.getExpression(); 181 } 182 }, null); 183 184 return (rightMostExpr instanceof MemberSelectTree)? 185 rightMostExpr.getStartPosition() : -1L; 186 } 187 188 // return if the given String is a valid identifier name or not 189 private boolean isIdentifier(final String test) { 190 if (test.isEmpty()) { 191 return false; 192 } 193 194 final char[] buf = test.toCharArray(); 195 if (! Character.isJavaIdentifierStart(buf[0])) { 196 return false; 197 } 198 199 for (int idx = 1; idx < buf.length; idx++) { 200 if (! Character.isJavaIdentifierPart(buf[idx])) { 201 return false; 202 } 203 } 204 205 return true; 206 } 207} 208