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. */