From e87f927bcef5186f2e452b41c53e3012f43be4fb Mon Sep 17 00:00:00 2001
From: Mick Jordan <mick.jordan@oracle.com>
Date: Tue, 2 Jun 2015 18:20:10 -0700
Subject: [PATCH] seek support for connections (limited)

---
 .../r/nodes/builtin/base/BasePackage.java     |  2 +
 .../truffle/r/nodes/builtin/base/Cat.java     |  2 +-
 .../builtin/base/ConnectionFunctions.java     | 27 +++++++-
 .../com/oracle/truffle/r/runtime/RError.java  |  1 +
 .../r/runtime/conn/ConnectionSupport.java     |  6 +-
 .../r/runtime/conn/FileConnections.java       | 61 +++++++++++++++++--
 .../r/runtime/conn/GZIPConnections.java       |  2 +-
 .../truffle/r/runtime/conn/RConnection.java   | 30 ++++++++-
 .../r/runtime/conn/SocketConnections.java     |  2 +-
 .../r/runtime/conn/StdConnections.java        |  8 +--
 .../r/runtime/conn/TextConnections.java       |  6 +-
 11 files changed, 125 insertions(+), 22 deletions(-)

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 328873efa0..575c68930a 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 705fa0b1c8..08208779da 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 47e726f4fc..e8aa244440 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 61b32b972a..f0c5c535a2 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 36d4ececb7..90f36ccc73 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 c5bf361d66..3f750ff68c 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 fa16e679b7..7bcd98243f 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 c14f081ec3..768545b0c7 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 15ae98751d..4c3dad02c9 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 db4a9b6611..1a5d595cde 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 57c273b9c3..3c30eec03d 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);
-- 
GitLab