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