diff --git a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/REngine.java b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/REngine.java
index ec84baa9ed60d1161117164700bfa7e8d2c38e46..fe2eb079f49965a9bee08d91dbbc37b11f1d4a35 100644
--- a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/REngine.java
+++ b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/REngine.java
@@ -282,6 +282,7 @@ public final class REngine implements RContext.Engine {
             ConsoleHandler ch = singleton.context.getConsoleHandler();
             ch.println("Unsupported specialization in node " + use.getNode().getClass().getSimpleName() + " - supplied values: " +
                             Arrays.asList(use.getSuppliedValues()).stream().map(v -> v.getClass().getSimpleName()).collect(Collectors.toList()));
+            use.printStackTrace();
             return null;
         } catch (RecognitionException | RuntimeException e) {
             singleton.context.getConsoleHandler().println("Exception while parsing: " + e);
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 778bf01e2710667f148c82e83c991075bec38dfc..8d22b579121f52be4927c7d92137a1c215b342b5 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
@@ -87,6 +87,7 @@ public abstract class ConnectionFunctions {
     // TODO remove invisibility when print for RConnection works
     // TODO implement all open modes
     // TODO implement missing .Internal functions expected by connections.R
+    // TODO revisit the use of InputStream for internal use, e.g. in RSerialize
 
     /**
      * Base class for all {@link RConnection} instances. It supports lazy opening, as required by
@@ -98,6 +99,8 @@ public abstract class ConnectionFunctions {
      *
      */
     private abstract static class BaseRConnection extends RConnection {
+        private static final RStringVector DEFAULT_CLASSHR = RDataFactory.createStringVector(new String[]{"connection"}, RDataFactory.COMPLETE_VECTOR);
+
         protected boolean isOpen;
         /**
          * if {@link #isOpen} is {@code true} the {@link OpenMode} that this connection is opened
@@ -165,15 +168,32 @@ public abstract class ConnectionFunctions {
             return theConnection.getInputStream();
         }
 
+        @Override
+        public void writeLines(RAbstractStringVector lines, String sep) throws IOException {
+            checkOpen();
+            theConnection.writeLines(lines, sep);
+        }
+
+        @Override
+        public void flush() throws IOException {
+            checkOpen();
+            theConnection.flush();
+        }
+
         @Override
         public void close() throws IOException {
             isOpen = false;
-            theConnection.close();
+            if (theConnection != null) {
+                theConnection.close();
+            }
         }
 
         @Override
+        /**
+         * Must always respond to {@code inherits("connection")} even when not open.
+         */
         public RStringVector getClassHierarchy() {
-            return classHr;
+            return classHr != null ? classHr : DEFAULT_CLASSHR;
         }
 
         /**
@@ -196,6 +216,34 @@ public abstract class ConnectionFunctions {
         public RStringVector getClassHierarchy() {
             return base.getClassHierarchy();
         }
+    }
+
+    private abstract static class DelegateReadRConnection extends DelegateRConnection {
+        protected DelegateReadRConnection(BaseRConnection base) {
+            super(base);
+        }
+
+        @Override
+        public void writeLines(RAbstractStringVector lines, String sep) throws IOException {
+            throw new IOException(RError.Message.CANNOT_WRITE_CONNECTION.message);
+        }
+
+        @Override
+        public void flush() {
+            // nothing to do
+        }
+
+    }
+
+    private abstract static class DelegateWriteRConnection extends DelegateRConnection {
+        protected DelegateWriteRConnection(BaseRConnection base) {
+            super(base);
+        }
+
+        @Override
+        public String[] readLinesInternal(int n) throws IOException {
+            throw new IOException(RError.Message.CANNOT_READ_CONNECTION.message);
+        }
 
     }
 
@@ -210,6 +258,11 @@ public abstract class ConnectionFunctions {
             setClass("terminal");
         }
 
+        @Override
+        public boolean isStdin() {
+            return true;
+        }
+
         @Override
         @TruffleBoundary
         public String[] readLinesInternal(int n) throws IOException {
@@ -281,6 +334,9 @@ public abstract class ConnectionFunctions {
                 case Read:
                     delegate = new FileReadTextRConnection(this);
                     break;
+                case Write:
+                    delegate = new FileWriteTextRConnection(this);
+                    break;
                 default:
                     throw RError.nyi((SourceSection) null, "unimplemented open mode: " + mode);
             }
@@ -288,12 +344,14 @@ public abstract class ConnectionFunctions {
         }
     }
 
-    private static class FileReadTextRConnection extends DelegateRConnection {
+    private static class FileReadTextRConnection extends DelegateReadRConnection {
+        private BufferedInputStream inputStream;
         private BufferedReader bufferedReader;
 
         FileReadTextRConnection(FileRConnection base) throws IOException {
             super(base);
-            bufferedReader = new BufferedReader(new FileReader(base.path));
+            inputStream = new BufferedInputStream(new FileInputStream(base.path));
+            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
         }
 
         @TruffleBoundary
@@ -314,7 +372,7 @@ public abstract class ConnectionFunctions {
 
         @Override
         public InputStream getInputStream() throws IOException {
-            throw new IOException();
+            return inputStream;
         }
 
         @Override
@@ -323,6 +381,40 @@ public abstract class ConnectionFunctions {
         }
     }
 
+    private static class FileWriteTextRConnection extends DelegateWriteRConnection {
+        private BufferedOutputStream outputStream;
+        private BufferedWriter bufferedWriter;
+
+        FileWriteTextRConnection(FileRConnection base) throws IOException {
+            super(base);
+            outputStream = new BufferedOutputStream(new FileOutputStream(base.path));
+            bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
+        }
+
+        @Override
+        public void writeLines(RAbstractStringVector lines, String sep) throws IOException {
+            for (int i = 0; i < lines.getLength(); i++) {
+                bufferedWriter.write(lines.getDataAt(i));
+                bufferedWriter.write(sep);
+            }
+        }
+
+        @Override
+        public InputStream getInputStream() throws IOException {
+            throw RInternalError.shouldNotReachHere();
+        }
+
+        @Override
+        public void close() throws IOException {
+            bufferedWriter.close();
+        }
+
+        @Override
+        public void flush() throws IOException {
+            bufferedWriter.flush();
+        }
+    }
+
     @RBuiltin(name = "file", kind = INTERNAL, parameterNames = {"description", "open", "blocking", "encoding", "raw"})
     public abstract static class File extends RInvisibleBuiltinNode {
         @Specialization
@@ -375,7 +467,7 @@ public abstract class ConnectionFunctions {
         }
     }
 
-    private static class GZIPInputRConnection extends DelegateRConnection {
+    private static class GZIPInputRConnection extends DelegateReadRConnection {
         private GZIPInputStream stream;
 
         GZIPInputRConnection(GZIPRConnection base) throws IOException {
@@ -449,7 +541,7 @@ public abstract class ConnectionFunctions {
         }
     }
 
-    private static class TextReadRConnection extends DelegateRConnection {
+    private static class TextReadRConnection extends DelegateReadRConnection {
         private String[] lines;
         private int index;
 
@@ -478,6 +570,12 @@ public abstract class ConnectionFunctions {
             return result;
         }
 
+        @SuppressWarnings("hiding")
+        @Override
+        public void writeLines(RAbstractStringVector lines, String sep) throws IOException {
+            throw new IOException(RError.Message.CANNOT_WRITE_CONNECTION.message);
+        }
+
         @Override
         public InputStream getInputStream() throws IOException {
             throw RInternalError.shouldNotReachHere();
@@ -552,6 +650,43 @@ public abstract class ConnectionFunctions {
         }
     }
 
+    @RBuiltin(name = "writeLines", kind = INTERNAL, parameterNames = {"text", "con", "sep", "useBytes"})
+    public abstract static class WriteLines extends RInvisibleBuiltinNode {
+        @Specialization
+        @TruffleBoundary
+        protected RNull writeLines(RAbstractStringVector text, RConnection con, RAbstractStringVector sep, @SuppressWarnings("unused") byte useBytes) {
+            controlVisibility();
+            try {
+                con.writeLines(text, sep.getDataAt(0));
+            } catch (IOException x) {
+                throw RError.error(getEncapsulatingSourceSection(), RError.Message.ERROR_WRITING_CONNECTION, x.getMessage());
+            }
+            return RNull.instance;
+        }
+
+        @SuppressWarnings("unused")
+        @Fallback
+        protected RNull writeLines(Object text, Object con, Object sep, Object useBytes) {
+            controlVisibility();
+            throw RError.error(getEncapsulatingSourceSection(), RError.Message.INVALID_UNNAMED_ARGUMENTS);
+        }
+    }
+
+    @RBuiltin(name = "flush", kind = INTERNAL, parameterNames = {"con"})
+    public abstract static class Flush extends RInvisibleBuiltinNode {
+        @TruffleBoundary
+        @Specialization
+        protected RNull flush(RConnection con) {
+            controlVisibility();
+            try {
+                con.flush();
+            } catch (IOException x) {
+                throw RError.error(getEncapsulatingSourceSection(), RError.Message.ERROR_FLUSHING_CONNECTION, x.getMessage());
+            }
+            return RNull.instance;
+        }
+    }
+
     @RBuiltin(name = "pushBack", kind = INTERNAL, parameterNames = {"data", "connection", "newLine", "type"})
     public abstract static class PushBack extends RInvisibleBuiltinNode {
 
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 5757cfe6ec0109e474c209212d850f55a12f2c46..41bb9c8adaf5989b8d7c706ea9a6a388d170e6b7 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
@@ -22,13 +22,17 @@
  */
 package com.oracle.truffle.r.nodes.builtin.base;
 
+import java.io.*;
+
 import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.dsl.*;
 import com.oracle.truffle.api.frame.*;
 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.unary.*;
 import com.oracle.truffle.r.runtime.*;
 import com.oracle.truffle.r.runtime.RError.Message;
@@ -401,24 +405,6 @@ public class ForeignFunctions {
             return RDataFactory.createComplexVector(z, zVec.isComplete(), zVec.getDimensions());
         }
 
-        /**
-         * This is an inefficient guard but it matters little unless there are many different calls
-         * being made within the same evaluation. A {@code NativeSymbolInfo} object would provide a
-         * more efficient basis.
-         */
-        private static boolean matchName(RList f, String name) {
-            if (f.getNames() == RNull.instance) {
-                return false;
-            }
-            RStringVector names = (RStringVector) f.getNames();
-            for (int i = 0; i < names.getLength(); i++) {
-                if (names.getDataAt(i).equals("name")) {
-                    return f.getDataAt(i).equals(name) ? true : false;
-                }
-            }
-            return false;
-        }
-
         public boolean fft(RList f) {
             return matchName(f, "fft");
         }
@@ -467,4 +453,208 @@ public class ForeignFunctions {
 
     }
 
+    /**
+     * This is an inefficient guard but it matters little unless there are many different calls
+     * being made within the same evaluation. A {@code NativeSymbolInfo} object would provide a more
+     * efficient basis.
+     */
+    private static boolean matchName(RList f, String name) {
+        if (f.getNames() == RNull.instance) {
+            return false;
+        }
+        RStringVector names = (RStringVector) f.getNames();
+        for (int i = 0; i < names.getLength(); i++) {
+            if (names.getDataAt(i).equals("name")) {
+                return f.getDataAt(i).equals(name) ? true : false;
+            }
+        }
+        return false;
+    }
+
+    private static String isString(Object arg) {
+        if (arg instanceof String) {
+            return (String) arg;
+        } else if (arg instanceof RStringVector) {
+            if (((RStringVector) arg).getLength() == 0) {
+                return null;
+            } else {
+                return ((RStringVector) arg).getDataAt(0);
+            }
+        } else {
+            return null;
+        }
+    }
+
+    private abstract static class CastAdapter extends RBuiltinNode {
+        @Child private CastLogicalNode castLogical;
+        @Child private CastIntegerNode castInt;
+
+        protected byte castLogical(VirtualFrame frame, Object operand) {
+            if (castLogical == null) {
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                castLogical = insert(CastLogicalNodeFactory.create(null, false, false, false));
+            }
+            return (byte) castLogical.executeCast(frame, operand);
+        }
+
+        protected int castInt(VirtualFrame frame, Object operand) {
+            if (castInt == null) {
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                castInt = insert(CastIntegerNodeFactory.create(null, false, false, false));
+            }
+            return (int) castInt.executeCast(frame, operand);
+        }
+    }
+
+    @RBuiltin(name = ".External", kind = RBuiltinKind.PRIMITIVE, parameterNames = {".NAME", "...", "PACKAGE"})
+    public abstract static class DotExternal extends CastAdapter {
+
+        // Transcribed from GnuR, library/utils/src/io.c
+        @SuppressWarnings("unused")
+        @TruffleBoundary
+        @Specialization(guards = "isCountFields")
+        protected Object countFields(VirtualFrame frame, RList f, RArgsValuesAndNames args, RMissing packageName) {
+            controlVisibility();
+            Object[] argValues = args.getValues();
+            RConnection conn = (RConnection) argValues[0];
+            Object sepArg = argValues[1];
+            char sepChar;
+            Object quoteArg = argValues[2];
+            int nskip = castInt(frame, argValues[3]);
+            byte blskip = castLogical(frame, argValues[4]);
+            String commentCharArg = isString(argValues[5]);
+            char comChar;
+            if (!(commentCharArg != null && commentCharArg.length() == 1)) {
+                throw RError.error(getEncapsulatingSourceSection(), RError.Message.INVALID_ARGUMENT, "comment.char");
+            } else {
+                comChar = commentCharArg.charAt(0);
+            }
+
+            if (nskip < 0 || nskip == RRuntime.INT_NA) {
+                nskip = 0;
+            }
+            if (blskip == RRuntime.LOGICAL_NA) {
+                blskip = RRuntime.LOGICAL_TRUE;
+            }
+
+            if (sepArg instanceof RNull) {
+                sepChar = 0;
+            } else {
+                String s = isString(sepArg);
+                if (s == null) {
+                    throw RError.error(getEncapsulatingSourceSection(), RError.Message.INVALID_ARGUMENT, "sep");
+                } else {
+                    if (s.length() == 0) {
+                        sepChar = 0;
+                    } else {
+                        sepChar = s.charAt(0);
+                    }
+                }
+            }
+            String quoteSet;
+            if (quoteArg instanceof RNull) {
+                quoteSet = "";
+            } else {
+                String s = isString(quoteArg);
+                if (s == null) {
+                    throw RError.error(getEncapsulatingSourceSection(), RError.Message.GENERIC, "invalid quote symbol set");
+                } else {
+                    quoteSet = s;
+                }
+            }
+            try {
+                return CountFields.execute(conn, sepChar, quoteSet, nskip, RRuntime.fromLogical(blskip), comChar);
+            } catch (IllegalStateException | IOException ex) {
+                throw RError.error(getEncapsulatingSourceSection(), RError.Message.GENERIC, ex.getMessage());
+            }
+        }
+
+        public boolean isCountFields(RList f) {
+            return matchName(f, "countfields");
+        }
+    }
+
+    @RBuiltin(name = ".External2", kind = RBuiltinKind.PRIMITIVE, parameterNames = {".NAME", "..."})
+    public abstract static class DotExternal2 extends CastAdapter {
+        // Transcribed from GnuR, library/utils/src/io.c
+        @TruffleBoundary
+        @Specialization(guards = "isWriteTable")
+        protected Object doWriteTable(VirtualFrame frame, RList f, RArgsValuesAndNames args) {
+            controlVisibility();
+            Object[] argValues = args.getValues();
+            Object x = argValues[0];
+            Object con = argValues[1];
+            if (!(con instanceof RConnection)) {
+                throw RError.error(getEncapsulatingSourceSection(), RError.Message.GENERIC, "'file' is not a connection");
+            }
+            // TODO check connection writeable
+
+            int nr = castInt(frame, argValues[2]);
+            int nc = castInt(frame, argValues[3]);
+            Object rnamesArg = argValues[4];
+            Object sepArg = argValues[5];
+            Object eolArg = argValues[6];
+            Object naArg = argValues[7];
+            Object decArg = argValues[8];
+            Object quoteArg = argValues[9];
+            byte qmethod = castLogical(frame, argValues[10]);
+
+            String sep;
+            String eol;
+            String na;
+            String dec;
+
+            if (nr == RRuntime.INT_NA) {
+                invalidArgument("nr");
+            }
+            if (nc == RRuntime.INT_NA) {
+                invalidArgument("nc");
+            }
+            if (!(rnamesArg instanceof RNull) && isString(rnamesArg) == null) {
+                invalidArgument("rnames");
+            }
+            if ((sep = isString(sepArg)) == null) {
+                invalidArgument("sep");
+            }
+            if ((eol = isString(eolArg)) == null) {
+                invalidArgument("eol");
+            }
+            if ((na = isString(naArg)) == null) {
+                invalidArgument("na");
+            }
+            if ((dec = isString(decArg)) == null) {
+                invalidArgument("dec");
+            }
+            if (qmethod == RRuntime.LOGICAL_NA) {
+                invalidArgument("qmethod");
+            }
+            if (dec.length() != 1) {
+                throw RError.error(getEncapsulatingSourceSection(), RError.Message.GENERIC, "'dec' must be a single character");
+            }
+            char cdec = dec.charAt(0);
+            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
+            return RNull.instance;
+        }
+
+        protected void invalidArgument(String name) throws RError {
+            throw RError.error(getEncapsulatingSourceSection(), RError.Message.INVALID_ARGUMENT, name);
+        }
+
+        public boolean isWriteTable(RList f) {
+            return matchName(f, "writetable");
+        }
+    }
+
 }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Lapply.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Lapply.java
index ff282cbe1ff9c5a0e385de24d7796c5f4d71273b..de63f19076c91492e70bf0bdd03a898a3a2617b2 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Lapply.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Lapply.java
@@ -13,6 +13,7 @@ package com.oracle.truffle.r.nodes.builtin.base;
 
 import static com.oracle.truffle.r.runtime.RBuiltinKind.*;
 
+import com.oracle.truffle.api.*;
 import com.oracle.truffle.api.dsl.*;
 import com.oracle.truffle.api.frame.*;
 import com.oracle.truffle.r.nodes.*;
@@ -26,6 +27,9 @@ import com.oracle.truffle.r.runtime.data.model.*;
 public abstract class Lapply extends RBuiltinNode {
 
     @Child private CallInlineCacheNode callCache = CallInlineCacheNode.create(3);
+    @Child private Lapply dataFrameLapply;
+
+    public abstract Object execute(VirtualFrame frame, RAbstractVector x, RFunction fun, RArgsValuesAndNames optionalArgs);
 
     @Override
     public RNode[] getParameterValues() {
@@ -76,4 +80,13 @@ public abstract class Lapply extends RBuiltinNode {
     protected boolean argMissing(RAbstractVector x, RFunction fun, RArgsValuesAndNames optionalArg) {
         return optionalArg.length() == 1 && optionalArg.getValues()[0] == RMissing.instance;
     }
+
+    @Specialization
+    protected Object lapply(VirtualFrame frame, RDataFrame x, RFunction fun, RArgsValuesAndNames optionalArgs) {
+        if (dataFrameLapply == null) {
+            CompilerDirectives.transferToInterpreterAndInvalidate();
+            dataFrameLapply = insert(LapplyFactory.create(new RNode[3], getBuiltin(), getSuppliedArgsNames()));
+        }
+        return dataFrameLapply.execute(frame, x.getVector(), fun, optionalArgs);
+    }
 }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Substitute.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Substitute.java
index 5d11df287d82564acac5a77ad4591611d4951e17..0cb8efffbe8476bbedad9d82ca7b1f6a69def170 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Substitute.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Substitute.java
@@ -26,6 +26,7 @@ import static com.oracle.truffle.r.runtime.RBuiltinKind.*;
 
 import java.util.*;
 
+import com.oracle.truffle.api.*;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.dsl.*;
 import com.oracle.truffle.api.frame.*;
@@ -61,6 +62,7 @@ public abstract class Substitute extends RBuiltinNode {
 
     private Quote checkQuote() {
         if (quote == null) {
+            CompilerDirectives.transferToInterpreterAndInvalidate();
             quote = insert(QuoteFactory.create(new RNode[1], getBuiltin(), getSuppliedArgsNames()));
         }
         return quote;
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/TypeConvert.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/TypeConvert.java
index 15e3fe0797bf2c52fba612ea87c133bb35702d9c..03ef8a58d23cd2fabfa762bd1268775a9ead3f8a 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/TypeConvert.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/TypeConvert.java
@@ -165,7 +165,7 @@ public abstract class TypeConvert extends RBuiltinNode {
             RIntVector res = RDataFactory.createIntVector(data, complete);
             res.setAttr("levels", RDataFactory.createStringVector(levelsArray, RDataFactory.COMPLETE_VECTOR));
             res.setAttr("class", RDataFactory.createStringVector("factor"));
-            return res;
+            return RDataFactory.createFactor(res);
         }
     }
 }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateAttr.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateAttr.java
index 69d3d49b10821db3909ce52d7e07a13a352c5264..9cc09ec1e4d84395cd5dea0798e1d1c1645ca325 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateAttr.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateAttr.java
@@ -102,6 +102,8 @@ public abstract class UpdateAttr extends RInvisibleBuiltinNode {
             return RVector.setClassAttr(resultVector, null, container.getElementClass() == RDataFrame.class ? container : null, container.getElementClass() == RFactor.class ? container : null);
         } else if (name.equals(RRuntime.ROWNAMES_ATTR_KEY)) {
             resultVector.setRowNames(null);
+        } else if (name.equals(RRuntime.LEVELS_ATTR_KEY)) {
+            resultVector.setLevels(null);
         } else if (resultVector.getAttributes() != null) {
             resultVector.getAttributes().remove(name);
         }
@@ -140,6 +142,8 @@ public abstract class UpdateAttr extends RInvisibleBuiltinNode {
             return setClassAttrFromObject(resultVector, container, value, getEncapsulatingSourceSection());
         } else if (name.equals(RRuntime.ROWNAMES_ATTR_KEY)) {
             resultVector.setRowNames(castVector(frame, value));
+        } else if (name.equals(RRuntime.LEVELS_ATTR_KEY)) {
+            resultVector.setLevels(castVector(frame, value));
         } else {
             // generic attribute
             resultVector.setAttr(name, value);
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateAttributes.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateAttributes.java
index 2dac8d6ef4276fed1818457c4e39bf0d1a6321e7..e057dd8e85058fa8143487c7c56b1901aef861b5 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateAttributes.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateAttributes.java
@@ -179,11 +179,9 @@ public abstract class UpdateAttributes extends RInvisibleBuiltinNode {
                     UpdateAttr.setClassAttrFromObject(resultVector, container, value, getEncapsulatingSourceSection());
                 }
             } else if (attrName.equals(RRuntime.ROWNAMES_ATTR_KEY)) {
-                if (value == RNull.instance) {
-                    resultVector.setRowNames(null);
-                } else {
-                    resultVector.setRowNames(castVector(virtualFrame, value));
-                }
+                resultVector.setRowNames(castVector(virtualFrame, value));
+            } else if (attrName.equals(RRuntime.LEVELS_ATTR_KEY)) {
+                resultVector.setLevels(castVector(virtualFrame, value));
             } else {
                 if (value == RNull.instance) {
                     resultVector.removeAttr(attrName);
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateLevels.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateLevels.java
index b39f7f345d3a1a6ac8eb05fbca3d8800073cea24..47e1c683d5fb66e1d58d4dc5f5020d9bef174a66 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateLevels.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateLevels.java
@@ -11,8 +11,11 @@
 
 package com.oracle.truffle.r.nodes.builtin.base;
 
+import com.oracle.truffle.api.*;
 import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.frame.*;
 import com.oracle.truffle.r.nodes.builtin.RInvisibleBuiltinNode;
+import com.oracle.truffle.r.nodes.unary.*;
 import com.oracle.truffle.r.runtime.RBuiltin;
 import com.oracle.truffle.r.runtime.RBuiltinKind;
 import com.oracle.truffle.r.runtime.data.*;
@@ -24,6 +27,16 @@ import static com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 // 2nd parameter is "value", but should not be matched against, so ""
 public abstract class UpdateLevels extends RInvisibleBuiltinNode {
 
+    @Child private CastToVectorNode castVector;
+
+    private RAbstractVector castVector(VirtualFrame frame, Object value) {
+        if (castVector == null) {
+            CompilerDirectives.transferToInterpreterAndInvalidate();
+            castVector = insert(CastToVectorNodeFactory.create(null, false, false, false, false));
+        }
+        return (RAbstractVector) castVector.executeObject(frame, value);
+    }
+
     @Specialization
     @TruffleBoundary
     protected RAbstractVector updateLevels(RAbstractVector vector, @SuppressWarnings("unused") RNull levels) {
@@ -34,11 +47,10 @@ public abstract class UpdateLevels extends RInvisibleBuiltinNode {
     }
 
     @Specialization
-    @TruffleBoundary
-    protected RAbstractVector updateLevels(RAbstractVector vector, Object levels) {
+    protected RAbstractVector updateLevels(VirtualFrame frame, RAbstractVector vector, Object levels) {
         controlVisibility();
         RVector v = vector.materialize();
-        v.setLevels(levels);
+        v.setLevels(castVector(frame, levels));
         return v;
     }
 
@@ -52,9 +64,9 @@ public abstract class UpdateLevels extends RInvisibleBuiltinNode {
 
     @Specialization
     @TruffleBoundary
-    protected RFactor updateLevels(RFactor factor, Object levels) {
+    protected RFactor updateLevels(VirtualFrame frame, RFactor factor, Object levels) {
         controlVisibility();
-        factor.getVector().setLevels(levels);
+        factor.getVector().setLevels(castVector(frame, levels));
         return factor;
     }
 
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/utils/CountFields.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/utils/CountFields.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f30642163f5dfc24a1089017606ad8b57401879
--- /dev/null
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/utils/CountFields.java
@@ -0,0 +1,292 @@
+/*
+ * 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.*;
+
+// Transcribed from GnuR, library/utils/src/io.c
+
+// Checkstyle: stop
+@SuppressWarnings("unused")
+public class CountFields {
+    private static final int R_EOF = -1;
+    private static final int SCAN_BLOCKSIZE = 1000;
+
+    private static class LocalData {
+        Object NAstrings;
+        boolean quiet;
+        int sepchar; /* = 0 *//* This gets compared to ints */
+        char decchar = '.'; /* = '.' *//* This only gets compared to chars */
+        String quoteset; /* = NULL */
+        int comchar = 100000; /* = NO_COMCHAR */
+        boolean ttyflag; /* = 0 */
+        RConnection con; /* = NULL */
+        boolean wasopen; /* = FALSE */
+        boolean escapes; /* = FALSE */
+        int save; /* = 0; */
+        boolean isLatin1; /* = FALSE */
+        boolean isUTF8; /* = FALSE */
+        boolean skipNul;
+        byte[] convbuf = new byte[100];
+        InputStream is;
+
+        private void checkClose() {
+            if (wasopen) {
+                try {
+                    con.close();
+                } catch (IOException ex) {
+                    // Ignore
+                }
+            }
+        }
+
+    }
+
+    public static Object execute(RConnection file, char sepChar, String quoteSet, int nskip, boolean blskip, char comChar) throws IOException {
+        LocalData data = new LocalData();
+        data.sepchar = sepChar;
+        data.comchar = comChar;
+        data.quoteset = quoteSet;
+        data.is = file.getInputStream();
+
+        if (file.isStdin()) {
+            data.ttyflag = true;
+            throw new IOException("count.fields not implemented for stdin");
+        } else {
+            data.ttyflag = false;
+        }
+
+        data.save = 0;
+        int quote = 0;
+        int inquote = 0;
+        int nfields = 0;
+        int nlines = 0;
+        int blocksize = SCAN_BLOCKSIZE;
+        int[] ans = new int[blocksize];
+
+        while (true) {
+            int c = scanchar(inquote, data);
+            if (c == R_EOF) {
+                if (nfields != 0) {
+                    ans[nlines] = nfields;
+                } else {
+                    nlines--;
+                }
+                break;
+            } else if (c == '\n') {
+                if (inquote != 0) {
+                    ans[nlines] = RRuntime.INT_NA;
+                    nlines++;
+                } else if (nfields > 0 || !blskip) {
+                    ans[nlines] = nfields;
+                    nlines++;
+                    nfields = 0;
+                    inquote = 0;
+                }
+                if (nlines == blocksize) {
+                    int[] bns = ans;
+                    blocksize = 2 * blocksize;
+                    ans = new int[blocksize];
+                    System.arraycopy(bns, 0, ans, 0, bns.length);
+                }
+                continue;
+            } else if (data.sepchar != 0) {
+                if (nfields == 0)
+                    nfields++;
+                if (inquote != 0 && c == R_EOF) {
+                    data.checkClose();
+                    throw new IllegalStateException("quoted string on line " + inquote + " terminated by EOF");
+                }
+                if (inquote != 0 && c == quote)
+                    inquote = 0;
+                else if (data.quoteset.indexOf(c) > 0) {
+                    inquote = nlines + 1;
+                    quote = c;
+                }
+                if (c == data.sepchar && inquote == 0)
+                    nfields++;
+            } else if (!Rspace(c)) {
+                if (data.quoteset.indexOf(c) > 0) {
+                    quote = c;
+                    inquote = nlines + 1;
+                    while ((c = scanchar(inquote, data)) != quote) {
+                        if (c == R_EOF) {
+                            data.checkClose();
+                            throw new IllegalStateException("quoted string on line " + inquote + " terminated by EOF");
+                        } else if (c == '\n') {
+                            ans[nlines] = RRuntime.INT_NA;
+                            nlines++;
+                            if (nlines == blocksize) {
+                                int[] bns = ans;
+                                blocksize = 2 * blocksize;
+                                ans = new int[blocksize];
+                                System.arraycopy(bns, 0, ans, 0, bns.length);
+                            }
+                        }
+                    }
+                    inquote = 0;
+                } else {
+                    do {
+                        // if (dbcslocale && btowc(c) == WEOF)
+                        // scanchar2(&data);
+                        c = scanchar(0, data);
+                    } while (!Rspace(c) && c != R_EOF);
+                    if (c == R_EOF)
+                        c = '\n';
+                    unscanchar(c, data);
+                }
+                nfields++;
+            }
+
+        }
+        /*
+         * we might have a character that was unscanchar-ed. So pushback if possible
+         */
+// if (data.save && !data.ttyflag && data.wasopen) {
+// char line[2] = " ";
+// line[0] = (char) data.save;
+// con_pushback(data.con, FALSE, line);
+// }
+        data.checkClose();
+
+        if (nlines < 0) {
+            return RNull.instance;
+        }
+        if (nlines == blocksize) {
+            return RDataFactory.createIntVector(ans, RDataFactory.COMPLETE_VECTOR);
+        }
+
+        int[] bns = new int[nlines + 1];
+        for (int i = 0; i <= nlines; i++) {
+            bns[i] = ans[i];
+        }
+        return RDataFactory.createIntVector(bns, RDataFactory.COMPLETE_VECTOR);
+
+    }
+
+    private static int scanchar_raw(LocalData d) throws IOException {
+        int c = (d.ttyflag) ? -1 : d.is.read();
+        if (c == 0) {
+            if (d.skipNul) {
+                do {
+                    c = (d.ttyflag) ? -1 : d.is.read();
+                } while (c == 0);
+            }
+        }
+        return c;
+    }
+
+    private static void unscanchar(int c, LocalData d) throws IOException {
+        d.save = c;
+    }
+
+    private static int scanchar(int inQuote, LocalData d) throws IOException {
+        int next;
+        if (d.save != 0) {
+            next = d.save;
+            d.save = 0;
+        } else {
+            next = scanchar_raw(d);
+        }
+        if (next == d.comchar && inQuote != 0) {
+            do {
+                next = scanchar_raw(d);
+            } while (next != '\n' && next != R_EOF);
+        }
+        if (next == '\\' && d.escapes) {
+            next = scanchar_raw(d);
+            if ('0' <= next && next <= '8') {
+                int octal = next - '0';
+                if ('0' <= (next = scanchar_raw(d)) && next <= '8') {
+                    octal = 8 * octal + next - '0';
+                    if ('0' <= (next = scanchar_raw(d)) && next <= '8') {
+                        octal = 8 * octal + next - '0';
+                    } else {
+                        unscanchar(next, d);
+                    }
+                } else {
+                    unscanchar(next, d);
+                }
+                next = octal;
+            } else {
+                switch (next) {
+                    case 'a':
+                        next = 7;
+                        break;
+                    case 'b':
+                        next = '\b';
+                        break;
+                    case 'f':
+                        next = '\f';
+                        break;
+                    case 'n':
+                        next = '\n';
+                        break;
+                    case 'r':
+                        next = '\r';
+                        break;
+                    case 't':
+                        next = '\t';
+                        break;
+                    case 'v':
+                        next = 11;
+                        break;
+                    case 'x': {
+                        int val = 0;
+                        int i;
+                        int ext;
+                        for (i = 0; i < 2; i++) {
+                            next = scanchar_raw(d);
+                            if (next >= '0' && next <= '9') {
+                                ext = next - '0';
+                            } else if (next >= 'A' && next <= 'F') {
+                                ext = next - 'A' + 10;
+                            } else if (next >= 'a' && next <= 'f') {
+                                ext = next - 'a' + 10;
+                            } else {
+                                unscanchar(next, d);
+                                break;
+                            }
+                            val = 16 * val + ext;
+                        }
+                        next = val;
+                    }
+                        break;
+
+                    default:
+                        /*
+                         * Any other char and even EOF escapes to itself, but we need to preserve \"
+                         * etc inside quotes.
+                         */
+                        if (inQuote != 0 && d.quoteset.indexOf(next) >= 0) {
+                            unscanchar(next, d);
+                            next = '\\';
+                        }
+                        break;
+                }
+            }
+        }
+        return next;
+    }
+
+    private static boolean Rspace(int c) {
+        if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
+            return true;
+        }
+        // TODO locale
+        return false;
+    }
+
+}
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/utils/R/init.R b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/utils/R/init.R
new file mode 100644
index 0000000000000000000000000000000000000000..6ab8943e8873938483938866c175b95ae86ce807
--- /dev/null
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/utils/R/init.R
@@ -0,0 +1,24 @@
+# Copyright (c) 2014, 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
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.
+#
+# This code is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+
+# object defined in the utils package describing native countfields function
+C_countfields <- list(name="countfields")
+C_writetable <- list(name="writetable")
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/R/readtable.R b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/utils/R/readtable.R
similarity index 100%
rename from com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/R/readtable.R
rename to com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/utils/R/readtable.R
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/utils/R/writetable.R b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/utils/R/writetable.R
new file mode 100644
index 0000000000000000000000000000000000000000..5ac109f11d248f39650fd8888fde93a52c325120
--- /dev/null
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/utils/R/writetable.R
@@ -0,0 +1,176 @@
+#  File src/library/utils/R/write.table.R
+#  Part of the R package, http://www.R-project.org
+#
+#  Copyright (C) 1995-2012 The R Core Team
+#
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  A copy of the GNU General Public License is available at
+#  http://www.r-project.org/Licenses/
+
+write.table <-
+		function (x, file = "", append = FALSE, quote = TRUE, sep = " ",
+				eol = "\n", na = "NA", dec = ".", row.names = TRUE,
+				col.names = TRUE, qmethod = c("escape", "double"),
+				fileEncoding = "")
+{
+	qmethod <- match.arg(qmethod)
+	if(is.logical(quote) && (length(quote) != 1L || is.na(quote)))
+		stop("'quote' must be 'TRUE', 'FALSE' or numeric")
+	## quote column names unless quote == FALSE (see help).
+	quoteC <- if(is.logical(quote)) quote else TRUE
+	qset <- is.logical(quote) && quote
+
+	if(!is.data.frame(x) && !is.matrix(x)) x <- data.frame(x)
+
+	makeRownames <- isTRUE(row.names)
+	## need col names if col.names is TRUE or NA
+	makeColnames <- is.logical(col.names) && !identical(FALSE, col.names)
+	if(is.matrix(x)) {
+		## fix up dimnames as as.data.frame would
+		p <- ncol(x)
+		d <- dimnames(x)
+		if(is.null(d)) d <- list(NULL, NULL)
+		if(is.null(d[[1L]]) && makeRownames) d[[1L]] <- seq_len(nrow(x))
+		if(is.null(d[[2L]]) && makeColnames && p > 0L)
+			d[[2L]] <- paste0("V", 1L:p)
+		if(qset)
+			quote <- if(is.character(x)) seq_len(p) else numeric()
+	} else { ## data.frame
+		if(qset)
+			quote <- if(length(x))
+						which(unlist(lapply(x, function(x)
+													is.character(x) || is.factor(x))))
+					else numeric()
+		## fix up embedded matrix columns into separate cols:
+		if(any(sapply(x, function(z) length(dim(z)) == 2 && dim(z)[2L] > 1))) {
+			c1 <- names(x)
+			x <- as.matrix(x, rownames.force = makeRownames)
+			d <- dimnames(x)
+			if(qset) {
+				ord <- match(c1, d[[2L]], 0L)
+				quote <- ord[quote]; quote <- quote[quote > 0L]
+			}
+		}
+		else
+			d <- list(if(makeRownames) row.names(x),
+					if(makeColnames) names(x))
+		p <- ncol(x)
+	}
+	nocols <- p == 0L
+
+	if(is.logical(quote)) # must be false
+		quote <- NULL
+	else if(is.numeric(quote)) {
+		if(any(quote < 1L | quote > p))
+			stop("invalid numbers in 'quote'")
+	} else
+		stop("invalid 'quote' specification")
+
+	rn <- FALSE
+	rnames <- NULL
+	if(is.logical(row.names)) {
+		if(row.names) {rnames <- as.character(d[[1L]]); rn <- TRUE}
+	} else {
+		rnames <- as.character(row.names)
+		rn <- TRUE
+		if(length(rnames) != nrow(x))
+			stop("invalid 'row.names' specification")
+	}
+	if(!is.null(quote) && rn) # quote the row names
+		quote <- c(0, quote)
+
+	if(is.logical(col.names)) {
+		if(!rn && is.na(col.names))
+			stop("'col.names = NA' makes no sense when 'row.names = FALSE'")
+		col.names <- if(is.na(col.names) && rn) c("", d[[2L]])
+				else if(col.names) d[[2L]] else NULL
+	} else {
+		col.names <- as.character(col.names)
+		if(length(col.names) != p)
+			stop("invalid 'col.names' specification")
+	}
+
+	if(file == "") file <- stdout()
+	else if(is.character(file)) {
+		file <- if(nzchar(fileEncoding))
+					file(file, ifelse(append, "a", "w"), encoding = fileEncoding)
+				else file(file, ifelse(append, "a", "w"))
+		on.exit(close(file))
+	} else if(!isOpen(file, "w")) {
+		open(file, "w")
+		on.exit(close(file))
+	}
+	if(!inherits(file, "connection"))
+		stop("'file' must be a character string or connection")
+
+	qstring <-                          # quoted embedded quote string
+			switch(qmethod,
+					"escape" = '\\\\"',
+					"double" = '""')
+	if(!is.null(col.names)) {
+		if(append)
+			warning("appending column names to file")
+		if(quoteC)
+			col.names <- paste("\"", gsub('"', qstring, col.names),
+					"\"", sep = "")
+		writeLines(paste(col.names, collapse = sep), file, sep = eol)
+	}
+
+	if (nrow(x) == 0L) return(invisible())
+	if (nocols && !rn) return(cat(rep.int(eol, NROW(x)), file=file, sep=""))
+
+	## convert list matrices to character - maybe not much use?
+	if(is.matrix(x) && !is.atomic(x)) mode(x) <- "character"
+	if(is.data.frame(x)) {
+		## convert columns we can't handle in C code
+		x[] <- lapply(x, function(z) {
+					if(is.object(z) && !is.factor(z)) as.character(z) else z
+				})
+	}
+
+	invisible(.External2(C_writetable, x, file, nrow(x), p, rnames, sep, eol,
+					na, dec, as.integer(quote), qmethod != "double"))
+}
+
+write.csv <- function(...)
+{
+	Call <- match.call(expand.dots = TRUE)
+	for(argname in c("append", "col.names", "sep", "dec", "qmethod"))
+		if(!is.null(Call[[argname]]))
+			warning(gettextf("attempt to set '%s' ignored", argname),
+					domain = NA)
+	rn <- eval.parent(Call$row.names)
+	Call$append <- NULL
+	Call$col.names <- if(is.logical(rn) && !rn) TRUE else NA
+	Call$sep <- ","
+	Call$dec <- "."
+	Call$qmethod <- "double"
+	Call[[1L]] <- as.name("write.table")
+	eval.parent(Call)
+}
+
+write.csv2 <- function(...)
+{
+	Call <- match.call(expand.dots = TRUE)
+	for(argname in c("append", "col.names", "sep", "dec", "qmethod"))
+		if(!is.null(Call[[argname]]))
+			warning(gettextf("attempt to set '%s' ignored", argname),
+					domain = NA)
+	rn <- eval.parent(Call$row.names)
+	Call$append <- NULL
+	Call$col.names <- if(is.logical(rn) && !rn) TRUE else NA
+	Call$sep <- ";"
+	Call$dec <- ","
+	Call$qmethod <- "double"
+	Call[[1L]] <- as.name("write.table")
+	eval.parent(Call)
+}
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RNode.java
index 261a69b958f4e6f901aa0b54e1cc0bc0194b5bac..8f09abde45a867940c74e2b3971ac9a1328dbc5e 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RNode.java
@@ -158,6 +158,10 @@ public abstract class RNode extends Node {
         return RTypesGen.RTYPES.expectRDataFrame(execute(frame));
     }
 
+    public RFactor executeRFactor(VirtualFrame frame) throws UnexpectedResultException {
+        return RTypesGen.RTYPES.expectRFactor(execute(frame));
+    }
+
     public RSymbol executeRSymbol(VirtualFrame frame) throws UnexpectedResultException {
         return RTypesGen.RTYPES.expectRSymbol(execute(frame));
     }
@@ -190,7 +194,7 @@ public abstract class RNode extends Node {
         return RTypesGen.RTYPES.expectRType(execute(frame));
     }
 
-    public static boolean areSameLength(RAbstractVector a, RAbstractVector b) {
+    public static boolean areSameLength(RAbstractContainer a, RAbstractContainer b) {
         return a.getLength() == b.getLength();
     }
 }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/ConstantNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/ConstantNode.java
index 35d65aaf9e88ea41afe39e5cd0d9b0d8fb455df9..a6b85084e2b0933f0ebfad322aa1ce9c45c349c4 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/ConstantNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/ConstantNode.java
@@ -60,6 +60,8 @@ public abstract class ConstantNode extends RNode implements VisibilityController
             return new ConstantVectorNode((RAbstractVector) value);
         } else if (value instanceof RDataFrame) {
             return new ConstantDataFrameNode((RDataFrame) value);
+        } else if (value instanceof RFactor) {
+            return new ConstantFactorNode((RFactor) value);
         } else if (value instanceof RRaw) {
             return new ConstantRawNode((RRaw) value);
         } else if (value instanceof RFunction) {
@@ -304,6 +306,27 @@ public abstract class ConstantNode extends RNode implements VisibilityController
         }
     }
 
+    private static final class ConstantFactorNode extends ConstantNode {
+
+        private final RFactor factor;
+
+        public ConstantFactorNode(RFactor factor) {
+            this.factor = factor;
+        }
+
+        @Override
+        public RFactor executeRFactor(VirtualFrame frame) {
+            controlVisibility();
+            return factor;
+        }
+
+        @Override
+        public Object execute(VirtualFrame frame) {
+            controlVisibility();
+            return factor;
+        }
+    }
+
     private static final class ConstantRawNode extends ConstantNode {
 
         private final RRaw data;
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/array/write/CoerceVector.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/array/write/CoerceVector.java
index b0185087e870bf218343d9f90616e9b536aed93c..eec8bfc66da0e9e94c01b80404bb1c5bad7d1e68 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/array/write/CoerceVector.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/array/write/CoerceVector.java
@@ -42,6 +42,15 @@ public abstract class CoerceVector extends RNode {
     @Child private CastIntegerNode castInteger;
     @Child private CastStringNode castString;
     @Child private CastListNode castList;
+    @Child private CoerceVector coerceRecursive;
+
+    private Object coerceRecursive(VirtualFrame frame, Object value, Object vector, Object operand) {
+        if (coerceRecursive == null) {
+            CompilerDirectives.transferToInterpreterAndInvalidate();
+            coerceRecursive = insert(CoerceVectorFactory.create(null, null, null));
+        }
+        return coerceRecursive.executeEvaluated(frame, value, vector, operand);
+    }
 
     private Object castComplex(VirtualFrame frame, Object vector) {
         if (castComplex == null) {
@@ -309,6 +318,13 @@ public abstract class CoerceVector extends RNode {
         return (RList) castList(frame, vector);
     }
 
+    // factor value
+
+    @Specialization
+    protected Object coerce(VirtualFrame frame, RFactor value, RAbstractVector vector, Object operand) {
+        return coerceRecursive(frame, value.getVector(), vector, operand);
+    }
+
     // function vector value
 
     @Specialization
@@ -338,7 +354,7 @@ public abstract class CoerceVector extends RNode {
         return vector;
     }
 
-    protected boolean isVectorList(RAbstractVector value, RAbstractVector vector) {
+    protected boolean isVectorList(RAbstractContainer value, RAbstractVector vector) {
         return vector.getElementClass() == Object.class;
     }
 
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/array/write/UpdateArrayHelperNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/array/write/UpdateArrayHelperNode.java
index 6c714e11d7dd08946f3773235c584862beabdd21..6a1cfd0c93db3532420a91ca67ee01e3931542c6 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/array/write/UpdateArrayHelperNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/array/write/UpdateArrayHelperNode.java
@@ -177,6 +177,11 @@ public abstract class UpdateArrayHelperNode extends RNode {
         return CastToContainerNodeFactory.create(child, false, false, false, true);
     }
 
+    @Specialization
+    protected Object update(VirtualFrame frame, Object v, RFactor value, int recLevel, Object positions, Object vector) {
+        return updateRecursive(frame, v, value.getVector(), vector, positions, recLevel);
+    }
+
     @Specialization(guards = "emptyValue")
     protected RAbstractVector update(Object v, RAbstractVector value, int recLevel, Object[] positions, RAbstractVector vector) {
         if (isSubset) {
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/binary/BinaryBooleanNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/binary/BinaryBooleanNode.java
index b80217f68c4c03d3253c78571d06e6d9d8bb2f5d..d8c732f9d642aef5e1f54539446aa27a8299a6a0 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/binary/BinaryBooleanNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/binary/BinaryBooleanNode.java
@@ -701,6 +701,78 @@ public abstract class BinaryBooleanNode extends RBuiltinNode {
         throw RError.error(getEncapsulatingSourceSection(), RError.Message.NON_CONFORMABLE_ARRAYS);
     }
 
+    // factor and scalar
+
+    @Specialization(guards = "!isEq")
+    protected RLogicalVector doFactorOp(RFactor left, Object right) {
+        throw RError.error(getEncapsulatingSourceSection(), RError.Message.NOT_MEANINGFUL_FOR_FACTORS, logic.opName());
+    }
+
+    @Specialization(guards = "!isEq")
+    protected RLogicalVector doFactorOp(Object left, RFactor right) {
+        throw RError.error(getEncapsulatingSourceSection(), RError.Message.NOT_MEANINGFUL_FOR_FACTORS, logic.opName());
+    }
+
+    @Specialization(guards = "isEq")
+    protected RLogicalVector doFactorOp(RFactor left, int right) {
+        return performStringVectorOp(RClosures.createFactorToStringVector(left, leftNACheck), RRuntime.intToString(right, false), false);
+    }
+
+    @Specialization(guards = "isEq")
+    protected RLogicalVector doFactorOp(int left, RFactor right) {
+        return performStringVectorOp(RClosures.createFactorToStringVector(right, leftNACheck), RRuntime.intToString(left, false), true);
+    }
+
+    @Specialization(guards = "isEq")
+    protected RLogicalVector doFactorOp(RFactor left, double right) {
+        return performStringVectorOp(RClosures.createFactorToStringVector(left, leftNACheck), RRuntime.doubleToString(right), false);
+    }
+
+    @Specialization(guards = "isEq")
+    protected RLogicalVector doFactorOp(double left, RFactor right) {
+        return performStringVectorOp(RClosures.createFactorToStringVector(right, leftNACheck), RRuntime.doubleToString(left), true);
+    }
+
+    @Specialization(guards = "isEq")
+    protected RLogicalVector doFactorOp(RFactor left, byte right) {
+        return performStringVectorOp(RClosures.createFactorToStringVector(left, leftNACheck), RRuntime.logicalToString(right), false);
+    }
+
+    @Specialization(guards = "isEq")
+    protected RLogicalVector doFactorOp(byte left, RFactor right) {
+        return performStringVectorOp(RClosures.createFactorToStringVector(right, leftNACheck), RRuntime.logicalToString(left), false);
+    }
+
+    @Specialization(guards = "isEq")
+    protected RLogicalVector doFactorOp(RFactor left, String right) {
+        return performStringVectorOp(RClosures.createFactorToStringVector(left, leftNACheck), right, false);
+    }
+
+    @Specialization(guards = "isEq")
+    protected RLogicalVector doFactorOp(String left, RFactor right) {
+        return performStringVectorOp(RClosures.createFactorToStringVector(right, leftNACheck), left, true);
+    }
+
+    @Specialization(guards = "isEq")
+    protected RLogicalVector doFactorOp(RFactor left, RComplex right) {
+        return performStringVectorOp(RClosures.createFactorToStringVector(left, leftNACheck), RRuntime.complexToString(right), false);
+    }
+
+    @Specialization(guards = "isEq")
+    protected RLogicalVector doFactorOp(RComplex left, RFactor right) {
+        return performStringVectorOp(RClosures.createFactorToStringVector(right, leftNACheck), RRuntime.complexToString(left), true);
+    }
+
+    @Specialization(guards = "isEq")
+    protected RLogicalVector doFactorOp(RFactor left, RRaw right) {
+        return performStringVectorOp(RClosures.createFactorToStringVector(left, leftNACheck), RRuntime.rawToString(right), false);
+    }
+
+    @Specialization(guards = "isEq")
+    protected RLogicalVector doFactorOp(RRaw left, RFactor right) {
+        return performStringVectorOp(RClosures.createFactorToStringVector(right, leftNACheck), RRuntime.rawToString(left), true);
+    }
+
     protected static boolean differentDimensions(RAbstractVector left, RAbstractVector right) {
         if (!left.hasDimensions() || !right.hasDimensions()) {
             return false;
@@ -791,6 +863,26 @@ public abstract class BinaryBooleanNode extends RBuiltinNode {
         return performStringVectorOpSameLength(left, RClosures.createIntToStringVector(right, rightNACheck));
     }
 
+    @Specialization(guards = {"!areSameLength", "isEq"})
+    protected RLogicalVector doIntVectorDifferentLength(RAbstractIntVector left, RFactor right) {
+        return performStringVectorOpDifferentLength(RClosures.createIntToStringVector(left, leftNACheck), RClosures.createFactorToStringVector(right, leftNACheck));
+    }
+
+    @Specialization(guards = {"areSameLength", "isEq"})
+    protected RLogicalVector doIntVectorSameLength(RAbstractIntVector left, RFactor right) {
+        return performStringVectorOpSameLength(RClosures.createIntToStringVector(left, leftNACheck), RClosures.createFactorToStringVector(right, leftNACheck));
+    }
+
+    @Specialization(guards = {"!areSameLength", "isEq"})
+    protected RLogicalVector doIntVectorDifferentLength(RFactor left, RAbstractIntVector right) {
+        return performStringVectorOpDifferentLength(RClosures.createFactorToStringVector(left, leftNACheck), RClosures.createIntToStringVector(right, rightNACheck));
+    }
+
+    @Specialization(guards = {"areSameLength", "isEq"})
+    protected RLogicalVector doIntVectorSameLength(RFactor left, RAbstractIntVector right) {
+        return performStringVectorOpSameLength(RClosures.createFactorToStringVector(left, leftNACheck), RClosures.createIntToStringVector(right, rightNACheck));
+    }
+
     @Specialization(guards = "!areSameLength")
     protected RLogicalVector doIntVectorDifferentLength(RAbstractIntVector left, RComplexVector right) {
         return performComplexVectorOpDifferentLength(RClosures.createIntToComplexVector(left, leftNACheck), right);
@@ -883,6 +975,26 @@ public abstract class BinaryBooleanNode extends RBuiltinNode {
         return performStringVectorOpSameLength(left, RClosures.createDoubleToStringVector(right, rightNACheck));
     }
 
+    @Specialization(guards = {"!areSameLength", "isEq"})
+    protected RLogicalVector doDoubleVectorDifferentLength(RAbstractDoubleVector left, RFactor right) {
+        return performStringVectorOpDifferentLength(RClosures.createDoubleToStringVector(left, leftNACheck), RClosures.createFactorToStringVector(right, leftNACheck));
+    }
+
+    @Specialization(guards = {"areSameLength", "isEq"})
+    protected RLogicalVector doDoubleVectorSameLength(RAbstractDoubleVector left, RFactor right) {
+        return performStringVectorOpSameLength(RClosures.createDoubleToStringVector(left, leftNACheck), RClosures.createFactorToStringVector(right, leftNACheck));
+    }
+
+    @Specialization(guards = {"!areSameLength", "isEq"})
+    protected RLogicalVector doDoubleVectorDifferentLength(RFactor left, RAbstractDoubleVector right) {
+        return performStringVectorOpDifferentLength(RClosures.createFactorToStringVector(left, leftNACheck), RClosures.createDoubleToStringVector(right, rightNACheck));
+    }
+
+    @Specialization(guards = {"areSameLength", "isEq"})
+    protected RLogicalVector doDoubleVectorSameLength(RFactor left, RAbstractDoubleVector right) {
+        return performStringVectorOpSameLength(RClosures.createFactorToStringVector(left, leftNACheck), RClosures.createDoubleToStringVector(right, rightNACheck));
+    }
+
     @Specialization(guards = "!areSameLength")
     protected RLogicalVector doDoubleVectorDifferentLength(RAbstractDoubleVector left, RComplexVector right) {
         return performComplexVectorOpDifferentLength(RClosures.createDoubleToComplexVector(left, leftNACheck), right);
@@ -955,6 +1067,26 @@ public abstract class BinaryBooleanNode extends RBuiltinNode {
         return performStringVectorOpSameLength(left, RClosures.createLogicalToStringVector(right, rightNACheck));
     }
 
+    @Specialization(guards = {"!areSameLength", "isEq"})
+    protected RLogicalVector doLogicalVectorDifferentLength(RAbstractLogicalVector left, RFactor right) {
+        return performStringVectorOpDifferentLength(RClosures.createLogicalToStringVector(left, leftNACheck), RClosures.createFactorToStringVector(right, leftNACheck));
+    }
+
+    @Specialization(guards = {"areSameLength", "isEq"})
+    protected RLogicalVector doLogicalVectorSameLength(RAbstractLogicalVector left, RFactor right) {
+        return performStringVectorOpSameLength(RClosures.createLogicalToStringVector(left, leftNACheck), RClosures.createFactorToStringVector(right, leftNACheck));
+    }
+
+    @Specialization(guards = {"!areSameLength", "isEq"})
+    protected RLogicalVector doLogicalVectorDifferentLength(RFactor left, RAbstractLogicalVector right) {
+        return performStringVectorOpDifferentLength(RClosures.createFactorToStringVector(left, leftNACheck), RClosures.createLogicalToStringVector(right, rightNACheck));
+    }
+
+    @Specialization(guards = {"areSameLength", "isEq"})
+    protected RLogicalVector doLogicalVectorSameLength(RFactor left, RAbstractLogicalVector right) {
+        return performStringVectorOpSameLength(RClosures.createFactorToStringVector(left, leftNACheck), RClosures.createLogicalToStringVector(right, rightNACheck));
+    }
+
     @Specialization(guards = "!areSameLength")
     protected RLogicalVector doLogicalVectorDifferentLength(RAbstractLogicalVector left, RComplexVector right) {
         return performComplexVectorOpDifferentLength(RClosures.createLogicalToComplexVector(left, leftNACheck), right);
@@ -1007,6 +1139,26 @@ public abstract class BinaryBooleanNode extends RBuiltinNode {
         return performStringVectorOpSameLength(left, right);
     }
 
+    @Specialization(guards = {"!areSameLength", "isEq"})
+    protected RLogicalVector doStringVectorDifferentLength(RStringVector left, RFactor right) {
+        return performStringVectorOpDifferentLength(left, RClosures.createFactorToStringVector(right, rightNACheck));
+    }
+
+    @Specialization(guards = {"areSameLength", "isEq"})
+    protected RLogicalVector doStringVectorSameLength(RStringVector left, RFactor right) {
+        return performStringVectorOpSameLength(left, RClosures.createFactorToStringVector(right, rightNACheck));
+    }
+
+    @Specialization(guards = {"!areSameLength", "isEq"})
+    protected RLogicalVector doStringVectorDifferentLength(RFactor left, RStringVector right) {
+        return performStringVectorOpDifferentLength(RClosures.createFactorToStringVector(left, rightNACheck), right);
+    }
+
+    @Specialization(guards = {"areSameLength", "isEq"})
+    protected RLogicalVector doStringVectorSameLength(RFactor left, RStringVector right) {
+        return performStringVectorOpSameLength(RClosures.createFactorToStringVector(left, rightNACheck), right);
+    }
+
     @Specialization(guards = "!areSameLength")
     protected RLogicalVector doStringVectorDifferentLength(RStringVector left, RAbstractComplexVector right) {
         return performStringVectorOpDifferentLength(left, RClosures.createComplexToStringVector(right, rightNACheck));
@@ -1047,6 +1199,58 @@ public abstract class BinaryBooleanNode extends RBuiltinNode {
         return performStringVectorOpSameLength(RClosures.createRawToStringVector(left, leftNACheck), right);
     }
 
+    // factor and vectors
+
+    @Specialization(guards = {"!areSameLength", "isEq"})
+    protected RLogicalVector doStringVectorDifferentLength(RFactor left, RFactor right) {
+        return performStringVectorOpDifferentLength(RClosures.createFactorToStringVector(left, rightNACheck), RClosures.createFactorToStringVector(right, rightNACheck));
+    }
+
+    @Specialization(guards = {"areSameLength", "isEq"})
+    protected RLogicalVector doStringVectorSameLength(RFactor left, RFactor right) {
+        return performStringVectorOpSameLength(RClosures.createFactorToStringVector(left, rightNACheck), RClosures.createFactorToStringVector(right, rightNACheck));
+    }
+
+    @Specialization(guards = {"!areSameLength", "isEq"})
+    protected RLogicalVector doStringVectorDifferentLength(RFactor left, RAbstractComplexVector right) {
+        return performStringVectorOpDifferentLength(RClosures.createFactorToStringVector(left, rightNACheck), RClosures.createComplexToStringVector(right, rightNACheck));
+    }
+
+    @Specialization(guards = {"areSameLength", "isEq"})
+    protected RLogicalVector doStringVectorSameLength(RFactor left, RAbstractComplexVector right) {
+        return performStringVectorOpSameLength(RClosures.createFactorToStringVector(left, rightNACheck), RClosures.createComplexToStringVector(right, rightNACheck));
+    }
+
+    @Specialization(guards = {"!areSameLength", "isEq"})
+    protected RLogicalVector doStringVectorDifferentLength(RAbstractComplexVector left, RFactor right) {
+        return performStringVectorOpDifferentLength(RClosures.createComplexToStringVector(left, leftNACheck), RClosures.createFactorToStringVector(right, rightNACheck));
+    }
+
+    @Specialization(guards = {"areSameLength", "isEq"})
+    protected RLogicalVector doStringVectorSameLength(RAbstractComplexVector left, RFactor right) {
+        return performStringVectorOpSameLength(RClosures.createComplexToStringVector(left, leftNACheck), RClosures.createFactorToStringVector(right, rightNACheck));
+    }
+
+    @Specialization(guards = {"!areSameLength", "isEq"})
+    protected RLogicalVector doStringVectorDifferentLength(RFactor left, RRawVector right) {
+        return performStringVectorOpDifferentLength(RClosures.createFactorToStringVector(left, rightNACheck), RClosures.createRawToStringVector(right, rightNACheck));
+    }
+
+    @Specialization(guards = {"areSameLength", "isEq"})
+    protected RLogicalVector doStringVectorSameLength(RFactor left, RRawVector right) {
+        return performStringVectorOpSameLength(RClosures.createFactorToStringVector(left, rightNACheck), RClosures.createRawToStringVector(right, rightNACheck));
+    }
+
+    @Specialization(guards = {"!areSameLength", "isEq"})
+    protected RLogicalVector doStringVectorDifferentLengthRRawVector(RRawVector left, RFactor right) {
+        return performStringVectorOpDifferentLength(RClosures.createRawToStringVector(left, leftNACheck), RClosures.createFactorToStringVector(right, rightNACheck));
+    }
+
+    @Specialization(guards = {"areSameLength", "isEq"})
+    protected RLogicalVector doStringVectorSameLengthRRawVector(RRawVector left, RFactor right) {
+        return performStringVectorOpSameLength(RClosures.createRawToStringVector(left, leftNACheck), RClosures.createFactorToStringVector(right, rightNACheck));
+    }
+
     // complex vector and vectors
 
     @Specialization(guards = "!areSameLength")
@@ -1134,6 +1338,18 @@ public abstract class BinaryBooleanNode extends RBuiltinNode {
 
     // guards
 
+    public boolean isEq(RFactor left, RFactor right) {
+        return logic instanceof BinaryCompare.Equal || logic instanceof BinaryCompare.NotEqual;
+    }
+
+    public boolean isEq(RFactor left, Object right) {
+        return logic instanceof BinaryCompare.Equal || logic instanceof BinaryCompare.NotEqual;
+    }
+
+    public boolean isEq(Object left, RFactor right) {
+        return !(logic instanceof BinaryCompare.Equal || logic instanceof BinaryCompare.NotEqual);
+    }
+
     private boolean isVectorizedLogicalOp() {
         return !(logic instanceof BinaryLogic.And || logic instanceof BinaryLogic.Or);
     }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionDefinitionNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionDefinitionNode.java
index cb3adaf4e8cfe44e27deacdac1635572ecd0e66b..5e51f4a7de834dfd42884873d1abd88e21727c1d 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionDefinitionNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/FunctionDefinitionNode.java
@@ -95,6 +95,8 @@ public final class FunctionDefinitionNode extends RRootNode {
             return ex.getResult();
         } finally {
             if (onExitProfile.profile(onExitSlot != null && onExitSlot.hasValue(vf))) {
+                // Must preserve the visibility state as it may be changed by the on.exit expression
+                boolean isVisible = RContext.isVisible();
                 ArrayList<Object> current = getCurrentOnExitList(vf, onExitSlot.executeFrameSlot(vf));
                 for (Object expr : current) {
                     if (!(expr instanceof RNode)) {
@@ -103,6 +105,7 @@ public final class FunctionDefinitionNode extends RRootNode {
                     RNode node = (RNode) expr;
                     onExitExpressionCache.execute(vf, node);
                 }
+                RContext.setVisible(isVisible);
             }
         }
     }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/CastToContainerNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/CastToContainerNode.java
index 5b03886ca5b28b2c524d194c7b2934a84730775c..85cd6acc908fe39553b4105b6070163be01fd21f 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/CastToContainerNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/CastToContainerNode.java
@@ -72,6 +72,11 @@ public abstract class CastToContainerNode extends CastNode {
         return dataFrame;
     }
 
+    @Specialization
+    protected RFactor cast(RFactor factor) {
+        return factor;
+    }
+
     @Specialization
     protected RExpression cast(RExpression expression) {
         return expression;
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 bd1343aea27bd569f0b2b6e6557b60f8b98f8694..760444abf70eb99ff0857f3f95f4984de75518e5 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
@@ -115,6 +115,13 @@ public abstract class RConnection implements RClassHierarchy {
         }
     }
 
+    /**
+     * Returns {@code true} if this is the "stdin" connection.
+     */
+    public boolean isStdin() {
+        return false;
+    }
+
     /**
      * Return the underlying input stream (for internal use).
      */
@@ -163,4 +170,12 @@ public abstract class RConnection implements RClassHierarchy {
         pushBack = null;
     }
 
+    /**
+     * 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 flush() throws IOException;
+
 }
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 2e7305b88d64ee05f7369d610f0a48c5685742ef..c5ad5fba8bd9cd6ff01f9fa949957950c2136c28 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
@@ -502,6 +502,8 @@ public final class RError extends RuntimeException {
         CANNOT_ASSIGN_IN_EMPTY_ENV("cannot assign values in the empty environment"),
         CANNOT_OPEN_CONNECTION("cannot open the connection"),
         ERROR_READING_CONNECTION("error reading connection: %s"),
+        ERROR_WRITING_CONNECTION("error writing connection: %s"),
+        ERROR_FLUSHING_CONNECTION("error flushing connection: %s"),
         NO_ITEM_NAMED("no item named '%s' on the search list"),
         INVALID_OBJECT("invalid object for 'as.environment'"),
         EMPTY_NO_PARENT("the empty environment has no parent"),
@@ -553,7 +555,9 @@ public final class RError extends RuntimeException {
         FIRST_ELEMENT_USED("first element used of '%s' argument"),
         MUST_BE_COERCIBLE_INTEGER("argument must be coercible to non-negative integer"),
         DEFAULT_METHOD_NOT_IMPLEMENTED_FOR_TYPE("default method not implemented for type '%s'"),
-        ADDING_INVALID_CLASS("adding class \"%s\" to an invalid object");
+        ADDING_INVALID_CLASS("adding class \"%s\" to an invalid object"),
+        IS_NA_TO_NON_VECTOR("is.na() applied to non-(list or vector) of type '%s'"),
+        NOT_MEANINGFUL_FOR_FACTORS("%s not meaningful for factors");
 
         public final String message;
         private final boolean hasArgs;
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RVector.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RVector.java
index 6579249bf68bad3afb2bea4b37d09f7ba368ad27..f4a3e81da5c94fa07805b1fa1f5d3920c82baa19 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RVector.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RVector.java
@@ -214,6 +214,7 @@ public abstract class RVector extends RBounded implements RShareable, RAbstractV
         }
     }
 
+    @TruffleBoundary
     public final void setLevels(Object newLevels) {
         if (attributes != null && newLevels == null) {
             // whether it's one dimensional array or not, assigning null always removes the "Levels"
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/closures/RClosures.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/closures/RClosures.java
index 2dfd14c197005f9bad72fd05df1c43567eb8d944..32b7d1c26275edecf13b3f7f6db3ce9af049c68e 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/closures/RClosures.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/closures/RClosures.java
@@ -23,6 +23,7 @@
 package com.oracle.truffle.r.runtime.data.closures;
 
 import com.oracle.truffle.r.runtime.ops.na.NACheck;
+import com.oracle.truffle.r.runtime.data.*;
 import com.oracle.truffle.r.runtime.data.model.*;
 
 public class RClosures {
@@ -101,4 +102,10 @@ public class RClosures {
         return new RComplexToStringVectorClosure(vector, check);
     }
 
+    // Factor to
+
+    public static RAbstractStringVector createFactorToStringVector(RFactor factor, NACheck check) {
+        return new RFactorToStringVectorClosure(factor, check);
+    }
+
 }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/closures/RFactorToStringVectorClosure.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/closures/RFactorToStringVectorClosure.java
new file mode 100644
index 0000000000000000000000000000000000000000..cb2c3ca812dd0859281f3f864c63d0e840635b0a
--- /dev/null
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/closures/RFactorToStringVectorClosure.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2013, 2014, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.runtime.data.closures;
+
+import com.oracle.truffle.r.runtime.*;
+import com.oracle.truffle.r.runtime.ops.na.NACheck;
+import com.oracle.truffle.r.runtime.data.*;
+import com.oracle.truffle.r.runtime.data.model.*;
+
+/*
+ * This closure is meant to be used only for implementation of the binary operators.
+ */
+public class RFactorToStringVectorClosure extends RToStringVectorClosure implements RAbstractStringVector {
+
+    private final RIntVector vector;
+    private final RAbstractStringVector levels;
+
+    public RFactorToStringVectorClosure(RFactor factor, NACheck naCheck) {
+        super(factor.getVector(), naCheck);
+        this.vector = factor.getVector();
+        this.levels = (RAbstractStringVector) vector.getAttr(RRuntime.LEVELS_ATTR_KEY);
+        if (this.levels == null) {
+            RError.warning(RError.Message.IS_NA_TO_NON_VECTOR, "NULL");
+        }
+    }
+
+    public String getDataAt(int index) {
+        if (levels == null) {
+            return RRuntime.STRING_NA;
+        } else {
+            return this.levels.getDataAt(vector.getDataAt(index) - 1);
+        }
+    }
+}
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ops/BinaryCompare.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ops/BinaryCompare.java
index 22fc5f2164def5f8c60bafabda94c0163c0039da..0a614dc224f5f5cafc84402517093fa10a96b075 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ops/BinaryCompare.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ops/BinaryCompare.java
@@ -100,7 +100,7 @@ public abstract class BinaryCompare extends BooleanOperation {
         super(commutative, false);
     }
 
-    private static final class NotEqual extends BinaryCompare {
+    public static final class NotEqual extends BinaryCompare {
 
         public NotEqual() {
             super(true);
@@ -144,7 +144,7 @@ public abstract class BinaryCompare extends BooleanOperation {
         }
     }
 
-    private static final class Equal extends BinaryCompare {
+    public static final class Equal extends BinaryCompare {
 
         public Equal() {
             super(true);
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
index 29ac63cb1f6573978d8cbe4bec0e55ac182ea1bb..ed03a6ef4ed6f6cfe0518fa7de055f0b0ed9eb1a 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
@@ -8348,6 +8348,51 @@ NULL
 #{ x<-factor(c("a", "b", "a")); attr(x, "levels")<-character(); as.character(x) }
 [1] NA NA NA
 
+##com.oracle.truffle.r.test.simple.TestSimpleBuiltins.testFactor
+#{ x<-factor(c("a", "b", "a")); x == "a" }
+[1]  TRUE FALSE  TRUE
+
+##com.oracle.truffle.r.test.simple.TestSimpleBuiltins.testFactor
+#{ x<-factor(c("a", "b", "a")); x == c("a", "b") }
+[1] TRUE TRUE TRUE
+Warning messages:
+1: In is.na(e1) | is.na(e2) :
+  longer object length is not a multiple of shorter object length
+2: In `==.default`(x, c("a", "b")) :
+  longer object length is not a multiple of shorter object length
+
+##com.oracle.truffle.r.test.simple.TestSimpleBuiltins.testFactor
+#{ x<-factor(c("a", "b", "a")); x > "a" }
+[1] NA NA NA
+Warning message:
+In Ops.factor(x, "a") : > not meaningful for factors
+
+##com.oracle.truffle.r.test.simple.TestSimpleBuiltins.testFactor
+#{ x<-factor(c("a", "b", "a")); x > c("a", "b") }
+[1] NA NA NA
+Warning message:
+In Ops.factor(x, c("a", "b")) : > not meaningful for factors
+
+##com.oracle.truffle.r.test.simple.TestSimpleBuiltins.testFactor
+#{ x<-factor(c("a", "b", "a", "c")); x == c("a", "b") }
+[1]  TRUE  TRUE  TRUE FALSE
+
+##com.oracle.truffle.r.test.simple.TestSimpleBuiltins.testFactor
+#{ x<-factor(c("c", "b", "a", "c")); y<-c(1); y[1]<-x; y }
+[1] 3
+Warning message:
+In y[1] <- x :
+  number of items to replace is not a multiple of replacement length
+
+##com.oracle.truffle.r.test.simple.TestSimpleBuiltins.testFactor
+#{ x<-factor(c("c", "b", "a", "c")); y<-list(1); y[1]<-x; y }
+[[1]]
+[1] 3
+
+Warning message:
+In y[1] <- x :
+  number of items to replace is not a multiple of replacement length
+
 ##com.oracle.truffle.r.test.simple.TestSimpleBuiltins.testFactor
 #{data = c(1,2,2,3,1,2,3,3,1,2,3,3,1);fdata<-factor(data);levels(fdata) = c('I','II','III');print(fdata);}
  [1] I   II  II  III I   II  III III I   II  III III I
@@ -16205,6 +16250,12 @@ $foo
 #{ x<-list(c(7,42),c(1+1i, 2+2i)); class(x)<-c("foo", "data.frame", "bar"); is.data.frame(x) }
 [1] TRUE
 
+##com.oracle.truffle.r.test.simple.TestSimpleDataFrames.testLapply
+#{ x <- c(1, 2, 3); xa <- as.data.frame(x); lapply(xa, function(x) x > 1) }
+$x
+[1] FALSE  TRUE  TRUE
+
+
 ##com.oracle.truffle.r.test.simple.TestSimpleDataFrames.testPrint
 #{ x<-c(1,2); class(x)<-"data.frame"; row.names(x)<-integer(); x }
 NULL
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/all/AllTests.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/all/AllTests.java
index 9f80add3385e800a2dc0dfc798705ec8260b7c9c..9b0455125ca3d8c3b70d08b06205e5940cf3c436 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/all/AllTests.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/all/AllTests.java
@@ -8133,6 +8133,16 @@ public class AllTests extends TestBase {
         assertEval("{ x<-factor(c(\"a\", \"b\", \"a\")); attr(x, \"levels\")<-character(); as.character(x) }");
     }
 
+    @Test
+    public void TestSimpleBuiltins_testFactor_696be2cb6d37235d8e5aa08b6de78b44() {
+        assertEval("{ x<-factor(c(\"a\", \"b\", \"a\")); x == \"a\" }");
+    }
+
+    @Test
+    public void TestSimpleBuiltins_testFactor_6b857dc0c28485c71e998f91ad719e79() {
+        assertEval("{ x<-factor(c(\"a\", \"b\", \"a\", \"c\")); x == c(\"a\", \"b\") }");
+    }
+
     @Test
     public void TestSimpleBuiltins_testFactor_2ef7de52def309425a9b70965111f004() {
         assertEvalError("{ x<-c(1,2,3); class(x)<-\"factor\"; x }");
@@ -8148,6 +8158,31 @@ public class AllTests extends TestBase {
         assertEvalError("{ x<-c(1L,2L,3L); class(x)<-\"factor\"; x }");
     }
 
+    @Test
+    public void TestSimpleBuiltins_testFactor_8e866be378d6495f8d649996dcb5bb3c() {
+        assertEvalError("{ x<-factor(c(\"a\", \"b\", \"a\")); x > \"a\" }");
+    }
+
+    @Test
+    public void TestSimpleBuiltins_testFactor_7cd2b27121f6c77b417a436d60108819() {
+        assertEvalError("{ x<-factor(c(\"a\", \"b\", \"a\")); x > c(\"a\", \"b\") }");
+    }
+
+    @Test
+    public void TestSimpleBuiltins_testFactor_61bfd366e4db68e9bda18fe2c3cc87f2() {
+        assertEvalWarning("{ x<-factor(c(\"a\", \"b\", \"a\")); x == c(\"a\", \"b\") }");
+    }
+
+    @Test
+    public void TestSimpleBuiltins_testFactor_9b48b1721b63ffee900121993a15bb82() {
+        assertEvalWarning("{ x<-factor(c(\"c\", \"b\", \"a\", \"c\")); y<-list(1); y[1]<-x; y }");
+    }
+
+    @Test
+    public void TestSimpleBuiltins_testFactor_ea50b4927f7021c815fba8b2628b3939() {
+        assertEvalWarning("{ x<-factor(c(\"c\", \"b\", \"a\", \"c\")); y<-c(1); y[1]<-x; y }");
+    }
+
     @Test
     public void TestSimpleBuiltins_testFileListing_9646bfd3fb553824f1f54cc5d04b8219() {
         assertEval("{ list.files(\"test/r/simple/data/tree1\") }");
@@ -17038,6 +17073,11 @@ public class AllTests extends TestBase {
         assertEval("{ x<-c(7,42); class(x)<-\"data.frame\"; attr(x, \"foo\")<-\"foo\"; class(x)<-NULL;  attributes(x) }");
     }
 
+    @Test
+    public void TestSimpleDataFrames_testLapply_55bc0d568d00ad30f7aace6b015a4fcd() {
+        assertEval("{ x <- c(1, 2, 3); xa <- as.data.frame(x); lapply(xa, function(x) x > 1) }");
+    }
+
     @Test
     public void TestSimpleDataFrames_testPrint_da9c92f6582f469a3303b14bf936c77e() {
         assertEval("{x<-c(1,2); class(x)<-\"data.frame\"; x}");
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/simple/TestSimpleBuiltins.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/simple/TestSimpleBuiltins.java
index 9398cf67381741b95c225e52de0dded9dd72f2de..c465bbe1098064b47b8f07f68af78517bd1e177f 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/simple/TestSimpleBuiltins.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/simple/TestSimpleBuiltins.java
@@ -3900,6 +3900,16 @@ public class TestSimpleBuiltins extends TestBase {
         assertEvalError("{ x<-c(1,2,3); class(x)<-\"factor\"; x }");
         assertEvalError("{ x<-c(\"1\",\"2\",\"3\"); class(x)<-\"factor\"; x }");
         assertEvalError("{ x<-c(1L,2L,3L); class(x)<-\"factor\"; x }");
+
+        assertEval("{ x<-factor(c(\"a\", \"b\", \"a\")); x == \"a\" }");
+        assertEvalError("{ x<-factor(c(\"a\", \"b\", \"a\")); x > \"a\" }");
+
+        assertEvalWarning("{ x<-factor(c(\"a\", \"b\", \"a\")); x == c(\"a\", \"b\") }");
+        assertEvalError("{ x<-factor(c(\"a\", \"b\", \"a\")); x > c(\"a\", \"b\") }");
+        assertEval("{ x<-factor(c(\"a\", \"b\", \"a\", \"c\")); x == c(\"a\", \"b\") }");
+
+        assertEvalWarning("{ x<-factor(c(\"c\", \"b\", \"a\", \"c\")); y<-list(1); y[1]<-x; y }");
+        assertEvalWarning("{ x<-factor(c(\"c\", \"b\", \"a\", \"c\")); y<-c(1); y[1]<-x; y }");
     }
 
     @Test
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/simple/TestSimpleDataFrames.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/simple/TestSimpleDataFrames.java
index 2aca72765eee08030935a8b701dacf251a1ecbee..2ec6c750bab29350b5bb620df8ff9112162ff9cd 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/simple/TestSimpleDataFrames.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/simple/TestSimpleDataFrames.java
@@ -130,4 +130,9 @@ public class TestSimpleDataFrames extends TestBase {
         assertEval("{ data.frame(c(1,2)) }");
         assertEval("{ data.frame(c(1,2), c(11,12)) }");
     }
+
+    @Test
+    public void testLapply() {
+        assertEval("{ x <- c(1, 2, 3); xa <- as.data.frame(x); lapply(xa, function(x) x > 1) }");
+    }
 }
diff --git a/mx.fastr/copyrights/overrides b/mx.fastr/copyrights/overrides
index 46f1105e8a2361b0fb68759a2d45ab705b6ba75f..ae129fa904741d4c8d7e26e63c07b3cc4456613a 100644
--- a/mx.fastr/copyrights/overrides
+++ b/mx.fastr/copyrights/overrides
@@ -66,6 +66,7 @@ com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/F
 com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/stats/Qgamma.java,gnu_r_qgamma.copyright
 com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/stats/Rnorm.java,gnu_r.copyright
 com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/stats/StatsUtil.java,gnu_r_statsutil.copyright
+com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/utils/CountFields.java,gnu_r.copyright
 com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/utils/UtilsPackage.java,purdue.copyright
 com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/ParseException.java,purdue.copyright
 com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/ParseUtil.java,purdue.copyright