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