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 8d22b579121f52be4927c7d92137a1c215b342b5..de7553e14890f9674cfdf7752798dc06741e9fd6 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
@@ -168,6 +168,12 @@ public abstract class ConnectionFunctions {
             return theConnection.getInputStream();
         }
 
+        @Override
+        public OutputStream getOutputStream() throws IOException {
+            checkOpen();
+            return theConnection.getOutputStream();
+        }
+
         @Override
         public void writeLines(RAbstractStringVector lines, String sep) throws IOException {
             checkOpen();
@@ -233,6 +239,10 @@ public abstract class ConnectionFunctions {
             // nothing to do
         }
 
+        @Override
+        public OutputStream getOutputStream() {
+            throw RInternalError.shouldNotReachHere();
+        }
     }
 
     private abstract static class DelegateWriteRConnection extends DelegateRConnection {
@@ -245,6 +255,11 @@ public abstract class ConnectionFunctions {
             throw new IOException(RError.Message.CANNOT_READ_CONNECTION.message);
         }
 
+        @Override
+        public InputStream getInputStream() {
+            throw RInternalError.shouldNotReachHere();
+        }
+
     }
 
     /**
@@ -285,6 +300,11 @@ public abstract class ConnectionFunctions {
             throw RInternalError.shouldNotReachHere();
         }
 
+        @Override
+        public OutputStream getOutputStream() {
+            throw RInternalError.shouldNotReachHere();
+        }
+
         @Override
         public void close() throws IOException {
             throw RInternalError.shouldNotReachHere();
@@ -400,8 +420,8 @@ public abstract class ConnectionFunctions {
         }
 
         @Override
-        public InputStream getInputStream() throws IOException {
-            throw RInternalError.shouldNotReachHere();
+        public OutputStream getOutputStream() throws IOException {
+            return outputStream;
         }
 
         @Override
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/ForeignFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/ForeignFunctions.java
index 27659bc2c4796d106e2ae4c51982fc617c92e1c9..2d12a2bf032f27e981b2060119c56c7daf82bec4 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/ForeignFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/ForeignFunctions.java
@@ -32,7 +32,7 @@ import com.oracle.truffle.api.utilities.*;
 import com.oracle.truffle.r.nodes.*;
 import com.oracle.truffle.r.nodes.access.*;
 import com.oracle.truffle.r.nodes.builtin.*;
-import com.oracle.truffle.r.nodes.builtin.utils.CountFields;
+import com.oracle.truffle.r.nodes.builtin.utils.*;
 import com.oracle.truffle.r.nodes.unary.*;
 import com.oracle.truffle.r.runtime.*;
 import com.oracle.truffle.r.runtime.RError.Message;
@@ -582,9 +582,12 @@ public class ForeignFunctions {
         protected Object doWriteTable(VirtualFrame frame, @SuppressWarnings("unused") RList f, RArgsValuesAndNames args) {
             controlVisibility();
             Object[] argValues = args.getValues();
-            Object con = argValues[1];
-            if (!(con instanceof RConnection)) {
+            Object conArg = argValues[1];
+            RConnection con;
+            if (!(conArg instanceof RConnection)) {
                 throw RError.error(getEncapsulatingSourceSection(), RError.Message.GENERIC, "'file' is not a connection");
+            } else {
+                con = (RConnection) conArg;
             }
             // TODO check connection writeable
 
@@ -598,7 +601,10 @@ public class ForeignFunctions {
             Object quoteArg = argValues[9];
             byte qmethod = castLogical(frame, argValues[10]);
 
-            String dec;
+            String csep;
+            String ceol;
+            String cna;
+            String cdec;
 
             if (nr == RRuntime.INT_NA) {
                 invalidArgument("nr");
@@ -609,33 +615,41 @@ public class ForeignFunctions {
             if (!(rnamesArg instanceof RNull) && isString(rnamesArg) == null) {
                 invalidArgument("rnames");
             }
-            if (isString(sepArg) == null) {
+            if ((csep = isString(sepArg)) == null) {
                 invalidArgument("sep");
             }
-            if (isString(eolArg) == null) {
+            if ((ceol = isString(eolArg)) == null) {
                 invalidArgument("eol");
             }
-            if (isString(naArg) == null) {
+            if ((cna = isString(naArg)) == null) {
                 invalidArgument("na");
             }
-            if ((dec = isString(decArg)) == null) {
+            if ((cdec = isString(decArg)) == null) {
                 invalidArgument("dec");
             }
             if (qmethod == RRuntime.LOGICAL_NA) {
                 invalidArgument("qmethod");
             }
-            if (dec.length() != 1) {
+            if (cdec.length() != 1) {
                 throw RError.error(getEncapsulatingSourceSection(), RError.Message.GENERIC, "'dec' must be a single character");
             }
             boolean[] quoteCol = new boolean[nc];
+            boolean quoteRn = false;
             RIntVector quote = (RIntVector) quoteArg;
             for (int i = 0; i < quote.getLength(); i++) {
                 int qi = quote.getDataAt(i);
+                if (qi == 0) {
+                    quoteRn = true;
+                }
                 if (qi > 0) {
                     quoteCol[qi - 1] = true;
                 }
             }
-            // TODO call WriteTable
+            try {
+                WriteTable.execute(con, argValues[0], nr, nc, rnamesArg, csep, ceol, cna, cdec.charAt(0), RRuntime.fromLogical(qmethod), quoteCol, quoteRn);
+            } catch (IOException | IllegalArgumentException ex) {
+                throw RError.error(getEncapsulatingSourceSection(), RError.Message.GENERIC, ex.getMessage());
+            }
             return RNull.instance;
         }
 
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/utils/WriteTable.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/utils/WriteTable.java
new file mode 100644
index 0000000000000000000000000000000000000000..362b9cb92ca63f8cea009e462c75007c88e70389
--- /dev/null
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/utils/WriteTable.java
@@ -0,0 +1,172 @@
+/*
+ * This material is distributed under the GNU General Public License
+ * Version 2. You may review the terms of this license at
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * Copyright (c) 1995-2012, The R Core Team
+ * Copyright (c) 2003, The R Foundation
+ * Copyright (c) 2014, Oracle and/or its affiliates
+ *
+ * All rights reserved.
+ */
+package com.oracle.truffle.r.nodes.builtin.utils;
+
+import java.io.*;
+
+import com.oracle.truffle.r.runtime.*;
+import com.oracle.truffle.r.runtime.data.*;
+import com.oracle.truffle.r.runtime.data.model.*;
+
+//Transcribed from GnuR, library/utils/src/io.c
+
+//Checkstyle: stop
+public class WriteTable {
+    // @formatter:off
+    public static Object execute(RConnection con, Object xx, int nr, int nc, Object rnames, String csep, String ceol, String cna,
+                  char cdec, boolean qmethod, boolean[] quoteCol, boolean quoteRn) throws IOException, IllegalArgumentException {
+        // @formatter:on
+        OutputStream os = con.getOutputStream();
+        String tmp = null;
+        if (xx instanceof RDataFrame) { /* A data frame */
+            RVector xy = ((RDataFrame) xx).getVector();
+            RVector x = (RVector) xy.getDataAtAsObject(0);
+
+            /* handle factors internally, check integrity */
+            RStringVector[] levels = new RStringVector[nc];
+            for (int j = 0; j < nc; j++) {
+                RAbstractVector xj = (RAbstractVector) x.getDataAtAsObject(j);
+                if (xj.getLength() != nr) {
+                    throw new IllegalArgumentException("corrupt data frame -- length of column " + (j + 1) + " does not not match nrows");
+                }
+                if (isFactor(xj)) {
+                    levels[j] = (RStringVector) xj.getAttributes().get("levels");
+                }
+            }
+
+            for (int i = 0; i < nr; i++) {
+                // if (i % 1000 == 999)
+                // R_CheckUserInterrupt();
+                if (!(rnames instanceof RNull)) {
+                    os.write(encodeElement2((RStringVector) rnames, i, quoteRn, qmethod, cdec).getBytes());
+                    os.write(csep.getBytes());
+                }
+                for (int j = 0; j < nc; j++) {
+                    RAbstractVector xj = (RAbstractVector) x.getDataAtAsObject(j);
+                    if (j > 0)
+                        os.write(csep.getBytes());
+                    if (isna(xj, i)) {
+                        tmp = cna;
+                    } else {
+                        if (levels[j] != null) {
+                            /*
+                             * We do not assume factors have integer levels, although they should.
+                             */
+                            if (xj instanceof RIntVector || xj instanceof RDoubleVector) {
+                                tmp = encodeElement2(levels[j], (int) xj.getDataAtAsObject(i) - 1, quoteCol[j], qmethod, cdec);
+                            } else {
+                                throw new IllegalArgumentException("column " + (j + 1) + " claims to be a factor but does not have numeric codes");
+                            }
+                        } else {
+                            tmp = encodeElement2(xj, i, quoteCol[j], qmethod, cdec);
+                        }
+                        /* if(cdec) change_dec(tmp, cdec, TYPEOF(xj)); */
+                    }
+                    os.write(tmp.getBytes());
+                }
+                os.write(ceol.getBytes());
+            }
+
+        } else { /* A matrix */
+
+            // if (!isVectorAtomic(x))
+            // UNIMPLEMENTED_TYPE("write.table, matrix method", x);
+            RVector x = (RVector) xx;
+            /* quick integrity check */
+            if (x.getLength() != nr * nc) {
+                throw new IllegalArgumentException("corrupt matrix -- dims not not match length");
+            }
+
+            for (int i = 0; i < nr; i++) {
+                if (i % 1000 == 999) {
+                    // R_CheckUserInterrupt();
+                }
+                if (!(rnames instanceof RNull)) {
+                    os.write(encodeElement2((RStringVector) rnames, i, quoteRn, qmethod, cdec).getBytes());
+                    os.write(csep.getBytes());
+                }
+                for (int j = 0; j < nc; j++) {
+                    if (j > 0) {
+                        os.write(csep.getBytes());
+                    }
+                    if (isna(x, i + j * nr)) {
+                        tmp = cna;
+                    } else {
+                        tmp = encodeElement2(x, i + j * nr, quoteCol[j], qmethod, cdec);
+                        /* if(cdec) change_dec(tmp, cdec, TYPEOF(x)); */
+                    }
+                    os.write(tmp.getBytes());
+                }
+                os.write(ceol.getBytes());
+            }
+
+        }
+        return RNull.instance;
+    }
+
+    /* a version of EncodeElement with different escaping of char strings */
+    private static String encodeElement2(RAbstractVector x, int indx, boolean quote, boolean qmethod, char cdec) {
+        if (indx < 0 || indx >= x.getLength()) {
+            throw new IllegalArgumentException("index out of range");
+        }
+        if (x instanceof RStringVector) {
+            RStringVector sx = (RStringVector) x;
+            StringBuffer sb = new StringBuffer();
+            String p0 = /* translateChar */sx.getDataAt(indx);
+            if (!quote)
+                return p0;
+            sb.append('"');
+            for (int i = 0; i < p0.length(); i++) {
+                char p = p0.charAt(i);
+                if (p == '"') {
+                    sb.append(qmethod ? '\\' : '"');
+                }
+                sb.append(p);
+            }
+            sb.append('"');
+            return sb.toString();
+        }
+        return encodeElement(x, indx, quote ? '"' : 0, cdec);
+    }
+
+    private static boolean isna(RAbstractVector x, int indx) {
+        if (x instanceof RLogicalVector) {
+            return RRuntime.isNA(((RLogicalVector) x).getDataAt(indx));
+        } else if (x instanceof RDoubleVector) {
+            return RRuntime.isNA(((RDoubleVector) x).getDataAt(indx));
+        } else if (x instanceof RIntVector) {
+            return RRuntime.isNA(((RIntVector) x).getDataAt(indx));
+        } else if (x instanceof RStringVector) {
+            return RRuntime.isNA(((RStringVector) x).getDataAt(indx));
+        } else if (x instanceof RComplexVector) {
+            RComplexVector cvec = (RComplexVector) x;
+            RComplex c = cvec.getDataAt(indx);
+            return c.isNA();
+        } else {
+            return false;
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private static String encodeElement(Object x, int indx, char quote, char dec) {
+        throw RInternalError.unimplemented();
+    }
+
+    private static boolean isFactor(RAbstractVector v) {
+        for (int i = 0; i < v.getClassHierarchy().getLength(); i++) {
+            if (v.getClassHierarchy().getDataAt(i).equals("factor")) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RConnection.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RConnection.java
index 760444abf70eb99ff0857f3f95f4984de75518e5..5ca7160374ec866c2568f03c256f56a51631f99b 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RConnection.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RConnection.java
@@ -123,10 +123,17 @@ public abstract class RConnection implements RClassHierarchy {
     }
 
     /**
-     * Return the underlying input stream (for internal use).
+     * Return the underlying input stream (for internal use). TODO Replace with a more principled
+     * solution.
      */
     public abstract InputStream getInputStream() throws IOException;
 
+    /**
+     * Return the underlying output stream (for internal use). TODO Replace with a more principled
+     * solution.
+     */
+    public abstract OutputStream getOutputStream() throws IOException;
+
     /**
      * Close the connection.
      */