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