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 }
--- EOF ---