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