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.ir.FunctionNode; 45 import jdk.nashorn.internal.ir.debug.ASTWriter; 46 import jdk.nashorn.internal.ir.debug.PrintVisitor; 47 import jdk.nashorn.internal.objects.Global; 48 import jdk.nashorn.internal.parser.Parser; 49 import jdk.nashorn.internal.runtime.Context; 50 import jdk.nashorn.internal.runtime.ErrorManager; 51 import jdk.nashorn.internal.runtime.JSType; 52 import jdk.nashorn.internal.runtime.Property; 53 import jdk.nashorn.internal.runtime.ScriptEnvironment; 54 import jdk.nashorn.internal.runtime.ScriptFunction; 55 import jdk.nashorn.internal.runtime.ScriptRuntime; 56 import jdk.nashorn.internal.runtime.Source; 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 private 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 System.exit(main(System.in, System.out, System.err, args)); 112 } catch (final IOException e) { 113 System.err.println(e); //bootstrapping, Context.err may not exist 114 System.exit(IO_ERROR); 115 } 116 } 117 118 /** 119 * Starting point for executing a {@code Shell}. Starts a shell with the 120 * given arguments and streams and lets it run until exit. 121 * 122 * @param in input stream for Shell 123 * @param out output stream for Shell 124 * @param err error stream for Shell 125 * @param args arguments to Shell 126 * 127 * @return exit code 128 * 129 * @throws IOException if there's a problem setting up the streams 130 */ 131 public static int main(final InputStream in, final OutputStream out, final OutputStream err, final String[] args) throws IOException { 132 return new Shell().run(in, out, err, args); 133 } 134 135 /** 136 * Run method logic. 137 * 138 * @param in input stream for Shell 139 * @param out output stream for Shell 140 * @param err error stream for Shell 141 * @param args arguments to Shell 142 * 143 * @return exit code 144 * 145 * @throws IOException if there's a problem setting up the streams 146 */ 147 protected final int run(final InputStream in, final OutputStream out, final OutputStream err, final String[] args) throws IOException { 148 final Context context = makeContext(in, out, err, args); 149 if (context == null) { 150 return COMMANDLINE_ERROR; 151 } 152 153 final Global global = context.createGlobal(); 154 final ScriptEnvironment env = context.getEnv(); 155 final List<String> files = env.getFiles(); 156 if (files.isEmpty()) { 157 return readEvalPrint(context, global); 158 } 159 160 if (env._compile_only) { 161 return compileScripts(context, global, files); 162 } 163 164 if (env._fx) { 165 return runFXScripts(context, global, files); 166 } 167 168 return runScripts(context, global, files); 169 } 170 171 /** 172 * Make a new Nashorn Context to compile and/or run JavaScript files. 173 * 174 * @param in input stream for Shell 175 * @param out output stream for Shell 176 * @param err error stream for Shell 177 * @param args arguments to Shell 178 * 179 * @return null if there are problems with option parsing. 180 */ 181 @SuppressWarnings("resource") 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 * 229 * @param context the nashorn context 230 * @param global the global scope 231 * @param files the list of script files to compile 232 * 233 * @return error code 234 * @throws IOException when any script file read results in I/O error 235 */ 236 private static int compileScripts(final Context context, final Global global, final List<String> files) throws IOException { 237 final Global oldGlobal = Context.getGlobal(); 238 final boolean globalChanged = (oldGlobal != global); 239 final ScriptEnvironment env = context.getEnv(); 240 try { 241 if (globalChanged) { 242 Context.setGlobal(global); 243 } 244 final ErrorManager errors = context.getErrorManager(); 245 246 // For each file on the command line. 247 for (final String fileName : files) { 248 final FunctionNode functionNode = new Parser(env, sourceFor(fileName, new File(fileName)), errors).parse(); 249 250 if (errors.getNumberOfErrors() != 0) { 251 return COMPILATION_ERROR; 252 } 253 254 if (env._print_ast) { 255 context.getErr().println(new ASTWriter(functionNode)); 256 } 257 258 if (env._print_parse) { 259 context.getErr().println(new PrintVisitor(functionNode)); 260 } 261 262 //null - pass no code installer - this is compile only 263 new Compiler(env).compile(functionNode); 264 } 265 } finally { 266 env.getOut().flush(); 267 env.getErr().flush(); 268 if (globalChanged) { 269 Context.setGlobal(oldGlobal); 270 } 271 } 272 273 return SUCCESS; 274 } 275 276 /** 277 * Runs the given JavaScript files in the command line 278 * 279 * @param context the nashorn context 280 * @param global the global scope 281 * @param files the list of script files to run 282 * 283 * @return error code 284 * @throws IOException when any script file read results in I/O error 285 */ 286 private int runScripts(final Context context, final Global global, final List<String> files) throws IOException { 287 final Global oldGlobal = Context.getGlobal(); 288 final boolean globalChanged = (oldGlobal != global); 289 try { 290 if (globalChanged) { 291 Context.setGlobal(global); 292 } 293 final ErrorManager errors = context.getErrorManager(); 294 295 // For each file on the command line. 296 for (final String fileName : files) { 297 if ("-".equals(fileName)) { 298 final int res = readEvalPrint(context, global); 299 if (res != SUCCESS) { 300 return res; 301 } 302 continue; 303 } 304 305 final File file = new File(fileName); 306 final ScriptFunction script = context.compileScript(sourceFor(fileName, file), global); 307 if (script == null || errors.getNumberOfErrors() != 0) { 308 return COMPILATION_ERROR; 309 } 310 311 try { 312 apply(script, global); 313 } catch (final NashornException e) { 314 errors.error(e.toString()); 315 if (context.getEnv()._dump_on_error) { 316 e.printStackTrace(context.getErr()); 317 } 318 319 return RUNTIME_ERROR; 320 } 321 } 322 } finally { 323 context.getOut().flush(); 324 context.getErr().flush(); 325 if (globalChanged) { 326 Context.setGlobal(oldGlobal); 327 } 328 } 329 330 return SUCCESS; 331 } 332 333 /** 334 * Runs launches "fx:bootstrap.js" with the given JavaScript files provided 335 * as arguments. 336 * 337 * @param context the nashorn context 338 * @param global the global scope 339 * @param files the list of script files to provide 340 * 341 * @return error code 342 * @throws IOException when any script file read results in I/O error 343 */ 344 private static int runFXScripts(final Context context, final Global global, final List<String> files) throws IOException { 345 final Global oldGlobal = Context.getGlobal(); 346 final boolean globalChanged = (oldGlobal != global); 347 try { 348 if (globalChanged) { 349 Context.setGlobal(global); 350 } 351 352 global.addOwnProperty("$GLOBAL", Property.NOT_ENUMERABLE, global); 353 global.addOwnProperty("$SCRIPTS", Property.NOT_ENUMERABLE, files); 354 context.load(global, "fx:bootstrap.js"); 355 } catch (final NashornException e) { 356 context.getErrorManager().error(e.toString()); 357 if (context.getEnv()._dump_on_error) { 358 e.printStackTrace(context.getErr()); 359 } 360 361 return RUNTIME_ERROR; 362 } finally { 363 context.getOut().flush(); 364 context.getErr().flush(); 365 if (globalChanged) { 366 Context.setGlobal(oldGlobal); 367 } 368 } 369 370 return SUCCESS; 371 } 372 373 /** 374 * Hook to ScriptFunction "apply". A performance metering shell may 375 * introduce enter/exit timing here. 376 * 377 * @param target target function for apply 378 * @param self self reference for apply 379 * 380 * @return result of the function apply 381 */ 382 protected Object apply(final ScriptFunction target, final Object self) { 383 return ScriptRuntime.apply(target, self); 384 } 385 386 /** 387 * read-eval-print loop for Nashorn shell. 388 * 389 * @param context the nashorn context 390 * @param global global scope object to use 391 * @return return code 392 */ 393 @SuppressWarnings("resource") 394 private static int readEvalPrint(final Context context, final Global global) { 395 final String prompt = bundle.getString("shell.prompt"); 396 final BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 397 final PrintWriter err = context.getErr(); 398 final Global oldGlobal = Context.getGlobal(); 399 final boolean globalChanged = (oldGlobal != global); 400 final ScriptEnvironment env = context.getEnv(); 401 402 try { 403 if (globalChanged) { 404 Context.setGlobal(global); 405 } 406 407 // initialize with "shell.js" script 408 try { 409 final Source source = sourceFor("<shell.js>", Shell.class.getResource("resources/shell.js")); 410 context.eval(global, source.getString(), global, "<shell.js>", false); 411 } catch (final Exception e) { 412 err.println(e); 413 if (env._dump_on_error) { 414 e.printStackTrace(err); 415 } 416 417 return INTERNAL_ERROR; 418 } 419 420 while (true) { 421 err.print(prompt); 422 err.flush(); 423 424 String source = ""; 425 try { 426 source = in.readLine(); 427 } catch (final IOException ioe) { 428 err.println(ioe.toString()); 429 } 430 431 if (source == null) { 432 break; 433 } 434 435 if (source.isEmpty()) { 436 continue; 437 } 438 439 Object res; 440 try { 441 res = context.eval(global, source, global, "<shell>", env._strict); 442 } catch (final Exception e) { 443 err.println(e); 444 if (env._dump_on_error) { 445 e.printStackTrace(err); 446 } 447 continue; 448 } 449 450 if (res != ScriptRuntime.UNDEFINED) { 451 err.println(JSType.toString(res)); 452 } 453 } 454 } finally { 455 if (globalChanged) { 456 Context.setGlobal(oldGlobal); 457 } 458 } 459 460 return SUCCESS; 461 } 462 }