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