/** Emulates the (Gnu)R command as precisely as possible. */ public class RCommand { // CheckStyle: stop system..print check public static void main(String[] args) { doMain(args, null, true, System.in, System.out); // never returns throw RInternalError.shouldNotReachHere(); } public static int doMain( String[] args, String[] env, boolean initial, InputStream inStream, OutputStream outStream) { RCmdOptions options = RCmdOptions.parseArguments(RCmdOptions.Client.R, args, false); options.printHelpAndVersion(); PolyglotEngine vm = createPolyglotEngineFromCommandLine(options, false, initial, inStream, outStream, env); return readEvalPrint(vm); } /** The standard R script escapes spaces to "~+~" in "-e" and "-f" commands. */ private static String unescapeSpace(String input) { return input.replace("~+~", " "); } static PolyglotEngine createPolyglotEngineFromCommandLine( RCmdOptions options, boolean embedded, boolean initial, InputStream inStream, OutputStream outStream, String[] env) { RStartParams rsp = new RStartParams(options, embedded); String fileArg = options.getString(FILE); if (fileArg != null) { if (options.getStringList(EXPR) != null) { Utils.rSuicide("cannot use -e with -f or --file"); } if (!rsp.getSlave()) { rsp.setSaveAction(SA_TYPE.NOSAVE); } if (fileArg.equals("-")) { // means stdin, but still implies NO_SAVE fileArg = null; } else { fileArg = unescapeSpace(fileArg); } // cf GNU R rsp.setInteractive(false); } /* * Outputting the welcome message here has the virtue that the VM initialization delay * occurs later. However, it does not work in embedded mode as console redirects have not * been installed at this point. So we do it later in REmbedded. */ if (!rsp.getQuiet() && !embedded) { System.out.println(RRuntime.WELCOME_MESSAGE); } /* * Whether the input is from stdin, a file (-f), or an expression on the command line (-e) * it goes through the console. N.B. -f and -e can't be used together and this is already * checked. */ ConsoleHandler consoleHandler; if (fileArg != null) { List<String> lines; String filePath; try { /* * If initial==false, ~ expansion will not have been done and the open will fail. * It's harmless to always do it. */ File file = new File(Utils.tildeExpand(fileArg)); lines = Files.readAllLines(file.toPath()); filePath = file.getCanonicalPath(); } catch (IOException e) { if (initial) { throw Utils.rSuicide(String.format(RError.Message.NO_SUCH_FILE.message, fileArg)); } else { throw RError.error(RError.NO_CALLER, RError.Message.NO_SUCH_FILE, fileArg); } } consoleHandler = new StringConsoleHandler(lines, outStream, filePath); } else if (options.getStringList(EXPR) != null) { List<String> exprs = options.getStringList(EXPR); for (int i = 0; i < exprs.size(); i++) { exprs.set(i, unescapeSpace(exprs.get(i))); } if (!rsp.getSlave()) { rsp.setSaveAction(SA_TYPE.NOSAVE); } // cf GNU R rsp.setInteractive(false); consoleHandler = new StringConsoleHandler(exprs, outStream, RSource.Internal.EXPRESSION_INPUT.string); } else { /* * GnuR behavior differs from the manual entry for {@code interactive} in that {@code * --interactive} never applies to {@code -e/-f}, only to console input that has been * redirected from a pipe/file etc. * * If we are in embedded mode, the creation of ConsoleReader and the ConsoleHandler * should be lazy, as these may not be necessary and can cause hangs if stdin has been * redirected. */ Console sysConsole = System.console(); // TODO fix for context sessions boolean isInteractive = options.getBoolean(INTERACTIVE) || sysConsole != null; if (!isInteractive && rsp.getSaveAction() != SA_TYPE.SAVE && rsp.getSaveAction() != SA_TYPE.NOSAVE) { String msg = "you must specify '--save', '--no-save' or '--vanilla'"; if (initial) { throw Utils.rSuicide(msg); } else { throw RError.error(RError.NO_CALLER, RError.Message.GENERIC, msg); } } if (embedded) { consoleHandler = new EmbeddedConsoleHandler(rsp); } else { boolean useReadLine = !rsp.getNoReadline(); if (useReadLine) { consoleHandler = new JLineConsoleHandler(rsp, inStream, outStream); } else { consoleHandler = new DefaultConsoleHandler(inStream, outStream); } } } return ContextInfo.create( rsp, env, ContextKind.SHARE_NOTHING, initial ? null : RContext.getInstance(), consoleHandler) .createVM(); } private static final Source GET_ECHO = RSource.fromTextInternal("invisible(getOption('echo'))", RSource.Internal.GET_ECHO); private static final Source QUIT_EOF = RSource.fromTextInternal("quit(\"default\", 0L, TRUE)", RSource.Internal.QUIT_EOF); /** * The read-eval-print loop, which can take input from a console, command line expression or a * file. There are two ways the repl can terminate: * * <ol> * <li>A {@code quit} command is executed successfully, which case the system exits from the * {@link Quit} {@code .Internal} . * <li>EOF on the input. * </ol> * * In case 2, we must implicitly execute a {@code quit("default, 0L, TRUE} command before exiting. * So,in either case, we never return. */ static int readEvalPrint(PolyglotEngine vm) { int lastStatus = 0; ContextInfo contextInfo = ContextInfo.getContextInfo(vm); ConsoleHandler consoleHandler = contextInfo.getConsoleHandler(); try { // console.println("initialize time: " + (System.currentTimeMillis() - start)); REPL: for (; ; ) { boolean doEcho = doEcho(vm); consoleHandler.setPrompt(doEcho ? "> " : null); try { String input = consoleHandler.readLine(); if (input == null) { throw new EOFException(); } String trInput = input.trim(); if (trInput.equals("") || trInput.charAt(0) == '#') { // nothing to parse continue; } String continuePrompt = getContinuePrompt(); StringBuffer sb = new StringBuffer(input); Source source = RSource.fromTextInternal(sb.toString(), RSource.Internal.SHELL_INPUT); while (true) { lastStatus = 0; try { try { vm.eval(source); // checked exceptions are wrapped in RuntimeExceptions } catch (RuntimeException e) { if (e.getCause() instanceof com.oracle.truffle.api.vm.IncompleteSourceException) { throw e.getCause().getCause(); } throw e; } } catch (IncompleteSourceException e) { // read another line of input consoleHandler.setPrompt(doEcho ? continuePrompt : null); String additionalInput = consoleHandler.readLine(); if (additionalInput == null) { throw new EOFException(); } sb.append(additionalInput); source = RSource.fromTextInternal(sb.toString(), RSource.Internal.SHELL_INPUT); // The only continuation in the while loop continue; } catch (ParseException e) { e.report(consoleHandler); lastStatus = 1; } catch (RError e) { // drop through to continue REPL and remember last eval was an error lastStatus = 1; } catch (JumpToTopLevelException e) { // drop through to continue REPL } catch (DebugExitException e) { throw (RuntimeException) e.getCause(); } catch (ExitException e) { // usually from quit int status = e.getStatus(); if (contextInfo.getParent() == null) { vm.dispose(); Utils.systemExit(status); } else { return status; } } catch (Throwable e) { RInternalError.reportErrorAndConsoleLog(e, consoleHandler, 0); // We continue the repl even though the system may be broken lastStatus = 1; } continue REPL; } } catch (UserInterruptException e) { // interrupted by ctrl-c } } } catch (JumpToTopLevelException | EOFException ex) { // JumpToTopLevelException can happen if user profile invokes browser (unlikely but // possible) try { vm.eval(QUIT_EOF); } catch (JumpToTopLevelException e) { Utils.systemExit(0); } catch (ExitException e) { // normal quit, but with exit code based on lastStatus if (contextInfo.getParent() == null) { Utils.systemExit(lastStatus); } else { return lastStatus; } } catch (Throwable e) { throw RInternalError.shouldNotReachHere(e); } } finally { vm.dispose(); } return 0; } private static boolean doEcho(PolyglotEngine vm) { PolyglotEngine.Value echoValue = vm.eval(GET_ECHO); Object echo = echoValue.get(); if (echo instanceof TruffleObject) { RLogicalVector echoVec = echoValue.as(RLogicalVector.class); return RRuntime.fromLogical(echoVec.getDataAt(0)); } else if (echo instanceof Byte) { return RRuntime.fromLogical((Byte) echo); } else { throw RInternalError.shouldNotReachHere(); } } private static String getContinuePrompt() { return RRuntime.asString( RRuntime.asAbstractVector(RContext.getInstance().stateROptions.getValue("continue"))); } }
/** * The read-eval-print loop, which can take input from a console, command line expression or a * file. There are two ways the repl can terminate: * * <ol> * <li>A {@code quit} command is executed successfully, which case the system exits from the * {@link Quit} {@code .Internal} . * <li>EOF on the input. * </ol> * * In case 2, we must implicitly execute a {@code quit("default, 0L, TRUE} command before exiting. * So,in either case, we never return. */ static int readEvalPrint(PolyglotEngine vm) { int lastStatus = 0; ContextInfo contextInfo = ContextInfo.getContextInfo(vm); ConsoleHandler consoleHandler = contextInfo.getConsoleHandler(); try { // console.println("initialize time: " + (System.currentTimeMillis() - start)); REPL: for (; ; ) { boolean doEcho = doEcho(vm); consoleHandler.setPrompt(doEcho ? "> " : null); try { String input = consoleHandler.readLine(); if (input == null) { throw new EOFException(); } String trInput = input.trim(); if (trInput.equals("") || trInput.charAt(0) == '#') { // nothing to parse continue; } String continuePrompt = getContinuePrompt(); StringBuffer sb = new StringBuffer(input); Source source = RSource.fromTextInternal(sb.toString(), RSource.Internal.SHELL_INPUT); while (true) { lastStatus = 0; try { try { vm.eval(source); // checked exceptions are wrapped in RuntimeExceptions } catch (RuntimeException e) { if (e.getCause() instanceof com.oracle.truffle.api.vm.IncompleteSourceException) { throw e.getCause().getCause(); } throw e; } } catch (IncompleteSourceException e) { // read another line of input consoleHandler.setPrompt(doEcho ? continuePrompt : null); String additionalInput = consoleHandler.readLine(); if (additionalInput == null) { throw new EOFException(); } sb.append(additionalInput); source = RSource.fromTextInternal(sb.toString(), RSource.Internal.SHELL_INPUT); // The only continuation in the while loop continue; } catch (ParseException e) { e.report(consoleHandler); lastStatus = 1; } catch (RError e) { // drop through to continue REPL and remember last eval was an error lastStatus = 1; } catch (JumpToTopLevelException e) { // drop through to continue REPL } catch (DebugExitException e) { throw (RuntimeException) e.getCause(); } catch (ExitException e) { // usually from quit int status = e.getStatus(); if (contextInfo.getParent() == null) { vm.dispose(); Utils.systemExit(status); } else { return status; } } catch (Throwable e) { RInternalError.reportErrorAndConsoleLog(e, consoleHandler, 0); // We continue the repl even though the system may be broken lastStatus = 1; } continue REPL; } } catch (UserInterruptException e) { // interrupted by ctrl-c } } } catch (JumpToTopLevelException | EOFException ex) { // JumpToTopLevelException can happen if user profile invokes browser (unlikely but // possible) try { vm.eval(QUIT_EOF); } catch (JumpToTopLevelException e) { Utils.systemExit(0); } catch (ExitException e) { // normal quit, but with exit code based on lastStatus if (contextInfo.getParent() == null) { Utils.systemExit(lastStatus); } else { return lastStatus; } } catch (Throwable e) { throw RInternalError.shouldNotReachHere(e); } } finally { vm.dispose(); } return 0; }