1 /* 2 * Copyright (c) 2010, 2013, 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; 27 28 import static jdk.nashorn.internal.runtime.Source.sourceFor; 29 30 import java.io.BufferedReader; 31 import java.io.File; 32 import java.io.FileReader; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.io.InputStreamReader; 36 import java.io.OutputStream; 37 import java.io.PrintStream; 38 import java.io.PrintWriter; 39 import java.util.List; 40 import java.util.Locale; 41 import java.util.ResourceBundle; 42 import jdk.nashorn.api.scripting.NashornException; 43 import jdk.nashorn.internal.codegen.Compiler; 44 import jdk.nashorn.internal.codegen.Compiler.CompilationPhases; 45 import jdk.nashorn.internal.ir.FunctionNode; 46 import jdk.nashorn.internal.ir.Expression; 47 import jdk.nashorn.internal.ir.debug.ASTWriter; 48 import jdk.nashorn.internal.ir.debug.PrintVisitor; 49 import jdk.nashorn.internal.objects.Global; 50 import jdk.nashorn.internal.parser.Parser; 51 import jdk.nashorn.internal.runtime.Context; 52 import jdk.nashorn.internal.runtime.ErrorManager; 53 import jdk.nashorn.internal.runtime.JSType; 54 import jdk.nashorn.internal.runtime.Property; 55 import jdk.nashorn.internal.runtime.ScriptEnvironment; 56 import jdk.nashorn.internal.runtime.ScriptFunction; 57 import jdk.nashorn.internal.runtime.ScriptRuntime; 58 import jdk.nashorn.internal.runtime.options.Options; 59 60 /** 61 * Command line Shell for processing JavaScript files. 62 */ 63 public class Shell { 64 65 /** 66 * Resource name for properties file 67 */ 68 private static final String MESSAGE_RESOURCE = "jdk.nashorn.tools.resources.Shell"; 69 /** 70 * Shell message bundle. 71 */ 72 protected static final ResourceBundle bundle = ResourceBundle.getBundle(MESSAGE_RESOURCE, Locale.getDefault()); 73 74 /** 75 * Exit code for command line tool - successful 76 */ 77 public static final int SUCCESS = 0; 78 /** 79 * Exit code for command line tool - error on command line 80 */ 81 public static final int COMMANDLINE_ERROR = 100; 82 /** 83 * Exit code for command line tool - error compiling script 84 */ 85 public static final int COMPILATION_ERROR = 101; 86 /** 87 * Exit code for command line tool - error during runtime 88 */ 89 public static final int RUNTIME_ERROR = 102; 90 /** 91 * Exit code for command line tool - i/o error 92 */ 93 public static final int IO_ERROR = 103; 94 /** 95 * Exit code for command line tool - internal error 96 */ 97 public static final int INTERNAL_ERROR = 104; 98 99 /** 100 * Constructor 101 */ 102 protected Shell() { 103 } 104 105 /** 106 * Main entry point with the default input, output and error streams. 107 * 108 * @param args The command line arguments 109 */ 110 public static void main(final String[] args) { 111 try { 112 final int exitCode = main(System.in, System.out, System.err, args); 113 if (exitCode != SUCCESS) { 114 System.exit(exitCode); 115 } 116 } catch (final IOException e) { 117 System.err.println(e); //bootstrapping, Context.err may not exist 118 System.exit(IO_ERROR); 119 } 120 } 121 122 /** 123 * Starting point for executing a {@code Shell}. Starts a shell with the 124 * given arguments and streams and lets it run until exit. 125 * 126 * @param in input stream for Shell 127 * @param out output stream for Shell 128 * @param err error stream for Shell 129 * @param args arguments to Shell 130 * 131 * @return exit code 132 * 133 * @throws IOException if there's a problem setting up the streams 134 */ 135 public static int main(final InputStream in, final OutputStream out, final OutputStream err, final String[] args) throws IOException { 136 return new Shell().run(in, out, err, args); 137 } 138 139 /** 140 * Run method logic. 141 * 142 * @param in input stream for Shell 143 * @param out output stream for Shell 144 * @param err error stream for Shell 145 * @param args arguments to Shell 146 * 147 * @return exit code 148 * 149 * @throws IOException if there's a problem setting up the streams 150 */ 151 protected final int run(final InputStream in, final OutputStream out, final OutputStream err, final String[] args) throws IOException { 152 final Context context = makeContext(in, out, err, args); 153 if (context == null) { 154 return COMMANDLINE_ERROR; 155 } 156 157 final Global global = context.createGlobal(); 158 final ScriptEnvironment env = context.getEnv(); 159 final List<String> files = env.getFiles(); 160 if (files.isEmpty()) { 161 return readEvalPrint(context, global); 162 } 163 164 if (env._compile_only) { 165 return compileScripts(context, global, files); 166 } 167 168 if (env._fx) { 169 return runFXScripts(context, global, files); 170 } 171 172 return runScripts(context, global, files); 173 } 174 175 /** 176 * Make a new Nashorn Context to compile and/or run JavaScript files. 177 * 178 * @param in input stream for Shell 179 * @param out output stream for Shell 180 * @param err error stream for Shell 181 * @param args arguments to Shell 182 * 183 * @return null if there are problems with option parsing. 184 */ 185 private static Context makeContext(final InputStream in, final OutputStream out, final OutputStream err, final String[] args) { 186 final PrintStream pout = out instanceof PrintStream ? (PrintStream) out : new PrintStream(out); 187 final PrintStream perr = err instanceof PrintStream ? (PrintStream) err : new PrintStream(err); 188 final PrintWriter wout = new PrintWriter(pout, true); 189 final PrintWriter werr = new PrintWriter(perr, true); 190 191 // Set up error handler. 192 final ErrorManager errors = new ErrorManager(werr); 193 // Set up options. 194 final Options options = new Options("nashorn", werr); 195 196 // parse options 197 if (args != null) { 198 try { 199 options.process(args); 200 } catch (final IllegalArgumentException e) { 201 werr.println(bundle.getString("shell.usage")); 202 options.displayHelp(e); 203 return null; 204 } 205 } 206 207 // detect scripting mode by any source's first character being '#' 208 if (!options.getBoolean("scripting")) { 209 for (final String fileName : options.getFiles()) { 210 final File firstFile = new File(fileName); 211 if (firstFile.isFile()) { 212 try (final FileReader fr = new FileReader(firstFile)) { 213 final int firstChar = fr.read(); 214 // starts with '# 215 if (firstChar == '#') { 216 options.set("scripting", true); 217 break; 218 } 219 } catch (final IOException e) { 220 // ignore this. File IO errors will be reported later anyway 221 } 222 } 223 } 224 } 225 226 return new Context(options, errors, wout, werr, Thread.currentThread().getContextClassLoader()); 227 } 228 229 /** 230 * Compiles the given script files in the command line 231 * This is called only when using the --compile-only flag 232 * 233 * @param context the nashorn context 234 * @param global the global scope 235 * @param files the list of script files to compile 236 * 237 * @return error code 238 * @throws IOException when any script file read results in I/O error 239 */ 240 private static int compileScripts(final Context context, final Global global, final List<String> files) throws IOException { 241 final Global oldGlobal = Context.getGlobal(); 242 final boolean globalChanged = (oldGlobal != global); 243 final ScriptEnvironment env = context.getEnv(); 244 try { 245 if (globalChanged) { 246 Context.setGlobal(global); 247 } 248 final ErrorManager errors = context.getErrorManager(); 249 250 // For each file on the command line. 251 for (final String fileName : files) { 252 final FunctionNode functionNode = new Parser(env, sourceFor(fileName, new File(fileName)), errors, env._strict, 0, context.getLogger(Parser.class)).parse(); 253 254 if (errors.getNumberOfErrors() != 0) { 255 return COMPILATION_ERROR; 256 } 257 258 new Compiler( 259 context, 260 env, 261 null, //null - pass no code installer - this is compile only 262 functionNode.getSource(), 263 context.getErrorManager(), 264 env._strict | functionNode.isStrict()). 265 compile(functionNode, CompilationPhases.COMPILE_ALL_NO_INSTALL); 266 267 if (env._print_ast) { 268 context.getErr().println(new ASTWriter(functionNode)); 269 } 270 271 if (env._print_parse) { 272 context.getErr().println(new PrintVisitor(functionNode)); 273 } 274 275 if (errors.getNumberOfErrors() != 0) { 276 return COMPILATION_ERROR; 277 } 278 } 279 } finally { 280 env.getOut().flush(); 281 env.getErr().flush(); 282 if (globalChanged) { 283 Context.setGlobal(oldGlobal); 284 } 285 } 286 287 return SUCCESS; 288 } 289 290 /** 291 * Runs the given JavaScript files in the command line 292 * 293 * @param context the nashorn context 294 * @param global the global scope 295 * @param files the list of script files to run 296 * 297 * @return error code 298 * @throws IOException when any script file read results in I/O error 299 */ 300 private int runScripts(final Context context, final Global global, final List<String> files) throws IOException { 301 final Global oldGlobal = Context.getGlobal(); 302 final boolean globalChanged = (oldGlobal != global); 303 try { 304 if (globalChanged) { 305 Context.setGlobal(global); 306 } 307 final ErrorManager errors = context.getErrorManager(); 308 309 // For each file on the command line. 310 for (final String fileName : files) { 311 if ("-".equals(fileName)) { 312 final int res = readEvalPrint(context, global); 313 if (res != SUCCESS) { 314 return res; 315 } 316 continue; 317 } 318 319 final File file = new File(fileName); 320 final ScriptFunction script = context.compileScript(sourceFor(fileName, file), global); 321 if (script == null || errors.getNumberOfErrors() != 0) { 322 return COMPILATION_ERROR; 323 } 324 325 try { 326 apply(script, global); 327 } catch (final NashornException e) { 328 errors.error(e.toString()); 329 if (context.getEnv()._dump_on_error) { 330 e.printStackTrace(context.getErr()); 331 } 332 333 return RUNTIME_ERROR; 334 } 335 } 336 } finally { 337 context.getOut().flush(); 338 context.getErr().flush(); 339 if (globalChanged) { 340 Context.setGlobal(oldGlobal); 341 } 342 } 343 344 return SUCCESS; 345 } 346 347 /** 348 * Runs launches "fx:bootstrap.js" with the given JavaScript files provided 349 * as arguments. 350 * 351 * @param context the nashorn context 352 * @param global the global scope 353 * @param files the list of script files to provide 354 * 355 * @return error code 356 * @throws IOException when any script file read results in I/O error 357 */ 358 private static int runFXScripts(final Context context, final Global global, final List<String> files) throws IOException { 359 final Global oldGlobal = Context.getGlobal(); 360 final boolean globalChanged = (oldGlobal != global); 361 try { 362 if (globalChanged) { 363 Context.setGlobal(global); 364 } 365 366 global.addOwnProperty("$GLOBAL", Property.NOT_ENUMERABLE, global); 367 global.addOwnProperty("$SCRIPTS", Property.NOT_ENUMERABLE, files); 368 context.load(global, "fx:bootstrap.js"); 369 } catch (final NashornException e) { 370 context.getErrorManager().error(e.toString()); 371 if (context.getEnv()._dump_on_error) { 372 e.printStackTrace(context.getErr()); 373 } 374 375 return RUNTIME_ERROR; 376 } finally { 377 context.getOut().flush(); 378 context.getErr().flush(); 379 if (globalChanged) { 380 Context.setGlobal(oldGlobal); 381 } 382 } 383 384 return SUCCESS; 385 } 386 387 /** 388 * Hook to ScriptFunction "apply". A performance metering shell may 389 * introduce enter/exit timing here. 390 * 391 * @param target target function for apply 392 * @param self self reference for apply 393 * 394 * @return result of the function apply 395 */ 396 protected Object apply(final ScriptFunction target, final Object self) { 397 return ScriptRuntime.apply(target, self); 398 } 399 400 /** 401 * Parse potentially partial code and keep track of the start of last expression. 402 * This 'partial' parsing support is meant to be used for code-completion. 403 * 404 * @param context the nashorn context 405 * @param code code that is to be parsed 406 * @return the start index of the last expression parsed in the (incomplete) code. 407 */ 408 protected int getLastExpressionStart(final Context context, final String code) { 409 final int[] exprStart = { -1 }; 410 411 final Parser p = new Parser(context.getEnv(), sourceFor("<partial_code>", code),new Context.ThrowErrorManager()) { 412 @Override 413 protected Expression expression() { 414 exprStart[0] = this.start; 415 return super.expression(); 416 } 417 418 @Override 419 protected Expression assignmentExpression(final boolean noIn) { 420 exprStart[0] = this.start; 421 return super.expression(); 422 } 423 }; 424 425 try { 426 p.parse(); 427 } catch (final Exception ignored) { 428 // throw any parser exception, but we are partial parsing anyway 429 } 430 431 return exprStart[0]; 432 } 433 434 435 /** 436 * read-eval-print loop for Nashorn shell. 437 * 438 * @param context the nashorn context 439 * @param global global scope object to use 440 * @return return code 441 */ 442 protected int readEvalPrint(final Context context, final Global global) { 443 final String prompt = bundle.getString("shell.prompt"); 444 final BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 445 final PrintWriter err = context.getErr(); 446 final Global oldGlobal = Context.getGlobal(); 447 final boolean globalChanged = (oldGlobal != global); 448 final ScriptEnvironment env = context.getEnv(); 449 450 try { 451 if (globalChanged) { 452 Context.setGlobal(global); 453 } 454 455 global.addShellBuiltins(); 456 457 while (true) { 458 err.print(prompt); 459 err.flush(); 460 461 String source = ""; 462 try { 463 source = in.readLine(); 464 } catch (final IOException ioe) { 465 err.println(ioe.toString()); 466 } 467 468 if (source == null) { 469 break; 470 } 471 472 if (source.isEmpty()) { 473 continue; 474 } 475 476 try { 477 final Object res = context.eval(global, source, global, "<shell>"); 478 if (res != ScriptRuntime.UNDEFINED) { 479 err.println(JSType.toString(res)); 480 } 481 } catch (final Exception e) { 482 err.println(e); 483 if (env._dump_on_error) { 484 e.printStackTrace(err); 485 } 486 } 487 } 488 } finally { 489 if (globalChanged) { 490 Context.setGlobal(oldGlobal); 491 } 492 } 493 494 return SUCCESS; 495 } 496 }