diff --git a/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/RCommand.java b/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/RCommand.java index 64e03cca267097f2d160f8641fdf7d38d9fc6e5c..df12992a42bcc379d0c08517ec54dc2539d059fa 100644 --- a/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/RCommand.java +++ b/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/RCommand.java @@ -183,9 +183,8 @@ public class RCommand { public static ConsoleHandler createConsoleHandler(RCmdOptions options, DelegatingConsoleHandler useDelegatingWrapper, InputStream inStream, OutputStream outStream) { /* - * 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. + * 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. */ RStartParams rsp = new RStartParams(options, false); String fileArgument = rsp.getFileArgument(); @@ -193,8 +192,8 @@ public class RCommand { List<String> lines; try { /* - * If initial==false, ~ expansion will not have been done and the open will fail. - * It's harmless to always do it. + * If initial==false, ~ expansion will not have been done and the open will fail. It's harmless to + * always do it. */ File file = fileArgument.startsWith("~") ? new File(System.getProperty("user.home") + fileArgument.substring(1)) : new File(fileArgument); lines = Files.readAllLines(file.toPath()); @@ -216,9 +215,8 @@ public class RCommand { boolean useReadLine = isInteractive && !rsp.noReadline(); if (useDelegatingWrapper != null) { /* - * 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. + * 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. */ Supplier<ConsoleHandler> delegateFactory = useReadLine ? () -> new JLineConsoleHandler(inStream, outStream, rsp.isSlave()) : () -> new DefaultConsoleHandler(inStream, outStream, isInteractive); @@ -251,14 +249,14 @@ public class RCommand { } /** - * 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: + * 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.</li> * <li>EOF on the input.</li> * </ol> - * In case 2, we must implicitly execute a {@code quit("default, 0L, TRUE} command before - * exiting. So,in either case, we never return. + * In case 2, we must implicitly execute a {@code quit("default, 0L, TRUE} command before exiting. + * So,in either case, we never return. */ public static int readEvalPrint(Context context, ConsoleHandler consoleHandler, File srcFile) { int lastStatus = 0; @@ -286,7 +284,7 @@ public class RCommand { Source src; if (srcFile != null) { int endLine = consoleHandler.getCurrentLineIndex(); - src = Source.newBuilder("R", sb.toString(), srcFile.getName() + "#" + startLine + "-" + endLine).interactive(true).uri(srcFile.toURI()).buildLiteral(); + src = Source.newBuilder("R", sb.toString(), srcFile.toString() + "#" + startLine + "-" + endLine).interactive(true).uri(srcFile.toURI()).buildLiteral(); } else { src = Source.newBuilder("R", sb.toString(), "<REPL>").interactive(true).buildLiteral(); } diff --git a/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/DefaultRParserFactory.java b/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/DefaultRParserFactory.java index df41e3d0d2c420bb88c6699133eb5e1dc2f0417c..f92d3697ba5833dfbe6f37da88c4e8f578bb0a56 100644 --- a/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/DefaultRParserFactory.java +++ b/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/DefaultRParserFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,6 +34,7 @@ import com.oracle.truffle.api.source.Source; import com.oracle.truffle.r.runtime.RParserFactory; import com.oracle.truffle.r.runtime.context.Engine.IncompleteSourceException; import com.oracle.truffle.r.runtime.context.Engine.ParseException; +import com.oracle.truffle.r.runtime.context.RContext; import com.oracle.truffle.r.runtime.context.TruffleRLanguage; import com.oracle.truffle.r.runtime.nodes.RCodeBuilder; @@ -45,7 +46,8 @@ public class DefaultRParserFactory extends RParserFactory { public List<T> script(Source source, RCodeBuilder<T> builder, TruffleRLanguage language) throws ParseException { try { try { - RParser<T> parser = new RParser<>(source, builder, language); + RContext context = language.getContextReference().get(); + RParser<T> parser = new RParser<>(source, builder, language, context.sourceCache); return parser.script(); } catch (IllegalArgumentException e) { // the lexer will wrap exceptions in IllegalArgumentExceptions @@ -62,7 +64,8 @@ public class DefaultRParserFactory extends RParserFactory { @Override public RootCallTarget rootFunction(Source source, String name, RCodeBuilder<T> builder, TruffleRLanguage language) throws ParseException { - RParser<T> parser = new RParser<>(source, builder, language); + RContext context = language.getContextReference().get(); + RParser<T> parser = new RParser<>(source, builder, language, context.sourceCache); try { return parser.root_function(name); } catch (RecognitionException e) { diff --git a/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/R.g b/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/R.g index 21326517ababa0af8da39f71ae6c33dadeac4c8d..a86d1728d7e0fd16b916f60228ab2a974b939586 100644 --- a/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/R.g +++ b/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/R.g @@ -26,14 +26,15 @@ options { //@formatter:off package com.oracle.truffle.r.parser; -import java.util.ArrayList; -import java.util.List; import java.io.File; import java.io.IOException; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; -import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import com.oracle.truffle.api.RootCallTarget; import com.oracle.truffle.api.frame.MaterializedFrame; @@ -73,16 +74,18 @@ import com.oracle.truffle.r.runtime.RError; private RCodeBuilder<T> builder; private TruffleRLanguage language; private int fileStartOffset = 0; + private Map<String, Source> sourceCache; /** * Always use this constructor to initialize the R specific fields. */ - public RParser(Source source, RCodeBuilder<T> builder, TruffleRLanguage language) { + public RParser(Source source, RCodeBuilder<T> builder, TruffleRLanguage language, Map<String, Source> sourceCache) { super(new CommonTokenStream(new RLexer(new ANTLRStringStream(source.getCharacters().toString())))); assert source != null && builder != null; this.initialSource = source; this.builder = builder; this.language = language; + this.sourceCache = sourceCache; if (source.getURI() != null && source.getName().contains("#")) { this.source = createFullSource(source); } else { @@ -91,53 +94,75 @@ import com.oracle.truffle.r.runtime.RError; } private Source createFullSource(Source original) { - String originalName = original.getName(); - - // check if source name is like 'path/to/source.R#45-54' - int hash_idx = originalName.lastIndexOf("#"); - if (hash_idx == -1) { - return original; - } + String originalName = original.getName(); - String fileName = originalName.substring(0, hash_idx); - String lineRange = originalName.substring(hash_idx + 1); - - try { - // check for line range, e.g. '45-54' - int startLine = -1; - int endLine = -1; - int dashIdx = lineRange.indexOf('-'); - if (dashIdx != -1) { - startLine = Integer.parseInt(lineRange.substring(0, dashIdx)); - endLine = Integer.parseInt(lineRange.substring(dashIdx + 1)); - } else { - startLine = Integer.parseInt(lineRange); - endLine = startLine; - } - Builder<IOException, RuntimeException, RuntimeException> newBuilder = Source.newBuilder(new File(fileName)); - if (original.isInteractive()) { - newBuilder.interactive(); - } - Source fullSource = newBuilder.build(); - - // verify to avoid accidentally matching file names - for (int i = 0; i < endLine - startLine + 1; i++) { - if (!original.getCharacters(i + 1).equals(fullSource.getCharacters(startLine + i))) { - return original; - } - } + // check if source name is like 'path/to/source.R#45-54' + int hash_idx = originalName.lastIndexOf("#"); + if (hash_idx == -1) { + return original; + } - fileStartOffset = -fullSource.getLineStartOffset(startLine); - return fullSource; - } catch (NumberFormatException e) { - // invalid line number - } catch (IllegalArgumentException e) { - // file name is accidentally named in the expected scheme - } catch (IOException e) { - } catch (RuntimeException e) { + String fileName = originalName.substring(0, hash_idx); + String lineRange = originalName.substring(hash_idx + 1); + + try { + // check for line range, e.g. '45-54' + int startLine = -1; + int endLine = -1; + int dashIdx = lineRange.indexOf('-'); + if (dashIdx != -1) { + startLine = Integer.parseInt(lineRange.substring(0, dashIdx)); + endLine = Integer.parseInt(lineRange.substring(dashIdx + 1)); + } else { + startLine = Integer.parseInt(lineRange); + endLine = startLine; + } + File f = new File(fileName); + Source fullSource; + String canonicalName; + try { + canonicalName = f.getAbsoluteFile().getCanonicalPath(); + fullSource = sourceCache != null ? sourceCache.get(canonicalName) : null; + } catch(IOException e) { + // ignore an freshly load file + fullSource = null; + canonicalName = null; + } + if(fullSource == null) { + Builder<IOException, RuntimeException, RuntimeException> newBuilder = Source.newBuilder(f); + if (original.isInteractive()) { + newBuilder.interactive(); + } + fullSource = newBuilder.build(); + + if (sourceCache != null && canonicalName != null) { + sourceCache.put(canonicalName, fullSource); + } } - return original; - } + + // verify to avoid accidentally matching file names + for (int i = 0; i < endLine - startLine + 1; i++) { + if (!original.getCharacters(i + 1).equals(fullSource.getCharacters(startLine + i))) { + return original; + } + } + + fileStartOffset = -fullSource.getLineStartOffset(startLine); + return fullSource; + } catch (NumberFormatException e) { + // invalid line number + } catch (IllegalArgumentException e) { + // file name is accidentally named in the expected scheme + } catch (IOException e) { + } catch (RuntimeException e) { + assert rethrow(e); + } + return original; + } + + private <T extends Throwable> boolean rethrow(T e) throws T { + throw e; + } /** * Helper function that returns the last parsed token, usually used for building source sections. diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java index 393a3df3fddce03701f21938395fb5cf51231754..14cbbbc8216169c4ca2e8770410c69787fec6a70 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java @@ -361,6 +361,7 @@ public final class RContext { public final List<String> libraryPaths = new ArrayList<>(1); public final Map<Integer, Thread> threads = new ConcurrentHashMap<>(); public final LanguageClosureCache languageClosureCache = new LanguageClosureCache(); + public final Map<String, Source> sourceCache = new ConcurrentHashMap<>(); private final AllocationReporter allocationReporter;