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 26 package jdk.nashorn.tools.jjs; 27 28 import java.io.BufferedReader; 29 import java.io.InputStream; 30 import java.io.InputStreamReader; 31 import java.io.IOException; 32 import java.io.OutputStream; 33 import java.io.PrintWriter; 34 import java.util.Iterator; 35 import java.util.List; 36 import java.util.prefs.Preferences; 37 import jdk.nashorn.api.tree.AssignmentTree; 38 import jdk.nashorn.api.tree.BinaryTree; 39 import jdk.nashorn.api.tree.CompilationUnitTree; 40 import jdk.nashorn.api.tree.CompoundAssignmentTree; 41 import jdk.nashorn.api.tree.ConditionalExpressionTree; 42 import jdk.nashorn.api.tree.ExpressionTree; 43 import jdk.nashorn.api.tree.ExpressionStatementTree; 44 import jdk.nashorn.api.tree.InstanceOfTree; 45 import jdk.nashorn.api.tree.MemberSelectTree; 46 import jdk.nashorn.api.tree.SimpleTreeVisitorES5_1; 47 import jdk.nashorn.api.tree.Tree; 48 import jdk.nashorn.api.tree.UnaryTree; 49 import jdk.nashorn.api.tree.Parser; 50 import jdk.nashorn.api.scripting.NashornException; 51 import jdk.nashorn.internal.objects.Global; 52 import jdk.nashorn.internal.runtime.Context; 53 import jdk.nashorn.internal.runtime.ErrorManager; 54 import jdk.nashorn.internal.runtime.JSType; 55 import jdk.nashorn.internal.runtime.ScriptEnvironment; 56 import jdk.nashorn.internal.runtime.ScriptObject; 57 import jdk.nashorn.internal.runtime.ScriptRuntime; 58 import jdk.nashorn.tools.Shell; 59 import jdk.internal.jline.console.completer.Completer; 60 import jdk.internal.jline.console.UserInterruptException; 61 62 /** 63 * Interactive command line Shell for Nashorn. 64 */ 65 public final class Main extends Shell { 66 private Main() {} 67 68 static final Preferences PREFS = Preferences.userRoot().node("tool/jjs"); 69 70 /** 71 * Main entry point with the default input, output and error streams. 72 * 73 * @param args The command line arguments 74 */ 75 public static void main(final String[] args) { 76 try { 77 final int exitCode = main(System.in, System.out, System.err, args); 78 if (exitCode != SUCCESS) { 79 System.exit(exitCode); 80 } 81 } catch (final IOException e) { 82 System.err.println(e); //bootstrapping, Context.err may not exist 83 System.exit(IO_ERROR); 84 } 85 } 86 87 /** 88 * Starting point for executing a {@code Shell}. Starts a shell with the 89 * given arguments and streams and lets it run until exit. 90 * 91 * @param in input stream for Shell 92 * @param out output stream for Shell 93 * @param err error stream for Shell 94 * @param args arguments to Shell 95 * 96 * @return exit code 97 * 98 * @throws IOException if there's a problem setting up the streams 99 */ 100 public static int main(final InputStream in, final OutputStream out, final OutputStream err, final String[] args) throws IOException { 101 return new Main().run(in, out, err, args); 102 } 103 104 /** 105 * read-eval-print loop for Nashorn shell. 106 * 107 * @param context the nashorn context 108 * @param global global scope object to use 109 * @return return code 110 */ 111 protected int readEvalPrint(final Context context, final Global global) { 112 final ScriptEnvironment env = context.getEnv(); 113 final String prompt = bundle.getString("shell.prompt"); 114 final PrintWriter err = context.getErr(); 115 final Global oldGlobal = Context.getGlobal(); 116 final boolean globalChanged = (oldGlobal != global); 117 final Parser parser = Parser.create(); 118 119 // simple source "tab completer" for nashorn 120 final Completer completer = new Completer() { 121 @Override 122 public int complete(final String test, final int cursor, final List<CharSequence> result) { 123 // check that cursor is at the end of test string. Do not complete in the middle! 124 if (cursor != test.length()) { 125 return cursor; 126 } 127 128 // if it has a ".", then assume it is a member selection expression 129 final int idx = test.lastIndexOf('.'); 130 if (idx == -1) { 131 return cursor; 132 } 133 134 // stuff before the last "." 135 final String exprBeforeDot = test.substring(0, idx); 136 137 // Make sure that completed code will have a member expression! Adding ".x" as a 138 // random property/field name selected to make it possible to be a proper member select 139 final ExpressionTree topExpr = getTopLevelExpression(parser, exprBeforeDot + ".x"); 140 if (topExpr == null) { 141 // did not parse to be a top level expression, no suggestions! 142 return cursor; 143 } 144 145 146 // Find 'right most' member select expression's start position 147 final int startPosition = (int) getStartOfMemberSelect(topExpr); 148 if (startPosition == -1) { 149 // not a member expression that we can handle for completion 150 return cursor; 151 } 152 153 // The part of the right most member select expression before the "." 154 final String objExpr = test.substring(startPosition, idx); 155 156 // try to evaluate the object expression part as a script 157 Object obj = null; 158 try { 159 obj = context.eval(global, objExpr, global, "<suggestions>"); 160 } catch (Exception ignored) { 161 // throw the exception - this is during tab-completion 162 } 163 164 if (obj != null && obj != ScriptRuntime.UNDEFINED) { 165 // where is the last dot? Is there a partial property name specified? 166 final String prefix = test.substring(idx + 1); 167 if (prefix.isEmpty()) { 168 // no user specified "prefix". List all properties of the object 169 result.addAll(PropertiesHelper.getProperties(obj)); 170 return cursor; 171 } else { 172 // list of properties matching the user specified prefix 173 result.addAll(PropertiesHelper.getProperties(obj, prefix)); 174 return idx + 1; 175 } 176 } 177 178 return cursor; 179 } 180 }; 181 182 try (final Console in = new Console(System.in, System.out, PREFS, completer)) { 183 if (globalChanged) { 184 Context.setGlobal(global); 185 } 186 187 global.addShellBuiltins(); 188 189 while (true) { 190 String source = ""; 191 try { 192 source = in.readLine(prompt); 193 } catch (final IOException ioe) { 194 err.println(ioe.toString()); 195 if (env._dump_on_error) { 196 ioe.printStackTrace(err); 197 } 198 return IO_ERROR; 199 } catch (final UserInterruptException ex) { 200 break; 201 } 202 203 if (source.isEmpty()) { 204 continue; 205 } 206 207 try { 208 final Object res = context.eval(global, source, global, "<shell>"); 209 if (res != ScriptRuntime.UNDEFINED) { 210 err.println(JSType.toString(res)); 211 } 212 } catch (final Exception e) { 213 err.println(e); 214 if (env._dump_on_error) { 215 e.printStackTrace(err); 216 } 217 } 218 } 219 } catch (final Exception e) { 220 err.println(e); 221 if (env._dump_on_error) { 222 e.printStackTrace(err); 223 } 224 } finally { 225 if (globalChanged) { 226 Context.setGlobal(oldGlobal); 227 } 228 } 229 230 return SUCCESS; 231 } 232 233 // returns ExpressionTree if the given code parses to a top level expression. 234 // Or else returns null. 235 private ExpressionTree getTopLevelExpression(final Parser parser, final String code) { 236 try { 237 final CompilationUnitTree cut = parser.parse("<code>", code, null); 238 final List<? extends Tree> stats = cut.getSourceElements(); 239 if (stats.size() == 1) { 240 final Tree stat = stats.get(0); 241 if (stat instanceof ExpressionStatementTree) { 242 return ((ExpressionStatementTree)stat).getExpression(); 243 } 244 } 245 } catch (final NashornException ignored) { 246 // ignore any parser error. This is for completion anyway! 247 // And user will get that error later when the expression is evaluated. 248 } 249 250 return null; 251 } 252 253 254 private long getStartOfMemberSelect(final ExpressionTree expr) { 255 if (expr instanceof MemberSelectTree) { 256 return ((MemberSelectTree)expr).getStartPosition(); 257 } 258 259 final Tree rightMostExpr = expr.accept(new SimpleTreeVisitorES5_1<Tree, Void>() { 260 @Override 261 public Tree visitAssignment(final AssignmentTree at, final Void v) { 262 return at.getExpression(); 263 } 264 265 @Override 266 public Tree visitCompoundAssignment(final CompoundAssignmentTree cat, final Void v) { 267 return cat.getExpression(); 268 } 269 270 @Override 271 public Tree visitConditionalExpression(final ConditionalExpressionTree cet, final Void v) { 272 return cet.getFalseExpression(); 273 } 274 275 @Override 276 public Tree visitBinary(final BinaryTree bt, final Void v) { 277 return bt.getRightOperand(); 278 } 279 280 @Override 281 public Tree visitInstanceOf(final InstanceOfTree it, final Void v) { 282 return it.getType(); 283 } 284 285 @Override 286 public Tree visitUnary(final UnaryTree ut, final Void v) { 287 return ut.getExpression(); 288 } 289 }, null); 290 291 return (rightMostExpr instanceof MemberSelectTree)? 292 rightMostExpr.getStartPosition() : -1L; 293 } 294 }