diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java index 328873efa0fb4b516c2fd658f73fc5c5d8b5ad38..575c68930adf236a99572ec0b323f110a0106d19 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java @@ -132,6 +132,7 @@ public class BasePackage extends RBuiltinPackage { add(ConnectionFunctions.GetAllConnections.class, ConnectionFunctionsFactory.GetAllConnectionsNodeGen::create); add(ConnectionFunctions.GetConnection.class, ConnectionFunctionsFactory.GetConnectionNodeGen::create); add(ConnectionFunctions.IsOpen.class, ConnectionFunctionsFactory.IsOpenNodeGen::create); + add(ConnectionFunctions.IsSeekable.class, ConnectionFunctionsFactory.IsSeekableNodeGen::create); add(ConnectionFunctions.Open.class, ConnectionFunctionsFactory.OpenNodeGen::create); add(ConnectionFunctions.PushBack.class, ConnectionFunctionsFactory.PushBackNodeGen::create); add(ConnectionFunctions.PushBackClear.class, ConnectionFunctionsFactory.PushBackClearNodeGen::create); @@ -139,6 +140,7 @@ public class BasePackage extends RBuiltinPackage { add(ConnectionFunctions.ReadBin.class, ConnectionFunctionsFactory.ReadBinNodeGen::create); add(ConnectionFunctions.ReadChar.class, ConnectionFunctionsFactory.ReadCharNodeGen::create); add(ConnectionFunctions.ReadLines.class, ConnectionFunctionsFactory.ReadLinesNodeGen::create); + add(ConnectionFunctions.Seek.class, ConnectionFunctionsFactory.SeekNodeGen::create); add(ConnectionFunctions.SocketConnection.class, ConnectionFunctionsFactory.SocketConnectionNodeGen::create); add(ConnectionFunctions.Stderr.class, ConnectionFunctionsFactory.StderrNodeGen::create); add(ConnectionFunctions.Stdin.class, ConnectionFunctionsFactory.StdinNodeGen::create); diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Cat.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Cat.java index 705fa0b1c8ec0bd0971bee577937f6945bcda91d..08208779daf086dbdfc7e71ecac6fa1cc115780d 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Cat.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Cat.java @@ -114,7 +114,7 @@ public abstract class Cat extends RInvisibleBuiltinNode { data = data + "\n"; } try { - conn.writeLines(RDataFactory.createStringVectorFromScalar(data), ""); + conn.writeLines(RDataFactory.createStringVectorFromScalar(data), "", false); } catch (IOException ex) { throw RError.error(getEncapsulatingSourceSection(), RError.Message.GENERIC, ex.getMessage()); } diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/ConnectionFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/ConnectionFunctions.java index 47e726f4fc16092ab591f991cddaccea5f51aadc..e8aa24444039ef0284767267d1de7400baab530a 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/ConnectionFunctions.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/ConnectionFunctions.java @@ -421,9 +421,9 @@ public abstract class ConnectionFunctions { public abstract static class WriteLines extends InternalCloseHelper { @Specialization @TruffleBoundary - protected RNull writeLines(RAbstractStringVector text, RConnection con, RAbstractStringVector sep, @SuppressWarnings("unused") byte useBytes) { + protected RNull writeLines(RAbstractStringVector text, RConnection con, RAbstractStringVector sep, byte useBytes) { try (RConnection openConn = con.forceOpen("wt")) { - openConn.writeLines(text, sep.getDataAt(0)); + openConn.writeLines(text, sep.getDataAt(0), RRuntime.fromLogical(useBytes)); } catch (IOException x) { throw RError.error(getEncapsulatingSourceSection(), RError.Message.ERROR_WRITING_CONNECTION, x.getMessage()); } @@ -945,4 +945,27 @@ public abstract class ConnectionFunctions { } } + @RBuiltin(name = "isSeekable", kind = INTERNAL, parameterNames = "con") + public abstract static class IsSeekable extends RBuiltinNode { + @Specialization + @TruffleBoundary + protected byte isSeekable(RConnection con) { + return RRuntime.asLogical(con.isSeekable()); + } + } + + @RBuiltin(name = "seek", kind = INTERNAL, parameterNames = {"con", "where", "origin", "rw"}) + public abstract static class Seek extends RBuiltinNode { + @Specialization + @TruffleBoundary + protected long seek(RConnection con, RAbstractDoubleVector where, RAbstractIntVector origin, RAbstractIntVector rw) { + long offset = (long) where.getDataAt(0); + try { + return con.seek(offset, RConnection.SeekMode.values()[origin.getDataAt(0)], RConnection.SeekRWMode.values()[rw.getDataAt(0)]); + } catch (IOException x) { + throw RError.error(getEncapsulatingSourceSection(), RError.Message.GENERIC, x.getMessage()); + } + } + } + } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java index 61b32b972a8bd986c2d8b7f9c810543a325cd8d3..f0c5c535a2878065ffb09ecbbf0e836367ef7b5f 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java @@ -265,6 +265,7 @@ public final class RError extends RuntimeException { ONLY_READ_BINARY_CONNECTION("can only read from a binary connection"), ONLY_WRITE_BINARY_CONNECTION("can only write to a binary connection"), NOT_A_TEXT_CONNECTION("'con' is not a textConnection"), + UNSEEKABLE_CONNECTION("'con' is not seekable"), MORE_CHARACTERS("more characters requested than are in the string - will zero-pad"), TOO_FEW_LINES_READ_LINES("too few lines read in readLineWRITE_ONs"), INVALID_CONNECTION("invalid connection"), diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/ConnectionSupport.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/ConnectionSupport.java index 36d4ececb750d7a6a25fc9a04a1e0616b95cc020..90f36ccc73bf9fa692d2ff143bc1f6d1128f46e5 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/ConnectionSupport.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/ConnectionSupport.java @@ -520,9 +520,9 @@ public class ConnectionSupport implements RContext.StateFactory { } @Override - public void writeLines(RAbstractStringVector lines, String sep) throws IOException { + public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { checkOpen(); - theConnection.writeLines(lines, sep); + theConnection.writeLines(lines, sep, useBytes); } @Override @@ -825,7 +825,7 @@ public class ConnectionSupport implements RContext.StateFactory { } @Override - public void writeLines(RAbstractStringVector lines, String sep) throws IOException { + public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { throw new IOException(RError.Message.CANNOT_WRITE_CONNECTION.message); } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/FileConnections.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/FileConnections.java index c5bf361d6617f58f0b1aeda0038199cb7896f6a3..3f750ff68c99aaf2b486e61d0a1c50da1e116463 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/FileConnections.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/FileConnections.java @@ -167,7 +167,7 @@ public class FileConnections { } @Override - public void writeLines(RAbstractStringVector lines, String sep) throws IOException { + public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { writeLinesHelper(outputStream, lines, sep); flush(); } @@ -282,7 +282,7 @@ public class FileConnections { } @Override - public void writeLines(RAbstractStringVector lines, String sep) throws IOException { + public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { for (int i = 0; i < lines.getLength(); i++) { String line = lines.getDataAt(i); outputStream.write(line.getBytes()); @@ -308,8 +308,15 @@ public class FileConnections { * useless. Life would be a little better if we converted everything to channels, as it does * support those. This is a minimal implementation to support one specific use in package * installation (write only). + * + * N.B. R mandates separate "position" offsets for reading and writing (pain). This code is + * pessimistic and assumes interleaved reads and writes, so does a lot of probably redundant + * seeking. It could be optimized. */ private final RandomAccessFile raf; + private long readOffset; + private long writeOffset; + private SeekRWMode lastMode = SeekRWMode.READ; FileReadWriteConnection(FileRConnection base) throws IOException { super(base); @@ -326,6 +333,43 @@ public class FileConnections { raf = new RandomAccessFile(base.path, rafMode); } + @Override + public int getc() throws IOException { + raf.seek(readOffset); + int value = raf.read(); + readOffset++; + return value; + } + + @Override + public boolean isSeekable() { + return true; + } + + @Override + public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { + long result = raf.getFilePointer(); + if (seekMode != SeekMode.START) { + throw RError.nyi(null, "seek mode"); + } + switch (seekRWMode) { + case LAST: + if (lastMode == SeekRWMode.READ) { + readOffset = offset; + } else { + writeOffset = offset; + } + break; + case READ: + readOffset = offset; + break; + case WRITE: + writeOffset = offset; + break; + } + return result; + } + @Override public String[] readLinesInternal(int n) throws IOException { throw RInternalError.unimplemented(); @@ -353,12 +397,17 @@ public class FileConnections { } @Override - public void writeLines(RAbstractStringVector lines, String sep) throws IOException { + public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { + // TODO encodings + raf.seek(writeOffset); + byte[] sepData = sep.getBytes(); for (int i = 0; i < lines.getLength(); i++) { - raf.writeChars(lines.getDataAt(i)); - raf.writeChars(sep); + byte[] data = lines.getDataAt(i).getBytes(); + raf.write(data); + raf.write(sepData); } - + writeOffset = raf.getFilePointer(); + lastMode = SeekRWMode.WRITE; } @Override diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/GZIPConnections.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/GZIPConnections.java index fa16e679b71e21f0940180af0095dffafb6a761e..7bcd98243fee5991ddc6ea53c672e02d0adfb3f6 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/GZIPConnections.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/GZIPConnections.java @@ -147,7 +147,7 @@ public class GZIPConnections { } @Override - public void writeLines(RAbstractStringVector lines, String sep) throws IOException { + public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { writeLinesHelper(outputStream, lines, sep); } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/RConnection.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/RConnection.java index c14f081ec3b5312cf2fbedf5e383e7ee53bef4f6..768545b0c7b10982c18d23153a926069ba69be64 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/RConnection.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/RConnection.java @@ -234,11 +234,39 @@ public abstract class RConnection implements RClassHierarchy, RAttributable, RTy pushBack = null; } + /** + * Internal support for reading one character at a time. + */ + public int getc() throws IOException { + return getInputStream().read(); + } + + public boolean isSeekable() { + return false; + } + + public enum SeekMode { + START, + CURRENT, + END + } + + public enum SeekRWMode { + LAST, + READ, + WRITE + } + + @SuppressWarnings("unused") + public long seek(long offset, SeekMode seekMode, SeekRWMode seekRWMode) throws IOException { + throw RError.error(RError.Message.UNSEEKABLE_CONNECTION); + } + /** * Write the {@code lines} to the connection, with {@code sep} appended after each "line". N.B. * The output will only appear as a sequence of lines if {@code sep == "\n"}. */ - public abstract void writeLines(RAbstractStringVector lines, String sep) throws IOException; + public abstract void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException; public abstract void flush() throws IOException; diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/SocketConnections.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/SocketConnections.java index 15ae98751d4386e8b03bc6037874459552901117..4c3dad02c987e3250a9c5084c54945368ad252d7 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/SocketConnections.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/SocketConnections.java @@ -112,7 +112,7 @@ public class SocketConnections { } @Override - public void writeLines(RAbstractStringVector lines, String sep) throws IOException { + public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { writeLinesHelper(outputStream, lines, sep); } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/StdConnections.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/StdConnections.java index db4a9b661133c6f99edf28f126ae0f84bd042713..1a5d595cde091e8f8f709b1559e14c486adc7742 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/StdConnections.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/StdConnections.java @@ -249,7 +249,7 @@ public class StdConnections implements RContext.StateFactory { } @Override - public void writeLines(RAbstractStringVector lines, String sep) throws IOException { + public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { /* * It is more efficient to test for diversion, as this is the most common entry point. */ @@ -260,7 +260,7 @@ public class StdConnections implements RContext.StateFactory { writeString(sep, false); } } else { - diversions[top].conn.writeLines(lines, sep); + diversions[top].conn.writeLines(lines, sep, useBytes); } } @@ -332,7 +332,7 @@ public class StdConnections implements RContext.StateFactory { } @Override - public void writeLines(RAbstractStringVector lines, String sep) throws IOException { + public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { /* * It is more efficient to test for diversion, as this is the most common entry point. */ @@ -343,7 +343,7 @@ public class StdConnections implements RContext.StateFactory { writeString(sep, false); } } else { - diversion.writeLines(lines, sep); + diversion.writeLines(lines, sep, useBytes); } } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/TextConnections.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/TextConnections.java index 57c273b9c3fc8465bfd6e6817580182f7259547d..3c30eec03dff7f44e65917aa0d036f6c544e8d88 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/TextConnections.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/conn/TextConnections.java @@ -109,7 +109,7 @@ public class TextConnections { @SuppressWarnings("hiding") @Override - public void writeLines(RAbstractStringVector lines, String sep) throws IOException { + public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { throw new IOException(RError.Message.CANNOT_WRITE_CONNECTION.message); } @@ -223,7 +223,7 @@ public class TextConnections { } @Override - public void writeLines(RAbstractStringVector lines, String sep) throws IOException { + public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { StringBuffer sb = new StringBuffer(); if (incompleteLine != null) { sb.append(incompleteLine); @@ -282,7 +282,7 @@ public class TextConnections { } @Override - public void writeLines(RAbstractStringVector lines, String sep) throws IOException { + public void writeLines(RAbstractStringVector lines, String sep, boolean useBytes) throws IOException { for (int i = 0; i < lines.getLength(); i++) { String line = lines.getDataAt(i); sb.append(line);