package com.oracle.truffle.trufflenode;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import org.graalvm.launcher.AbstractLanguageLauncher;
import org.graalvm.options.OptionCategory;
import org.graalvm.polyglot.Context;
import com.oracle.truffle.js.lang.JavaScriptLanguage;
import com.oracle.truffle.js.runtime.JSConfig;
public final class Options {
private final Context.Builder contextBuilder;
private final boolean exposeGC;
private Options(Context.Builder contextBuilder, boolean exposeGC) {
this.contextBuilder = contextBuilder;
this.exposeGC = exposeGC;
}
public static Options parseArguments(String[] args) throws Exception {
Function<String[], Object[]> parser;
if (JSConfig.SubstrateVM) {
parser = new OptionsParser();
} else {
Class<Function<String[], Object[]>> clazz = loadOptionsParser();
parser = clazz.getDeclaredConstructor().newInstance();
}
Object[] result = parser.apply(args);
return new Options((Context.Builder) result[0], (Boolean) result[1]);
}
@SuppressWarnings("unchecked")
private static Class<Function<String[], Object[]>> loadOptionsParser() throws Exception {
String javaHome = System.getProperty("java.home");
String truffleNodePath = System.getenv("TRUFFLENODE_JAR_PATH");
if (truffleNodePath == null) {
truffleNodePath = javaHome + "/languages/js/trufflenode.jar";
}
String launcherCommonPath = System.getenv("LAUNCHER_COMMON_JAR_PATH");
if (launcherCommonPath == null) {
launcherCommonPath = javaHome + "/lib/graalvm/launcher-common.jar";
}
URL truffleNodeURL = new URL("file:" + truffleNodePath);
URL launcherCommonURL = new URL("file:" + launcherCommonPath);
ClassLoader loader = new URLClassLoader(new URL[]{launcherCommonURL, truffleNodeURL}, ClassLoader.getSystemClassLoader());
return (Class<Function<String[], Object[]>>) loader.loadClass("com.oracle.truffle.trufflenode.Options$OptionsParser");
}
public Context.Builder getContextBuilder() {
return contextBuilder;
}
public boolean isGCExposed() {
return exposeGC;
}
public static class OptionsParser extends AbstractLanguageLauncher implements Function<String[], Object[]> {
private static final String INSPECT = "inspect";
private static final String INSPECT_SUSPEND = "inspect.Suspend";
private static final String INSPECT_WAIT_ATTACHED = "inspect.WaitAttached";
private Context.Builder contextBuilder;
private boolean exposeGC;
private boolean polyglot;
private static final Set<String> IGNORED_OPTIONS = new HashSet<>(Arrays.asList(new String[]{
"debug-code",
"es-staging",
"experimental-modules",
"expose-debug-as",
"expose-internals",
"expose-natives-as",
"gc-global",
"gc-interval",
"harmony",
"harmony-bigint",
"harmony-default-parameters",
"harmony-dynamic-import",
"harmony-import-meta",
"harmony-proxies",
"harmony-shipping",
"harmony-weak-refs",
"lazy",
"log-timer-events",
"nolazy",
"nouse-idle-notification",
"stack-size",
"use_idle_notification"
}));
@Override
public Object[] apply(String[] args) {
launch(filterArguments(args));
if (contextBuilder == null) {
System.exit(0);
}
return new Object[]{contextBuilder, exposeGC};
}
private String[] filterArguments(String[] args) {
List<String> filtered = new ArrayList<>();
for (String arg : args) {
if ("--polyglot".equals(arg)) {
polyglot = true;
} else {
filtered.add(arg);
}
}
return filtered.toArray(new String[filtered.size()]);
}
@Override
protected List<String> preprocessArguments(List<String> arguments, Map<String, String> polyglotOptions) {
polyglotOptions.put("js.print", "false");
List<String> unprocessedArguments = new ArrayList<>();
for (String arg : arguments) {
String key = "";
String value = null;
if (arg.startsWith("--")) {
int idx = arg.indexOf('=');
if (idx == -1) {
key = arg.substring(2);
} else {
key = arg.substring(2, idx);
value = arg.substring(idx + 1);
}
}
String normalizedKey = key.replace('_', '-');
if (IGNORED_OPTIONS.contains(normalizedKey)) {
continue;
}
if ("use-strict".equals(normalizedKey)) {
polyglotOptions.put("js.strict", "true");
continue;
}
if ("harmony-sharedarraybuffer".equals(normalizedKey)) {
polyglotOptions.put("js.shared-array-buffer", "true");
continue;
}
if ("-h".equals(arg)) {
unprocessedArguments.add("--help");
continue;
}
if ("expose-gc".equals(normalizedKey)) {
exposeGC = true;
continue;
}
if (INSPECT_SUSPEND.equals(key)) {
polyglotOptions.put(INSPECT_SUSPEND, valueOrTrue(value));
continue;
}
if (INSPECT.equals(key)) {
if (value != null || !polyglotOptions.containsKey(INSPECT)) {
polyglotOptions.put(key, valueOrTrue(value));
}
if (!polyglotOptions.containsKey(INSPECT_SUSPEND)) {
polyglotOptions.put(INSPECT_SUSPEND, "false");
}
continue;
}
if ("inspect-brk".equals(normalizedKey) || "debug-brk".equals(normalizedKey) || "inspect-brk-node".equals(normalizedKey)) {
if (value != null || !polyglotOptions.containsKey(INSPECT)) {
polyglotOptions.put(INSPECT, valueOrTrue(value));
}
if (!polyglotOptions.containsKey(INSPECT_SUSPEND)) {
polyglotOptions.put(INSPECT_SUSPEND, "false");
}
polyglotOptions.put(INSPECT_WAIT_ATTACHED, "true");
continue;
}
if ("prof".equals(key)) {
System.err.println("--prof option is not supported, use one of our profiling tools instead (use --help:tools for more details)");
System.exit(1);
}
if ("stack-trace-limit".equals(key)) {
polyglotOptions.put("js.stack-trace-limit", value);
continue;
}
unprocessedArguments.add(arg);
}
return unprocessedArguments;
}
private static String valueOrTrue(String value) {
return (value == null) ? "true" : value;
}
@Override
protected void launch(Context.Builder builder) {
this.contextBuilder = builder;
}
@Override
protected String getLanguageId() {
return JavaScriptLanguage.ID;
}
@Override
protected void collectArguments(Set<String> options) {
options.add("--eval");
options.add("--print");
options.add("--check");
options.add("--interactive");
options.add("--require");
}
@Override
protected void printHelp(OptionCategory maxCategory) {
System.out.println();
System.out.println("Usage: node [options] [ -e script | script.js ] [arguments]\n");
System.out.println("Basic Options:");
printOption("-v, --version", "print version");
printOption("-e, --eval script", "evaluate script");
printOption("-p, --print", "evaluate script and print result");
printOption("-c, --check", "syntax check script without executing");
printOption("-i, --interactive", "always enter the REPL even if stdin does not appear to be a terminal");
printOption("-r, --require", "module to preload (option can be repeated)");
printOption("--inspect[=port]", "activate inspector on port (overrides options of Chrome Inspector)");
printOption("--inspect-brk[=port]", "activate inspector on port and break at start of user script (overrides options of Chrome Inspector)");
}
private static void printOption(String option, String description) {
String opt;
if (option.length() >= 22) {
System.out.println(String.format("%s%s", " ", option));
opt = "";
} else {
opt = option;
}
System.out.println(String.format(" %-22s%s", opt, description));
}
@Override
protected String[] getDefaultLanguages() {
return polyglot ? new String[0] : super.getDefaultLanguages();
}
}
}