/** * Copyright (c) 2012-2016 André Bargull * Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms. * * <https://github.com/anba/es6draft> */ package com.github.anba.es6draft.repl; import static com.github.anba.es6draft.runtime.AbstractOperations.CreateArrayFromList; import static com.github.anba.es6draft.runtime.AbstractOperations.CreateMethodProperty; import static com.github.anba.es6draft.runtime.Realm.InitializeHostDefinedRealm; import static com.github.anba.es6draft.runtime.internal.Errors.newInternalError; import static com.github.anba.es6draft.runtime.modules.ModuleSemantics.ModuleEvaluationJob; import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED; import java.io.*; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; import java.util.Enumeration; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.ResourceBundle; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Option; import org.kohsuke.args4j.OptionDef; import org.kohsuke.args4j.OptionHandlerFilter; import org.kohsuke.args4j.ParserProperties; import org.kohsuke.args4j.spi.OneArgumentOptionHandler; import org.kohsuke.args4j.spi.OptionHandler; import org.kohsuke.args4j.spi.Parameters; import org.kohsuke.args4j.spi.RestOfArgumentsHandler; import org.kohsuke.args4j.spi.Setter; import org.kohsuke.args4j.spi.StopOptionHandler; import com.github.anba.es6draft.Script; import com.github.anba.es6draft.compiler.Compiler; import com.github.anba.es6draft.parser.Characters; import com.github.anba.es6draft.parser.Parser; import com.github.anba.es6draft.parser.ParserEOFException; import com.github.anba.es6draft.parser.ParserException; import com.github.anba.es6draft.repl.console.JLineConsole; import com.github.anba.es6draft.repl.console.LegacyConsole; import com.github.anba.es6draft.repl.console.NativeConsole; import com.github.anba.es6draft.repl.console.ShellConsole; import com.github.anba.es6draft.repl.global.ConsoleObject; import com.github.anba.es6draft.repl.global.MozShellGlobalObject; import com.github.anba.es6draft.repl.global.ShellGlobalObject; import com.github.anba.es6draft.repl.global.SimpleShellGlobalObject; import com.github.anba.es6draft.repl.global.StopExecutionException; import com.github.anba.es6draft.repl.global.StopExecutionException.Reason; import com.github.anba.es6draft.repl.global.V8ShellGlobalObject; import com.github.anba.es6draft.repl.loader.NodeModuleLoader; import com.github.anba.es6draft.repl.loader.NodeStandardModuleLoader; import com.github.anba.es6draft.runtime.ExecutionContext; import com.github.anba.es6draft.runtime.Realm; import com.github.anba.es6draft.runtime.Task; import com.github.anba.es6draft.runtime.World; import com.github.anba.es6draft.runtime.extensions.timer.Timers; import com.github.anba.es6draft.runtime.internal.*; import com.github.anba.es6draft.runtime.modules.MalformedNameException; import com.github.anba.es6draft.runtime.modules.ModuleLoader; import com.github.anba.es6draft.runtime.modules.ModuleSource; import com.github.anba.es6draft.runtime.modules.ResolutionException; import com.github.anba.es6draft.runtime.modules.SourceIdentifier; import com.github.anba.es6draft.runtime.modules.loader.FileModuleLoader; import com.github.anba.es6draft.runtime.types.ScriptObject; /** * Simple REPL */ public final class Repl { private static final String BUNDLE_NAME = "com.github.anba.es6draft.repl.messages"; private static final String PROGRAM_NAME = "es6draft"; private static final String PROMPT = "js> "; private static final int STACKTRACE_DEPTH = 20; public static void main(String[] args) throws Throwable { Options options = new Options(); try { parseOptions(options, args); ShellConsole console = createConsole(options); new Repl(console, options).loop(); } catch (Throwable e) { printStackTrace(System.err, e, options.stacktraceDepth); System.exit(1); } } private static ShellConsole createConsole(Options options) throws IOException { ShellConsole console; if (!options.noJLine) { console = new JLineConsole(PROGRAM_NAME); } else if (System.console() != null) { console = new NativeConsole(); } else { console = new LegacyConsole(); } return console; } private void printStackTrace(Throwable e) { if (options.stacktrace) { printStackTrace(console.writer(), e, options.stacktraceDepth); } } private static void printStackTrace(PrintStream stream, Throwable e, int maxDepth) { final int depth = Math.max(maxDepth, 0); new TruncatedThrowable(e, depth).printStackTrace(stream); } private static void printStackTrace(PrintWriter writer, Throwable e, int maxDepth) { final int depth = Math.max(maxDepth, 0); new TruncatedThrowable(e, depth).printStackTrace(writer); } @SuppressWarnings("serial") private static final class TruncatedThrowable extends Throwable { private static final boolean REMOVE_PACKAGE_NAME = System.console() != null; private static final String PACKAGE_NAME = "com.github.anba.es6draft."; private final Throwable throwable; TruncatedThrowable(Throwable throwable, int depth) { super(); this.throwable = throwable; setStackTrace(truncate(throwable, depth)); if (throwable.getCause() != null) { initCause(new TruncatedThrowable(throwable.getCause(), depth)); } for (Throwable suppressed : throwable.getSuppressed()) { addSuppressed(new TruncatedThrowable(suppressed, depth)); } } static StackTraceElement[] truncate(Throwable e, int depth) { StackTraceElement[] stackTrace = e.getStackTrace(); if (stackTrace.length > depth) { int omitted = stackTrace.length - depth; stackTrace = Arrays.copyOf(stackTrace, depth + 1); stackTrace[depth] = new StackTraceElement("..", "", "Frames omitted", omitted); } if (REMOVE_PACKAGE_NAME) { for (int i = 0; i < stackTrace.length; ++i) { StackTraceElement element = stackTrace[i]; String className = element.getClassName(); if (className.startsWith(PACKAGE_NAME)) { stackTrace[i] = new StackTraceElement(className.substring(PACKAGE_NAME.length()), element.getMethodName(), element.getFileName(), element.getLineNumber()); } } } return stackTrace; } @Override public String toString() { return throwable.toString(); } @Override public Throwable fillInStackTrace() { // empty return this; } } private void printScriptStackTrace(Realm realm, ScriptException e) { if (options.scriptStacktrace) { StringBuilder sb = new StringBuilder(); printScriptFrames(sb, realm, e, 1); if (sb.length() == 0 && e.getCause() != null) { printScriptFrames(sb, realm, e.getCause(), 1); } console.writer().print(sb.toString()); } } private void printScriptFrames(StringBuilder sb, Realm realm, Throwable e, int level) { final String indent = Strings.repeat('\t', level); final int maxDepth = options.stacktraceDepth; int depth = 0; StackTraceElement[] stackTrace = StackTraces.scriptStackTrace(e); for (; depth < Math.min(stackTrace.length, maxDepth); ++depth) { StackTraceElement element = stackTrace[depth]; String methodName = element.getMethodName(); String fileName = element.getFileName(); int lineNumber = element.getLineNumber(); sb.append(indent).append("at ").append(methodName).append(" (").append(fileName).append(':') .append(lineNumber).append(")\n"); } if (depth < stackTrace.length) { int skipped = stackTrace.length - depth; sb.append("\t.. ").append(skipped).append(" frames omitted\n"); } if (e.getSuppressed().length > 0 && level == 1) { Throwable suppressed = e.getSuppressed()[0]; String message; if (suppressed instanceof ScriptException) { message = ((ScriptException) suppressed).getMessage(realm.defaultContext()); } else { message = Objects.toString(suppressed.getMessage(), suppressed.getClass().getSimpleName()); } sb.append(indent).append(formatMessage("suppressed_exception", message)).append('\n'); printScriptFrames(sb, realm, suppressed, level + 1); } } private static void parseOptions(Options options, String[] args) { ParserProperties properties = ParserProperties.defaults().withUsageWidth(128); CmdLineParser parser = new CmdLineParser(options, properties); try { parser.parseArgument(args); } catch (CmdLineException e) { System.err.println(e.getMessage()); System.err.println(getUsageString(parser, false)); System.exit(1); } if (options.showVersion) { System.out.println(getVersionString()); System.exit(0); } if (options.showHelp) { System.out.println(getUsageString(parser, false)); System.exit(0); } if (options.showExtendedHelp) { System.out.println(getUsageString(parser, true)); System.exit(0); } if (options.debug || options.fullDebug || options.debugInfo) { // Disable interpreter when bytecode is requested options.noInterpreter = true; } if (options.fileName != null) { // Execute as last script if (options.fileName.toString().equals("-")) { // "-" is a short-hand to request reading from System.in if (System.console() == null) { // System.in is not interactive options.evalScripts.add(new EvalString(read(System.in))); } else { options.interactive = true; } } else { options.evalScripts.add(new EvalPath(options.fileName)); } } if (options.evalScripts.isEmpty()) { // Default to interactive mode when no files or expressions were set options.interactive = true; // Warn if --module is used without input files. if (options.module) { System.err.println(formatMessage("module_no_files")); } } if (options.ecmascript7) { System.err.println(formatMessage("deprecated.es7")); } } private interface EvalScript { Source getSource(); String getSourceCode() throws IOException; SourceIdentifier getModuleName(); ModuleSource getModuleSource() throws IOException; } private static final class EvalString implements EvalScript { private static final AtomicInteger moduleIds = new AtomicInteger(0); private final String sourceCode; EvalString(String sourceCode) { this.sourceCode = sourceCode; } @Override public Source getSource() { return new Source(Paths.get(".").toAbsolutePath(), "<eval>", 1); } @Override public String getSourceCode() { return sourceCode; } @Override public SourceIdentifier getModuleName() { return new EvalSourceIdentifier("eval-module-" + moduleIds.incrementAndGet(), URI.create("")); } @Override public ModuleSource getModuleSource() { return new EvalModuleSource(getSourceCode(), getSource()); } } private static final class EvalPath implements EvalScript { private final Path path; EvalPath(Path path) { this.path = path; } @Override public Source getSource() { return new Source(path.toAbsolutePath(), path.toString(), 1); } @Override public String getSourceCode() throws IOException { Path filePath = path.toAbsolutePath(); if (!Files.exists(filePath)) { String message = formatMessage("file_not_found", filePath.toString()); throw new FileNotFoundException(message); } byte[] content = Files.readAllBytes(filePath); return new String(content, StandardCharsets.UTF_8); } @Override public SourceIdentifier getModuleName() { URI file = Paths.get("").toAbsolutePath().toUri().relativize(path.toUri()); return new EvalSourceIdentifier(file.toString(), file); } @Override public ModuleSource getModuleSource() throws IOException { return new EvalModuleSource(getSourceCode(), getSource()); } } private static final class EvalSourceIdentifier implements SourceIdentifier { private final String name; private final URI uri; EvalSourceIdentifier(String name, URI uri) { this.name = name; this.uri = uri; } @Override public boolean equals(Object obj) { if (obj instanceof EvalSourceIdentifier) { return name.equals(((EvalSourceIdentifier) obj).name); } if (obj instanceof SourceIdentifier) { return uri.equals(((SourceIdentifier) obj).toUri()); } return false; } @Override public int hashCode() { return name.hashCode(); } @Override public String toString() { return name; } @Override public URI toUri() { return uri; } } private static final class EvalModuleSource implements ModuleSource { private final String sourceCode; private final Source source; public EvalModuleSource(String sourceCode, Source source) { this.sourceCode = sourceCode; this.source = source; } @Override public String sourceCode() { return sourceCode; } @Override public Source toSource() { return source; } } public enum ShellMode { // TODO: "simple" is a misnomer, change to "default"? Simple, Mozilla, V8; @Override public String toString() { return name().toLowerCase(Locale.ROOT); } } public enum ModuleLoaderMode { Default, Node, NodeStandard; @Override public String toString() { return name().toLowerCase(Locale.ROOT); } } public static final class Options { ArrayList<EvalScript> evalScripts = new ArrayList<>(); @Option(name = "-v", aliases = { "--version" }, usage = "options.version") boolean showVersion; @Option(name = "-h", aliases = { "--help" }, help = true, usage = "options.help") boolean showHelp; @Option(name = "-e", aliases = { "--eval", "--execute" }, metaVar = "meta.string", usage = "options.eval") void setEvalExpression(String expression) { evalScripts.add(new EvalString(expression)); } @Option(name = "-f", aliases = { "--file" }, metaVar = "meta.file", usage = "options.file") void setFile(Path path) { evalScripts.add(new EvalPath(path)); } @Option(name = "-i", aliases = { "--interactive" }, usage = "options.interactive") boolean interactive; @Option(name = "--module", usage = "options.module") boolean module; @Option(name = "--module-loader", usage = "options.module_loader") ModuleLoaderMode moduleLoaderMode = ModuleLoaderMode.Default; @Option(name = "--strict", usage = "options.strict") boolean strict; @Option(name = "--shell", usage = "options.shell") ShellMode shellMode = ShellMode.Simple; @Option(name = "--es7", usage = "options.es7") boolean ecmascript7; @Option(name = "--stage", usage = "options.stage", handler = RangeIntOptionHandler.class) @RangeIntOptionHandler.Range(min = 0, max = 4) int stage = 3; @Option(name = "--experimental", usage = "options.experimental") boolean experimental; @Option(name = "--async", usage = "options.async") boolean asyncFunctions; @Option(name = "--parser", usage = "options.parser") boolean parser; @Option(name = "--timers", usage = "options.timers") boolean timers; @Option(name = "--console", usage = "options.console") boolean console; @Option(name = "--no-jline", usage = "options.no_jline") boolean noJLine; @Option(name = "--no-color", usage = "options.no_color") boolean noColor; @Option(name = "--no-interpreter", aliases = { "--compile-only" }, usage = "options.no_interpreter") boolean noInterpreter; @Option(name = "--stacktrace", usage = "options.stacktrace") boolean stacktrace; @Option(name = "--script-stacktrace", usage = "options.script_stacktrace") boolean scriptStacktrace; @Option(name = "--stacktrace-depth", hidden = true, usage = "options.stacktrace_depth") int stacktraceDepth = STACKTRACE_DEPTH; @Option(name = "--debug", usage = "options.debug") boolean debug; @Option(name = "--full-debug", hidden = true, usage = "options.full_debug") boolean fullDebug; @Option(name = "--debug-info", hidden = true, usage = "options.debug_info") boolean debugInfo; @Option(name = "--no-tailcall", hidden = true, usage = "options.no_tailcall") boolean noTailCall; @Option(name = "--native-calls", hidden = true, usage = "options.native_calls") boolean nativeCalls; @Option(name = "--promise-rejection", hidden = true, usage = "options.promise_rejection") boolean promiseRejection; @Option(name = "--xhelp", help = true, hidden = true, usage = "options.extended_help") boolean showExtendedHelp; @Option(name = "-", handler = StopOptionAndRepeatHandler.class) @Argument(index = 0, multiValued = false, metaVar = "meta.file", usage = "options.filename") Path fileName = null; @Option(name = "--", handler = StopOptionAndConsumeRestHandler.class) @Argument(index = 1, multiValued = true, metaVar = "meta.arguments", usage = "options.arguments", handler = RestOfArgumentsHandler.class) List<String> arguments = new ArrayList<>(); } public static final class RangeIntOptionHandler extends OneArgumentOptionHandler<Integer> { public RangeIntOptionHandler(CmdLineParser parser, OptionDef option, Setter<? super Integer> setter) { super(parser, option, setter); } @Target(value = { ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface Range { int min() default Integer.MIN_VALUE; int max() default Integer.MAX_VALUE; } @Override protected Integer parse(String argument) throws NumberFormatException, CmdLineException { int value = Integer.parseInt(argument); Range range = setter.asAnnotatedElement().getAnnotation(Range.class); if (range != null && value != Math.min(Math.max(value, range.min()), range.max())) { throw new NumberFormatException(); } return value; } } public static final class StopOptionAndConsumeRestHandler extends OptionHandler<String> { private final StopOptionHandler stopOptionHandler; private final RestOfArgumentsHandler restOfArgumentsHandler; public StopOptionAndConsumeRestHandler(CmdLineParser parser, OptionDef option, Setter<String> setter) { super(parser, option, setter); this.stopOptionHandler = new StopOptionHandler(parser, option, setter); this.restOfArgumentsHandler = new RestOfArgumentsHandler(parser, option, setter); } @Override public int parseArguments(Parameters params) throws CmdLineException { return stopOptionHandler.parseArguments(params) + restOfArgumentsHandler.parseArguments(params); } @Override public String getDefaultMetaVariable() { return "ARGUMENTS"; } } public static final class StopOptionAndRepeatHandler extends OptionHandler<String> { private final StopOptionHandler stopOptionHandler; public StopOptionAndRepeatHandler(CmdLineParser parser, OptionDef option, Setter<String> setter) { super(parser, option, setter); this.stopOptionHandler = new StopOptionHandler(parser, option, setter); } @Override public int parseArguments(Parameters params) throws CmdLineException { stopOptionHandler.parseArguments(params); return -1; } @Override public String getDefaultMetaVariable() { return ""; } } private static String getUsageString(CmdLineParser parser, boolean showAll) { ResourceBundle rb = getResourceBundle(); StringWriter writer = new StringWriter(); writer.write(formatMessage(rb, "usage", getVersionString(), PROGRAM_NAME)); parser.printUsage(writer, rb, showAll ? OptionHandlerFilter.ALL : OptionHandlerFilter.PUBLIC); return writer.toString(); } private static ResourceBundle getResourceBundle() { ResourceBundle.Control control = new PropertiesReaderControl(StandardCharsets.UTF_8); return new XResourceBundle(ResourceBundle.getBundle(BUNDLE_NAME, Locale.getDefault(), control)); } private static final class XResourceBundle extends ResourceBundle { private final ResourceBundle bundle; XResourceBundle(ResourceBundle bundle) { this.bundle = bundle; } @Override protected Object handleGetObject(String key) { if (bundle.containsKey(key)) { return bundle.getObject(key); } // TODO: Workarounds for https://github.com/kohsuke/args4j/issues/70 // and https://github.com/kohsuke/args4j/issues/71 return key; } @Override public Enumeration<String> getKeys() { return bundle.getKeys(); } @Override public Locale getLocale() { return bundle.getLocale(); } @Override public boolean containsKey(String key) { return bundle.containsKey(key); } @Override public Set<String> keySet() { return bundle.keySet(); } } private static String read(InputStream in) { try (Reader reader = new BufferedReader(new InputStreamReader(in, Charset.defaultCharset()))) { StringBuilder sb = new StringBuilder(4096); char cbuf[] = new char[4096]; for (int len; (len = reader.read(cbuf)) != -1;) { sb.append(cbuf, 0, len); } return sb.toString(); } catch (IOException e) { System.err.println(e); return ""; } } private static String formatMessage(String key, String... messageArguments) { return formatMessage(getResourceBundle(), key, messageArguments); } private static String formatMessage(ResourceBundle rb, String key, String... messageArguments) { return new MessageFormat(rb.getString(key), rb.getLocale()).format(messageArguments); } private static String getVersionString() { return getResourceInfo("/version", PROGRAM_NAME); } private static String getResourceInfo(String resourceName, String defaultValue) { try (BufferedReader reader = new BufferedReader( new InputStreamReader(Repl.class.getResourceAsStream(resourceName), StandardCharsets.UTF_8))) { return reader.readLine(); } catch (IOException e) { return defaultValue; } } @SuppressWarnings("serial") private static final class ParserExceptionWithSource extends RuntimeException { private final Source source; private final String sourceCode; ParserExceptionWithSource(ParserException e, Source source, String sourceCode) { super(e); this.source = source; this.sourceCode = sourceCode; } @Override public ParserException getCause() { return (ParserException) super.getCause(); } public Source getSource() { return source; } public String getSourceCode() { return sourceCode; } } private final ShellConsole console; private final Options options; private final SourceBuilder sourceBuilder; private final AtomicInteger scriptCounter = new AtomicInteger(0); private Repl(ShellConsole console, Options options) { this.console = console; this.options = options; this.sourceBuilder = new SourceBuilder(console.isAnsiSupported() && !options.noColor, 10, 30, 80); } private void handleException(Throwable e) { String message = Objects.toString(e.getMessage(), e.getClass().getSimpleName()); console.printf("%s%n", message); printStackTrace(e); } private void handleException(IOException e) { String message = Objects.toString(e.getMessage(), ""); console.printf("%s: %s%n", e.getClass().getSimpleName(), message); printStackTrace(e); } private void handleException(Realm realm, OutOfMemoryError e) { // Try to recover after OOM. Runtime rt = Runtime.getRuntime(); long beforeGc = rt.freeMemory(); rt.gc(); long afterGc = rt.freeMemory(); if (afterGc > beforeGc && (afterGc - beforeGc) < 50_000_000) { // Calling gc() cleared less than 50MB, assume unrecoverable OOM and rethrow error. throw e; } // Create script exception with stacktrace from oom-error. ScriptException exception = newInternalError(realm.defaultContext(), Messages.Key.OutOfMemoryVM); exception.setStackTrace(e.getStackTrace()); handleException(realm, exception); } private void handleException(Realm realm, StackOverflowError e) { // Create script exception with stacktrace from stackoverflow-error. ScriptException exception = newInternalError(realm.defaultContext(), Messages.Key.StackOverflow); exception.setStackTrace(e.getStackTrace()); handleException(realm, exception); } private void handleException(Realm realm, ScriptException e) { String message = formatMessage("uncaught_exception", e.getMessage(realm.defaultContext())); console.printf("%s%n", message); printScriptStackTrace(realm, e); printStackTrace(e); } private void handleException(Realm realm, UnhandledRejectionException e) { String message = formatMessage("unhandled_rejection", e.getMessage(realm.defaultContext())); console.printf("%s%n", message); printStackTrace(e.getCauseIfPresent()); } private void handleException(ParserExceptionWithSource exception) { ParserException e = exception.getCause(); String sourceCode = exception.getSourceCode(); int lineOffset = exception.getSource().getLine(); String sourceInfo = String.format("%s:%d:%d", e.getFile(), e.getLine(), e.getColumn()); int start = skipLines(sourceCode, e.getLine() - lineOffset); int end = nextLineTerminator(sourceCode, start); String offendingLine = sourceCode.substring(start, end); String marker = Strings.repeat('.', Math.max(e.getColumn() - 1, 0)) + '^'; console.printf("%s %s: %s%n", sourceInfo, e.getType(), e.getFormattedMessage()); console.printf("%s %s%n", sourceInfo, offendingLine); console.printf("%s %s%n", sourceInfo, marker); printStackTrace(e); } private static int skipLines(String s, int n) { int index = 0; for (int length = s.length(); n > 0; --n) { int lineEnd = nextLineTerminator(s, index); if (lineEnd + 1 < length && s.charAt(lineEnd) == '\r' && s.charAt(lineEnd + 1) == '\n') { index = lineEnd + 2; } else { index = lineEnd + 1; } } return index; } private static int nextLineTerminator(String s, int index) { for (int length = s.length(); index < length && !Characters.isLineTerminator(s.charAt(index)); ++index) ; return index; } private static com.github.anba.es6draft.ast.Script parse(Realm realm, Source source, String sourceCode) throws ParserException { return realm.getScriptLoader().parseScript(source, sourceCode); } /** * REPL: Read * * @param realm * the realm instance * @param line * the current line * @return the parsed script node or {@coden null} if the end of stream has been reached */ private com.github.anba.es6draft.ast.Script read(Realm realm, int[] line) { StringBuilder sourceBuffer = new StringBuilder(); for (String prompt = PROMPT;; prompt = "") { String s = console.readLine(prompt); if (s == null) { return null; } sourceBuffer.append(s).append('\n'); String sourceCode = sourceBuffer.toString(); Source source = new Source(Paths.get(".").toAbsolutePath(), "typein", line[0]); try { com.github.anba.es6draft.ast.Script script = parse(realm, source, sourceCode); line[0] += script.getEndLine() - script.getBeginLine(); return script; } catch (ParserEOFException e) { continue; } catch (ParserException e) { throw new ParserExceptionWithSource(e, source, sourceCode); } } } /** * REPL: Eval * * @param realm * the realm instance * @param parsedScript * the parsed script node * @return the evaluated script result */ private Object eval(Realm realm, com.github.anba.es6draft.ast.Script parsedScript) { String className = "#typein_" + scriptCounter.incrementAndGet(); Script script; if (options.noInterpreter) { script = realm.getScriptLoader().compile(parsedScript, className); } else { script = realm.getScriptLoader().load(parsedScript, className); } return script.evaluate(realm); } /** * REPL: Print * * @param realm * the realm instance * @param result * the object to be printed */ private void print(Realm realm, Object result) { if (result != UNDEFINED) { console.printf("%s%n", sourceBuilder.toSource(realm.defaultContext(), result)); } } /** * REPL: Loop */ private void loop() throws InterruptedException { Realm realm = newRealm(); World world = realm.getWorld(); TaskSource taskSource = createTaskSource(realm); for (;;) { try { world.runEventLoop(taskSource); return; } catch (StopExecutionException e) { if (e.getReason() == Reason.Quit) { System.exit(0); } } catch (ParserExceptionWithSource e) { handleException(e); } catch (ScriptException e) { handleException(realm, e); } catch (UnhandledRejectionException e) { handleException(realm, e); } catch (StackOverflowError e) { handleException(realm, e); } catch (OutOfMemoryError e) { handleException(realm, e); } catch (InternalException e) { handleException(e); } catch (BootstrapMethodError e) { handleException(e.getCause()); } catch (UncheckedIOException e) { handleException(e.getCause()); } } } private void errorReporter(ExecutionContext cx, Throwable throwable) { try { throw throwable; } catch (StopExecutionException e) { if (e.getReason() == Reason.Quit) { System.exit(0); } } catch (ScriptException e) { handleException(cx.getRealm(), e); } catch (UnhandledRejectionException e) { handleException(cx.getRealm(), e); } catch (StackOverflowError e) { handleException(cx.getRealm(), e); } catch (OutOfMemoryError e) { handleException(cx.getRealm(), e); } catch (InternalException e) { handleException(e); } catch (BootstrapMethodError e) { handleException(e.getCause()); } catch (UncheckedIOException e) { handleException(e.getCause()); } catch (Throwable e) { printStackTrace(System.err, e, options.stacktraceDepth); System.exit(1); } } private TaskSource createTaskSource(Realm realm) { ArrayList<TaskSource> sources = new ArrayList<>(); if (options.interactive) { sources.add(new InteractiveTaskSource(realm)); } if (options.timers) { sources.add(realm.createGlobalProperties(new Timers(), Timers.class)); } switch (sources.size()) { case 0: return new EmptyTaskSource(); case 1: return sources.get(0); default: return new MultiTaskSource(sources); } } private static final class EmptyTaskSource implements TaskSource { @Override public Task nextTask() { return null; } @Override public Task awaitTask() { throw new IllegalStateException(); } } private final class InteractiveTaskSource implements TaskSource { private final Realm realm; private final int[] line = { 1 }; InteractiveTaskSource(Realm realm) { this.realm = realm; } @Override public Task nextTask() throws InterruptedException { return awaitTask(); } @Override public Task awaitTask() throws InterruptedException { for (;;) { try { com.github.anba.es6draft.ast.Script parsedScript = read(realm, line); if (parsedScript == null) { return null; } if (parsedScript.getStatements().isEmpty()) { continue; } return new EvalPrintTask(realm, parsedScript); } catch (RuntimeException e) { return new ThrowExceptionTask<>(e); } catch (Error e) { return new ThrowErrorTask<>(e); } } } private final class EvalPrintTask implements Task { private final Realm realm; private final com.github.anba.es6draft.ast.Script parsedScript; EvalPrintTask(Realm realm, com.github.anba.es6draft.ast.Script parsedScript) { this.realm = realm; this.parsedScript = parsedScript; } @Override public void execute() { Object result = eval(realm, parsedScript); print(realm, result); } } private final class ThrowErrorTask<E extends Error> implements Task { private final E exception; ThrowErrorTask(E exception) { this.exception = exception; } @Override public void execute() throws E { throw exception; } } private final class ThrowExceptionTask<E extends RuntimeException> implements Task { private final E exception; ThrowExceptionTask(E exception) { this.exception = exception; } @Override public void execute() throws E { throw exception; } } } private static final class MultiTaskSource implements TaskSource { private final SynchronousQueue<Task> queue = new SynchronousQueue<>(); private final Semaphore sem = new Semaphore(-1); MultiTaskSource(List<TaskSource> sources) { ExecutorService service = Executors.newFixedThreadPool(sources.size()); for (TaskSource source : sources) { service.submit(new TaskRunner(source)); } service.shutdown(); } @Override public Task nextTask() throws InterruptedException { return awaitTask(); } @Override public Task awaitTask() throws InterruptedException { sem.release(); return queue.take(); } private class TaskRunner implements Runnable { private final TaskSource source; TaskRunner(TaskSource source) { this.source = source; } @Override public void run() { for (;;) { try { queue.put(source.awaitTask()); sem.acquire(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } catch (Throwable t) { System.err.println("Unexpected exception: " + t.getMessage()); t.printStackTrace(); System.exit(1); } } } } } private Realm newRealm() { ObjectAllocator<? extends ShellGlobalObject> allocator; if (options.shellMode == ShellMode.Mozilla) { allocator = MozShellGlobalObject::new; } else if (options.shellMode == ShellMode.V8) { allocator = V8ShellGlobalObject::new; } else { allocator = SimpleShellGlobalObject::new; } BiFunction<RuntimeContext, ScriptLoader, ModuleLoader> moduleLoader; switch (options.moduleLoaderMode) { case Default: moduleLoader = FileModuleLoader::new; break; case Node: moduleLoader = NodeModuleLoader::new; break; case NodeStandard: moduleLoader = NodeStandardModuleLoader::new; break; default: throw new AssertionError(); } /* @formatter:off */ RuntimeContext context = new RuntimeContext.Builder() .setBaseDirectory(Paths.get("").toAbsolutePath()) .setGlobalAllocator(allocator) .setModuleLoader(moduleLoader) .setConsole(console) .setWorkerErrorReporter(this::errorReporter) .setOptions(compatibilityOptions(options)) .setParserOptions(parserOptions(options)) .setCompilerOptions(compilerOptions(options)) .build(); /* @formatter:on */ World world = new World(context); Realm realm = world.newRealm(); ExecutionContext cx = realm.defaultContext(); // Add completion to console console.addCompleter(new ShellCompleter(realm)); // Execute global specific initialization enqueueScriptTask(realm, () -> { InitializeHostDefinedRealm(realm); // Add global "arguments" property ScriptObject arguments = CreateArrayFromList(cx, options.arguments); CreateMethodProperty(cx, realm.getGlobalObject(), "arguments", arguments); }); if (options.console) { enqueueScriptTask(realm, () -> { ScriptObject console = ConsoleObject.createConsole(realm); CreateMethodProperty(cx, realm.getGlobalObject(), "console", console); }); } if (options.moduleLoaderMode == ModuleLoaderMode.Node) { // TODO: Add default initialize(Realm) method to ModuleLoader interface? enqueueScriptTask(realm, () -> { NodeModuleLoader nodeLoader = (NodeModuleLoader) world.getModuleLoader(); nodeLoader.initialize(realm); }); } // Run eval expressions and files for (EvalScript evalScript : options.evalScripts) { if (options.module) { enqueueScriptTask(realm, () -> { ModuleSource moduleSource = evalScript.getModuleSource(); SourceIdentifier moduleName = evalScript.getModuleName(); try { ModuleEvaluationJob(realm, moduleName, moduleSource); } catch (ParserException e) { Source source = moduleSource.toSource(); String file = e.getFile(); if (file.equals(source.getFileString())) { throw new ParserExceptionWithSource(e, source, moduleSource.sourceCode()); } Path filePath = Paths.get(file).toAbsolutePath(); Source errorSource = new Source(filePath, file, 1); String code = new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8); throw new ParserExceptionWithSource(e, errorSource, code); } }); } else { enqueueScriptTask(realm, () -> { Source source = evalScript.getSource(); String sourceCode = evalScript.getSourceCode(); try { eval(realm, parse(realm, source, sourceCode)); } catch (ParserException e) { throw new ParserExceptionWithSource(e, source, sourceCode); } }); } } return realm; } @FunctionalInterface private interface Init { void apply() throws IOException, URISyntaxException, MalformedNameException, ResolutionException; } private static void enqueueScriptTask(Realm realm, Init init) { realm.enqueueScriptTask(() -> { try { init.apply(); } catch (IOException e) { throw new UncheckedIOException(e); } catch (URISyntaxException e) { throw new UncheckedIOException(new IOException(e)); } catch (MalformedNameException | ResolutionException e) { throw e.toScriptException(realm.defaultContext()); } }); } private static Set<CompatibilityOption> compatibilityOptions(Options options) { Set<CompatibilityOption> compatibilityOptions; if (options.strict) { compatibilityOptions = CompatibilityOption.StrictCompatibility(); } else if (options.shellMode == ShellMode.Mozilla) { compatibilityOptions = CompatibilityOption.MozCompatibility(); } else { compatibilityOptions = CompatibilityOption.WebCompatibility(); } compatibilityOptions.addAll(CompatibilityOption.Version(CompatibilityOption.Version.ECMAScript2016)); if (options.asyncFunctions) { compatibilityOptions.add(CompatibilityOption.AsyncFunction); } if (options.ecmascript7) { compatibilityOptions.addAll(CompatibilityOption.Stage(CompatibilityOption.Stage.Strawman)); compatibilityOptions.addAll(CompatibilityOption.Experimental()); } if (options.experimental) { compatibilityOptions.addAll(CompatibilityOption.Experimental()); } if (options.stage >= 0) { compatibilityOptions.addAll(CompatibilityOption.Stage(stageForLevel(options.stage))); } if (options.parser) { compatibilityOptions.add(CompatibilityOption.ReflectParse); } if (options.promiseRejection) { compatibilityOptions.add(CompatibilityOption.PromiseRejection); } return compatibilityOptions; } private static CompatibilityOption.Stage stageForLevel(int level) { switch (level) { case 0: return CompatibilityOption.Stage.Strawman; case 1: return CompatibilityOption.Stage.Proposal; case 2: return CompatibilityOption.Stage.Draft; case 3: return CompatibilityOption.Stage.Candidate; case 4: return CompatibilityOption.Stage.Finished; default: throw new AssertionError(); } } private static EnumSet<Parser.Option> parserOptions(Options options) { EnumSet<Parser.Option> parserOptions = EnumSet.noneOf(Parser.Option.class); if (options.nativeCalls) { parserOptions.add(Parser.Option.NativeCall); } return parserOptions; } private static EnumSet<Compiler.Option> compilerOptions(Options options) { EnumSet<Compiler.Option> compilerOptions = EnumSet.noneOf(Compiler.Option.class); if (options.debug) { compilerOptions.add(Compiler.Option.PrintCode); } if (options.fullDebug) { compilerOptions.add(Compiler.Option.PrintCode); compilerOptions.add(Compiler.Option.PrintFullCode); } if (options.debugInfo) { compilerOptions.add(Compiler.Option.DebugInfo); } if (options.noTailCall) { compilerOptions.add(Compiler.Option.NoTailCall); } return compilerOptions; } }