Shell.java revision 1350:3cb11f4d617e
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
26package jdk.nashorn.tools;
27
28import static jdk.nashorn.internal.runtime.Source.sourceFor;
29
30import java.io.BufferedReader;
31import java.io.File;
32import java.io.FileReader;
33import java.io.IOException;
34import java.io.InputStream;
35import java.io.InputStreamReader;
36import java.io.OutputStream;
37import java.io.PrintStream;
38import java.io.PrintWriter;
39import java.util.List;
40import java.util.Locale;
41import java.util.ResourceBundle;
42import jdk.nashorn.api.scripting.NashornException;
43import jdk.nashorn.internal.codegen.Compiler;
44import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
45import jdk.nashorn.internal.ir.FunctionNode;
46import jdk.nashorn.internal.ir.debug.ASTWriter;
47import jdk.nashorn.internal.ir.debug.PrintVisitor;
48import jdk.nashorn.internal.objects.Global;
49import jdk.nashorn.internal.parser.Parser;
50import jdk.nashorn.internal.runtime.Context;
51import jdk.nashorn.internal.runtime.ErrorManager;
52import jdk.nashorn.internal.runtime.JSType;
53import jdk.nashorn.internal.runtime.Property;
54import jdk.nashorn.internal.runtime.ScriptEnvironment;
55import jdk.nashorn.internal.runtime.ScriptFunction;
56import jdk.nashorn.internal.runtime.ScriptRuntime;
57import jdk.nashorn.internal.runtime.options.Options;
58
59/**
60 * Command line Shell for processing JavaScript files.
61 */
62public 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            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    private static 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}
461