diff --git a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/shell/EmbeddedConsoleHandler.java b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/shell/EmbeddedConsoleHandler.java index 4af701bbecf95d302ed5f309cfbdb55e46575df1..a6fa46db300fefca8accfefe8e70e6e628fbc7a6 100644 --- a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/shell/EmbeddedConsoleHandler.java +++ b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/shell/EmbeddedConsoleHandler.java @@ -59,6 +59,7 @@ public final class EmbeddedConsoleHandler extends DelegatingConsoleHandler { private Context context; private Supplier<ConsoleHandler> delegateFactory; private ConsoleHandler delegate; + private int currentLine; private CallTarget readLineCallTarget; private CallTarget writeCallTarget; @@ -78,7 +79,9 @@ public final class EmbeddedConsoleHandler extends DelegatingConsoleHandler { @Override public String readLine() { try (ContextClose ignored = inContext()) { - return isOverridden("R_ReadConsole") ? (String) getReadLineCallTarget().call("TODO prompt>") : getDelegate().readLine(); + String l = isOverridden("R_ReadConsole") ? (String) getReadLineCallTarget().call("TODO prompt>") : getDelegate().readLine(); + currentLine++; + return l; } } @@ -239,4 +242,9 @@ public final class EmbeddedConsoleHandler extends DelegatingConsoleHandler { return null; } } + + @Override + public int getCurrentLineIndex() { + return currentLine; + } } diff --git a/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/ConsoleHandler.java b/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/ConsoleHandler.java index f83c933b57f2d639383f736449e25ef3c6b50023..7043743d98d5f1e65606d8985b3419665122ac21 100644 --- a/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/ConsoleHandler.java +++ b/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/ConsoleHandler.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 @@ -39,6 +39,11 @@ public abstract class ConsoleHandler { */ public abstract String readLine(); + /** + * Return the current 1-based line number. + */ + public abstract int getCurrentLineIndex(); + /** * Set the R prompt. */ diff --git a/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/DefaultConsoleHandler.java b/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/DefaultConsoleHandler.java index a395a0e613e5bfeac692cdc4491c79885c79f202..e7c14851ad99ec7def2e2c36e72471a086c29d28 100644 --- a/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/DefaultConsoleHandler.java +++ b/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/DefaultConsoleHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -35,6 +35,7 @@ public class DefaultConsoleHandler extends ConsoleHandler { private final BufferedReader in; private final PrintStream out; private String prompt; + private int currentLine; public DefaultConsoleHandler(InputStream in, OutputStream out, boolean interactive) { this.in = new BufferedReader(new InputStreamReader(in)); @@ -49,6 +50,7 @@ public class DefaultConsoleHandler extends ConsoleHandler { out.print(prompt); } String line = in.readLine(); + currentLine++; if ((line == null || "".equals(line.trim())) && prompt != null && !interactive) { out.println(); } @@ -62,4 +64,9 @@ public class DefaultConsoleHandler extends ConsoleHandler { public void setPrompt(String prompt) { this.prompt = prompt; } + + @Override + public int getCurrentLineIndex() { + return currentLine; + } } diff --git a/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/JLineConsoleHandler.java b/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/JLineConsoleHandler.java index e3c4cee27ef1c5139af1cdfa22552b540f4cf8ff..b3150e178f8e95c7ecdc84156e92688703bb7c4b 100644 --- a/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/JLineConsoleHandler.java +++ b/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/JLineConsoleHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -38,6 +38,7 @@ public class JLineConsoleHandler extends ConsoleHandler { private final ConsoleReader console; private final MemoryHistory history; private final boolean noPrompt; + private int currentLine; public JLineConsoleHandler(InputStream inStream, OutputStream outStream, boolean noPrompt) { this.noPrompt = noPrompt; @@ -65,6 +66,7 @@ public class JLineConsoleHandler extends ConsoleHandler { public String readLine() { try { console.getTerminal().init(); + currentLine++; return console.readLine(); } catch (UserInterruptException e) { // interrupted by ctrl-c @@ -94,4 +96,9 @@ public class JLineConsoleHandler extends ConsoleHandler { } return result; } + + @Override + public int getCurrentLineIndex() { + return currentLine; + } } 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 136388102c545428d784ceba9356143cab167ae0..1e74ca7956ff2e573deed0ecdcfa6b1bb4cf74e2 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 @@ -170,7 +170,14 @@ public class RCommand { consoleHandler.setContext(context); StartupTiming.timestamp("VM Created"); StartupTiming.printSummary(); - return readEvalPrint(context, consoleHandler); + + File srcFile = null; + String fileOption = options.getString(RCmdOption.FILE); + if (fileOption != null) { + srcFile = new File(fileOption); + } + + return readEvalPrint(context, consoleHandler, srcFile); } } @@ -239,6 +246,10 @@ public class RCommand { private static final Source GET_PROMPT = Source.newBuilder("R", ".Internal(getOption('prompt'))", "<prompt>").internal(true).buildLiteral(); private static final Source GET_CONTINUE_PROMPT = Source.newBuilder("R", ".Internal(getOption('continue'))", "<continue-prompt>").internal(true).buildLiteral(); + public static int readEvalPrint(Context context, ConsoleHandler consoleHandler) { + return readEvalPrint(context, consoleHandler, null); + } + /** * 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: @@ -249,7 +260,7 @@ public class RCommand { * 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) { + public static int readEvalPrint(Context context, ConsoleHandler consoleHandler, File srcFile) { int lastStatus = 0; try { while (true) { // processing inputs @@ -268,10 +279,18 @@ public class RCommand { String continuePrompt = null; StringBuilder sb = new StringBuilder(input); + int startLine = consoleHandler.getCurrentLineIndex(); while (true) { // processing subsequent lines while input is incomplete lastStatus = 0; try { - context.eval(Source.newBuilder("R", sb.toString(), "<REPL>").interactive(true).buildLiteral()); + Source src; + if (srcFile != null) { + int endLine = consoleHandler.getCurrentLineIndex(); + 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(); + } + context.eval(src); } catch (PolyglotException e) { if (continuePrompt == null) { continuePrompt = doEcho ? getContinuePrompt(context) : null; diff --git a/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/RscriptCommand.java b/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/RscriptCommand.java index e4ca8fa47dd8e44941047937ff8f9eea6d58b639..51fb18b43656c3e5451e6aa1c8c1087960b54521 100644 --- a/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/RscriptCommand.java +++ b/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/RscriptCommand.java @@ -22,6 +22,7 @@ */ package com.oracle.truffle.r.launcher; +import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; @@ -140,7 +141,8 @@ public class RscriptCommand { try (Context context = Context.newBuilder().allowHostAccess(useJVM).options(polyglotOptions).arguments("R", arguments).in(consoleHandler.createInputStream()).out(outStream).err( errStream).build()) { consoleHandler.setContext(context); - return RCommand.readEvalPrint(context, consoleHandler); + String fileOption = options.getString(RCmdOption.FILE); + return RCommand.readEvalPrint(context, consoleHandler, fileOption != null ? new File(fileOption) : null); } } } diff --git a/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/StringConsoleHandler.java b/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/StringConsoleHandler.java index 1327a8162997473ccaa032d9b4f27886e83d5f18..f5dd42526ad1f2de7df9fafd71bde76cc16485f2 100644 --- a/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/StringConsoleHandler.java +++ b/com.oracle.truffle.r.launcher/src/com/oracle/truffle/r/launcher/StringConsoleHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -54,4 +54,9 @@ class StringConsoleHandler extends ConsoleHandler { public void setPrompt(String prompt) { this.prompt = prompt; } + + @Override + public int getCurrentLineIndex() { + return currentLine; + } } 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/ParserGeneration.java b/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/ParserGeneration.java index 0aaa94d549b107e157a1d04c9c047294e43b7935..2d17f16cb44ee8dc2a0e892eab4b31168e17540a 100644 --- a/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/ParserGeneration.java +++ b/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/ParserGeneration.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 @@ -96,6 +96,7 @@ public class ParserGeneration { "handle four and more dots as identifier", "allow greek characters in identifiers", "allow everything but newlines in %<ident>% operators", - "allow strings in :: and :::" + "allow strings in :: and :::", + "use file for interactive single-line source" }; } 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 439c59319569283c6421c452f663bf39a5a9ca92..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 @@ -4,7 +4,7 @@ * http://www.gnu.org/licenses/gpl-2.0.html * * Copyright (c) 2012-2014, Purdue University - * Copyright (c) 2013, 2017, Oracle and/or its affiliates + * Copyright (c) 2013, 2018, Oracle and/or its affiliates * * All rights reserved. */ @@ -26,17 +26,20 @@ 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; import com.oracle.truffle.api.source.Source; +import com.oracle.truffle.api.source.Source.Builder; import com.oracle.truffle.api.source.SourceSection; import com.oracle.truffle.r.runtime.RError; @@ -71,17 +74,94 @@ 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.source = source; this.initialSource = source; this.builder = builder; this.language = language; + this.sourceCache = sourceCache; + if (source.getURI() != null && source.getName().contains("#")) { + this.source = createFullSource(source); + } else { + this.source = source; + } + } + + 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 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); + } + } + + // 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; } /** 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; diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/nodes/RNode.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/nodes/RNode.java index d585d0de6206aa59094ed82bbaffe231a7aae8ce..90764cd79f7da521d4e450f076249047ed89c7eb 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/nodes/RNode.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/nodes/RNode.java @@ -39,7 +39,7 @@ public abstract class RNode extends RBaseNode implements RInstrumentableNode { @Override public boolean isInstrumentable() { - return true; + return (this instanceof RSyntaxElement && ((RSyntaxElement) this).getLazySourceSection() != null) || getSourceSection() != null; } @Override