Shell.java revision 1381:5b0c3dc04a73
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.Expression;
47import jdk.nashorn.internal.ir.debug.ASTWriter;
48import jdk.nashorn.internal.ir.debug.PrintVisitor;
49import jdk.nashorn.internal.objects.Global;
50import jdk.nashorn.internal.parser.Parser;
51import jdk.nashorn.internal.runtime.Context;
52import jdk.nashorn.internal.runtime.ErrorManager;
53import jdk.nashorn.internal.runtime.JSType;
54import jdk.nashorn.internal.runtime.Property;
55import jdk.nashorn.internal.runtime.ScriptEnvironment;
56import jdk.nashorn.internal.runtime.ScriptFunction;
57import jdk.nashorn.internal.runtime.ScriptRuntime;
58import jdk.nashorn.internal.runtime.options.Options;
59
60/**
61 * Command line Shell for processing JavaScript files.
62 */
63public class Shell implements PartialParser {
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    protected 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            final int exitCode = main(System.in, System.out, System.err, args);
113            if (exitCode != SUCCESS) {
114                System.exit(exitCode);
115            }
116        } catch (final IOException e) {
117            System.err.println(e); //bootstrapping, Context.err may not exist
118            System.exit(IO_ERROR);
119        }
120    }
121
122    /**
123     * Starting point for executing a {@code Shell}. Starts a shell with the
124     * given arguments and streams and lets it run until exit.
125     *
126     * @param in input stream for Shell
127     * @param out output stream for Shell
128     * @param err error stream for Shell
129     * @param args arguments to Shell
130     *
131     * @return exit code
132     *
133     * @throws IOException if there's a problem setting up the streams
134     */
135    public static int main(final InputStream in, final OutputStream out, final OutputStream err, final String[] args) throws IOException {
136        return new Shell().run(in, out, err, args);
137    }
138
139    /**
140     * Run method logic.
141     *
142     * @param in input stream for Shell
143     * @param out output stream for Shell
144     * @param err error stream for Shell
145     * @param args arguments to Shell
146     *
147     * @return exit code
148     *
149     * @throws IOException if there's a problem setting up the streams
150     */
151    protected final int run(final InputStream in, final OutputStream out, final OutputStream err, final String[] args) throws IOException {
152        final Context context = makeContext(in, out, err, args);
153        if (context == null) {
154            return COMMANDLINE_ERROR;
155        }
156
157        final Global global = context.createGlobal();
158        final ScriptEnvironment env = context.getEnv();
159        final List<String> files = env.getFiles();
160        if (files.isEmpty()) {
161            return readEvalPrint(context, global);
162        }
163
164        if (env._compile_only) {
165            return compileScripts(context, global, files);
166        }
167
168        if (env._fx) {
169            return runFXScripts(context, global, files);
170        }
171
172        return runScripts(context, global, files);
173    }
174
175    /**
176     * Make a new Nashorn Context to compile and/or run JavaScript files.
177     *
178     * @param in input stream for Shell
179     * @param out output stream for Shell
180     * @param err error stream for Shell
181     * @param args arguments to Shell
182     *
183     * @return null if there are problems with option parsing.
184     */
185    private static Context makeContext(final InputStream in, final OutputStream out, final OutputStream err, final String[] args) {
186        final PrintStream pout = out instanceof PrintStream ? (PrintStream) out : new PrintStream(out);
187        final PrintStream perr = err instanceof PrintStream ? (PrintStream) err : new PrintStream(err);
188        final PrintWriter wout = new PrintWriter(pout, true);
189        final PrintWriter werr = new PrintWriter(perr, true);
190
191        // Set up error handler.
192        final ErrorManager errors = new ErrorManager(werr);
193        // Set up options.
194        final Options options = new Options("nashorn", werr);
195
196        // parse options
197        if (args != null) {
198            try {
199                options.process(args);
200            } catch (final IllegalArgumentException e) {
201                werr.println(bundle.getString("shell.usage"));
202                options.displayHelp(e);
203                return null;
204            }
205        }
206
207        // detect scripting mode by any source's first character being '#'
208        if (!options.getBoolean("scripting")) {
209            for (final String fileName : options.getFiles()) {
210                final File firstFile = new File(fileName);
211                if (firstFile.isFile()) {
212                    try (final FileReader fr = new FileReader(firstFile)) {
213                        final int firstChar = fr.read();
214                        // starts with '#
215                        if (firstChar == '#') {
216                            options.set("scripting", true);
217                            break;
218                        }
219                    } catch (final IOException e) {
220                        // ignore this. File IO errors will be reported later anyway
221                    }
222                }
223            }
224        }
225
226        return new Context(options, errors, wout, werr, Thread.currentThread().getContextClassLoader());
227    }
228
229    /**
230     * Compiles the given script files in the command line
231     * This is called only when using the --compile-only flag
232     *
233     * @param context the nashorn context
234     * @param global the global scope
235     * @param files the list of script files to compile
236     *
237     * @return error code
238     * @throws IOException when any script file read results in I/O error
239     */
240    private static int compileScripts(final Context context, final Global global, final List<String> files) throws IOException {
241        final Global oldGlobal = Context.getGlobal();
242        final boolean globalChanged = (oldGlobal != global);
243        final ScriptEnvironment env = context.getEnv();
244        try {
245            if (globalChanged) {
246                Context.setGlobal(global);
247            }
248            final ErrorManager errors = context.getErrorManager();
249
250            // For each file on the command line.
251            for (final String fileName : files) {
252                final FunctionNode functionNode = new Parser(env, sourceFor(fileName, new File(fileName)), errors, env._strict, 0, context.getLogger(Parser.class)).parse();
253
254                if (errors.getNumberOfErrors() != 0) {
255                    return COMPILATION_ERROR;
256                }
257
258                new Compiler(
259                       context,
260                       env,
261                       null, //null - pass no code installer - this is compile only
262                       functionNode.getSource(),
263                       context.getErrorManager(),
264                       env._strict | functionNode.isStrict()).
265                       compile(functionNode, CompilationPhases.COMPILE_ALL_NO_INSTALL);
266
267                if (env._print_ast) {
268                    context.getErr().println(new ASTWriter(functionNode));
269                }
270
271                if (env._print_parse) {
272                    context.getErr().println(new PrintVisitor(functionNode));
273                }
274
275                if (errors.getNumberOfErrors() != 0) {
276                    return COMPILATION_ERROR;
277                }
278            }
279        } finally {
280            env.getOut().flush();
281            env.getErr().flush();
282            if (globalChanged) {
283                Context.setGlobal(oldGlobal);
284            }
285        }
286
287        return SUCCESS;
288    }
289
290    /**
291     * Runs the given JavaScript files in the command line
292     *
293     * @param context the nashorn context
294     * @param global the global scope
295     * @param files the list of script files to run
296     *
297     * @return error code
298     * @throws IOException when any script file read results in I/O error
299     */
300    private int runScripts(final Context context, final Global global, final List<String> files) throws IOException {
301        final Global oldGlobal = Context.getGlobal();
302        final boolean globalChanged = (oldGlobal != global);
303        try {
304            if (globalChanged) {
305                Context.setGlobal(global);
306            }
307            final ErrorManager errors = context.getErrorManager();
308
309            // For each file on the command line.
310            for (final String fileName : files) {
311                if ("-".equals(fileName)) {
312                    final int res = readEvalPrint(context, global);
313                    if (res != SUCCESS) {
314                        return res;
315                    }
316                    continue;
317                }
318
319                final File file = new File(fileName);
320                final ScriptFunction script = context.compileScript(sourceFor(fileName, file), global);
321                if (script == null || errors.getNumberOfErrors() != 0) {
322                    return COMPILATION_ERROR;
323                }
324
325                try {
326                    apply(script, global);
327                } catch (final NashornException e) {
328                    errors.error(e.toString());
329                    if (context.getEnv()._dump_on_error) {
330                        e.printStackTrace(context.getErr());
331                    }
332
333                    return RUNTIME_ERROR;
334                }
335            }
336        } finally {
337            context.getOut().flush();
338            context.getErr().flush();
339            if (globalChanged) {
340                Context.setGlobal(oldGlobal);
341            }
342        }
343
344        return SUCCESS;
345    }
346
347    /**
348     * Runs launches "fx:bootstrap.js" with the given JavaScript files provided
349     * as arguments.
350     *
351     * @param context the nashorn context
352     * @param global the global scope
353     * @param files the list of script files to provide
354     *
355     * @return error code
356     * @throws IOException when any script file read results in I/O error
357     */
358    private static int runFXScripts(final Context context, final Global global, final List<String> files) throws IOException {
359        final Global oldGlobal = Context.getGlobal();
360        final boolean globalChanged = (oldGlobal != global);
361        try {
362            if (globalChanged) {
363                Context.setGlobal(global);
364            }
365
366            global.addOwnProperty("$GLOBAL", Property.NOT_ENUMERABLE, global);
367            global.addOwnProperty("$SCRIPTS", Property.NOT_ENUMERABLE, files);
368            context.load(global, "fx:bootstrap.js");
369        } catch (final NashornException e) {
370            context.getErrorManager().error(e.toString());
371            if (context.getEnv()._dump_on_error) {
372                e.printStackTrace(context.getErr());
373            }
374
375            return RUNTIME_ERROR;
376        } finally {
377            context.getOut().flush();
378            context.getErr().flush();
379            if (globalChanged) {
380                Context.setGlobal(oldGlobal);
381            }
382        }
383
384        return SUCCESS;
385    }
386
387    /**
388     * Hook to ScriptFunction "apply". A performance metering shell may
389     * introduce enter/exit timing here.
390     *
391     * @param target target function for apply
392     * @param self self reference for apply
393     *
394     * @return result of the function apply
395     */
396    protected Object apply(final ScriptFunction target, final Object self) {
397        return ScriptRuntime.apply(target, self);
398    }
399
400    /**
401     * Parse potentially partial code and keep track of the start of last expression.
402     * This 'partial' parsing support is meant to be used for code-completion.
403     *
404     * @param context the nashorn context
405     * @param code code that is to be parsed
406     * @return the start index of the last expression parsed in the (incomplete) code.
407     */
408    @Override
409    public final int getLastExpressionStart(final Context context, final String code) {
410        final int[] exprStart = { -1 };
411
412        final Parser p = new Parser(context.getEnv(), sourceFor("<partial_code>", code),new Context.ThrowErrorManager()) {
413            @Override
414            protected Expression expression() {
415                exprStart[0] = this.start;
416                return super.expression();
417            }
418
419            @Override
420            protected Expression assignmentExpression(final boolean noIn) {
421                exprStart[0] = this.start;
422                return super.expression();
423            }
424        };
425
426        try {
427            p.parse();
428        } catch (final Exception ignored) {
429            // throw any parser exception, but we are partial parsing anyway
430        }
431
432        return exprStart[0];
433    }
434
435
436    /**
437     * read-eval-print loop for Nashorn shell.
438     *
439     * @param context the nashorn context
440     * @param global  global scope object to use
441     * @return return code
442     */
443    protected int readEvalPrint(final Context context, final Global global) {
444        final String prompt = bundle.getString("shell.prompt");
445        final BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
446        final PrintWriter err = context.getErr();
447        final Global oldGlobal = Context.getGlobal();
448        final boolean globalChanged = (oldGlobal != global);
449        final ScriptEnvironment env = context.getEnv();
450
451        try {
452            if (globalChanged) {
453                Context.setGlobal(global);
454            }
455
456            global.addShellBuiltins();
457
458            while (true) {
459                err.print(prompt);
460                err.flush();
461
462                String source = "";
463                try {
464                    source = in.readLine();
465                } catch (final IOException ioe) {
466                    err.println(ioe.toString());
467                }
468
469                if (source == null) {
470                    break;
471                }
472
473                if (source.isEmpty()) {
474                    continue;
475                }
476
477                try {
478                    final Object res = context.eval(global, source, global, "<shell>");
479                    if (res != ScriptRuntime.UNDEFINED) {
480                        err.println(JSType.toString(res));
481                    }
482                } catch (final Exception e) {
483                    err.println(e);
484                    if (env._dump_on_error) {
485                        e.printStackTrace(err);
486                    }
487                }
488            }
489        } finally {
490            if (globalChanged) {
491                Context.setGlobal(oldGlobal);
492            }
493        }
494
495        return SUCCESS;
496    }
497}
498