From c0cff4bc4974ff4de282964d07c744dcda3e2bff Mon Sep 17 00:00:00 2001
From: stepan <stepan.sindelar@oracle.com>
Date: Tue, 7 Nov 2017 16:26:20 +0100
Subject: [PATCH] Implement chartr builtin

---
 .../device/awt/BufferedImageDevice.java       |   4 +-
 .../r/nodes/builtin/base/BasePackage.java     |   1 +
 .../truffle/r/nodes/builtin/base/CharTr.java  | 102 ++++++++++++++++++
 .../com/oracle/truffle/r/runtime/RError.java  |   1 +
 .../truffle/r/test/ExpectedTestOutput.test    |  49 ++++++++-
 .../r/test/builtins/TestBuiltin_chartr.java   |  21 +++-
 6 files changed, 167 insertions(+), 11 deletions(-)
 create mode 100644 com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/CharTr.java

diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/BufferedImageDevice.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/BufferedImageDevice.java
index c392fed266..3a98f5eca2 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/BufferedImageDevice.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/device/awt/BufferedImageDevice.java
@@ -31,6 +31,7 @@ import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 
 import javax.imageio.ImageIO;
@@ -75,7 +76,8 @@ public final class BufferedImageDevice extends Graphics2DDevice implements FileG
 
     private void saveImage() throws DeviceCloseException {
         try {
-            if (!Files.exists(Paths.get(filename).getParent())) {
+            Path parent = Paths.get(filename).getParent();
+            if (parent != null && !Files.exists(parent)) {
                 // Bug in JDK? when the path contains directory that does not exist, the code throws
                 // NPE and prints out to the standard output (!) stack trace of
                 // FileNotFoundException. We still catch the exception, because this check and
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java
index 44da8c02ea..9d4903d78d 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java
@@ -713,6 +713,7 @@ public class BasePackage extends RBuiltinPackage {
         add(Tabulate.class, TabulateNodeGen::create);
         add(TempDir.class, TempDirNodeGen::create);
         add(TempFile.class, TempFileNodeGen::create);
+        add(CharTr.class, CharTr::create);
         add(ToLowerOrUpper.ToLower.class, ToLowerOrUpperFactory.ToLowerNodeGen::create);
         add(ToLowerOrUpper.ToUpper.class, ToLowerOrUpperFactory.ToUpperNodeGen::create);
         add(Traceback.class, TracebackNodeGen::create);
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/CharTr.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/CharTr.java
new file mode 100644
index 0000000000..6c81776bed
--- /dev/null
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/CharTr.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2017, 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.nodes.builtin.base;
+
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.singleElement;
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.stringValue;
+import static com.oracle.truffle.r.runtime.RError.Message.X_LONGER_THAN_Y;
+import static com.oracle.truffle.r.runtime.builtins.RBehavior.PURE;
+import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.INTERNAL;
+
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.r.nodes.attributes.RemoveRegAttributesNode;
+import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
+import com.oracle.truffle.r.nodes.function.opt.ReuseTemporaryNode;
+import com.oracle.truffle.r.runtime.RError.Message;
+import com.oracle.truffle.r.runtime.RRuntime;
+import com.oracle.truffle.r.runtime.builtins.RBuiltin;
+import com.oracle.truffle.r.runtime.data.RStringVector;
+import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
+
+@RBuiltin(name = "chartr", kind = INTERNAL, parameterNames = {"old", "new", "x"}, behavior = PURE)
+public abstract class CharTr extends RBuiltinNode.Arg3 {
+
+    static {
+        Casts casts = new Casts(CharTr.class);
+        casts.arg("old").mustBe(stringValue()).asStringVector().shouldBe(singleElement(), Message.ARGUMENT_ONLY_FIRST, "old").findFirst();
+        casts.arg("new").mustBe(stringValue()).asStringVector().shouldBe(singleElement(), Message.ARGUMENT_ONLY_FIRST, "new").findFirst();
+        casts.arg("x").mustBe(stringValue()).asStringVector();
+    }
+
+    public static CharTr create() {
+        return CharTrNodeGen.create();
+    }
+
+    @Specialization
+    RStringVector doIt(String oldStr, String newStr, RAbstractStringVector values,
+                    @Cached("create()") RemoveRegAttributesNode removeRegAttributesNode,
+                    @Cached("create()") ReuseTemporaryNode reuseTemporaryNode) {
+        if (newStr.length() < oldStr.length()) {
+            throw error(X_LONGER_THAN_Y, "old", "new");
+        }
+        RStringVector result = (RStringVector) reuseTemporaryNode.execute(values);
+        removeRegAttributesNode.execute(result);
+        Object store = result.getInternalStore();
+        for (int i = 0; i < result.getLength(); i++) {
+            String value = result.getDataAt(store, i);
+            if (RRuntime.isNA(value)) {
+                continue;
+            }
+            int replaceIdx = 0;
+            while (replaceIdx < oldStr.length()) {
+                if (replaceIdx + 2 < oldStr.length() && oldStr.charAt(replaceIdx + 1) == '-') {
+                    value = replaceRange(replaceIdx, oldStr, newStr, value);
+                    replaceIdx += 3;
+                } else {
+                    value = value.replace(oldStr.charAt(replaceIdx), newStr.charAt(replaceIdx));
+                    replaceIdx++;
+                }
+            }
+            result.setDataAt(store, i, value);
+        }
+        return result;
+    }
+
+    private String replaceRange(int replaceIdx, String oldStr, String newStr, String value) {
+        if (replaceIdx + 2 >= newStr.length() || newStr.charAt(replaceIdx + 1) != '-') {
+            throw error(X_LONGER_THAN_Y, "old", "new");
+        }
+        int oldEnd = oldStr.charAt(replaceIdx + 2);
+        int oldStart = oldStr.charAt(replaceIdx);
+        int newStart = newStr.charAt(replaceIdx);
+        int newEnd = newStr.charAt(replaceIdx + 2);
+        if (newEnd - newStart < oldEnd - oldStart) {
+            throw error(X_LONGER_THAN_Y, "old", "new");
+        }
+        for (int rangeIdx = 0; rangeIdx <= oldEnd - oldStart; rangeIdx++) {
+            value = value.replace((char) (oldStart + rangeIdx), (char) (newStart + rangeIdx));
+        }
+        return value;
+    }
+}
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 bae562ba67..d34706d3fa 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
@@ -379,6 +379,7 @@ public final class RError extends RuntimeException implements TruffleException {
         NA_INTRODUCED_COERCION("NAs introduced by coercion"),
         NA_INTRODUCED_COERCION_INT("NAs introduced by coercion to integer range"),
         ARGUMENT_WHICH_NOT_LOGICAL("argument to 'which' is not logical"),
+        X_LONGER_THAN_Y("'%s' is longer than '%s'"),
         X_NUMERIC("'x' must be numeric"),
         X_LIST_ATOMIC("'x' must be a list or atomic vector"),
         X_ARRAY_TWO("'x' must be an array of at least two dimensions"),
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 076a5762ff..cafcf1f643 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
@@ -13811,26 +13811,65 @@ integer(0)
 #argv <- list(character(0), c('semiTransparency', 'transparentBackground', 'rasterImage', 'capture', 'locator', 'events'), 0L); .Internal(charmatch(argv[[1]], argv[[2]], argv[[3]]))
 integer(0)
 
-##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.testchartr1#Ignored.Unimplemented#
+##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.testchartr1#
 #argv <- list('.', '.', c('0.02', '0.06', '0.11', '0.22', '0.56', '1.1')); .Internal(chartr(argv[[1]], argv[[2]], argv[[3]]))
 [1] "0.02" "0.06" "0.11" "0.22" "0.56" "1.1"
 
-##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.testchartr2#Ignored.Unimplemented#
+##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.testchartr2#
 #argv <- list('iXs', 'why', 'MiXeD cAsE 123'); .Internal(chartr(argv[[1]], argv[[2]], argv[[3]]))
 [1] "MwheD cAyE 123"
 
-##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.testchartr3#Ignored.Unimplemented#
+##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.testchartr3#
 #argv <- list('a-cX', 'D-Fw', 'MiXeD cAsE 123'); .Internal(chartr(argv[[1]], argv[[2]], argv[[3]]))
 [1] "MiweD FAsE 123"
 
-##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.testchartr4#Ignored.Unimplemented#
+##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.testchartr4#
 #argv <- list('.', '.', character(0)); .Internal(chartr(argv[[1]], argv[[2]], argv[[3]]))
 character(0)
 
-##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.testchartr6#Ignored.Unimplemented#
+##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.testchartr6#
 #argv <- structure(list(old = 'NA', new = 'na', x = c('NA', NA,     'BANANA')), .Names = c('old', 'new', 'x'));do.call('chartr', argv)
 [1] "na"     NA       "Banana"
 
+##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.tests#
+#chartr('0-5', '0-', 'ah3g4t')
+Error in chartr("0-5", "0-", "ah3g4t") : 'old' is longer than 'new'
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.tests#
+#chartr('0-5', '0-3', 'ah3g4t')
+Error in chartr("0-5", "0-3", "ah3g4t") : 'old' is longer than 'new'
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.tests#
+#chartr('0-5', '045', 'ah3g4t')
+Error in chartr("0-5", "045", "ah3g4t") : 'old' is longer than 'new'
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.tests#
+#chartr(c('a','b'), c('c', 'e'), c('abc', 'efb'))
+[1] "cbc" "efb"
+Warning messages:
+1: In chartr(c("a", "b"), c("c", "e"), c("abc", "efb")) :
+  argument 'old' has length > 1 and only the first element will be used
+2: In chartr(c("a", "b"), c("c", "e"), c("abc", "efb")) :
+  argument 'new' has length > 1 and only the first element will be used
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.tests#
+#chartr(c('a','b'), c(3, 2), c('abc', 'efb'))
+Error in chartr(c("a", "b"), c(3, 2), c("abc", "efb")) :
+  invalid 'new' argument
+In addition: Warning message:
+In chartr(c("a", "b"), c(3, 2), c("abc", "efb")) :
+  argument 'old' has length > 1 and only the first element will be used
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.tests#
+#chartr(c('abq'), 'cd', c('agbc', 'efb'))
+Error in chartr(c("abq"), "cd", c("agbc", "efb")) :
+  'old' is longer than 'new'
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_chartr.tests#
+#chartr(c(3, 2), c('q', 'c'), c('abc', 'efb'))
+Error in chartr(c(3, 2), c("q", "c"), c("abc", "efb")) :
+  invalid 'old' argument
+
 ##com.oracle.truffle.r.test.builtins.TestBuiltin_chol.testChol#
 #{ chol(1) }
      [,1]
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_chartr.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_chartr.java
index ea22fa4553..c3d332a3b5 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_chartr.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_chartr.java
@@ -19,26 +19,37 @@ public class TestBuiltin_chartr extends TestBase {
 
     @Test
     public void testchartr1() {
-        assertEval(Ignored.Unimplemented, "argv <- list('.', '.', c('0.02', '0.06', '0.11', '0.22', '0.56', '1.1')); .Internal(chartr(argv[[1]], argv[[2]], argv[[3]]))");
+        assertEval("argv <- list('.', '.', c('0.02', '0.06', '0.11', '0.22', '0.56', '1.1')); .Internal(chartr(argv[[1]], argv[[2]], argv[[3]]))");
     }
 
     @Test
     public void testchartr2() {
-        assertEval(Ignored.Unimplemented, "argv <- list('iXs', 'why', 'MiXeD cAsE 123'); .Internal(chartr(argv[[1]], argv[[2]], argv[[3]]))");
+        assertEval("argv <- list('iXs', 'why', 'MiXeD cAsE 123'); .Internal(chartr(argv[[1]], argv[[2]], argv[[3]]))");
     }
 
     @Test
     public void testchartr3() {
-        assertEval(Ignored.Unimplemented, "argv <- list('a-cX', 'D-Fw', 'MiXeD cAsE 123'); .Internal(chartr(argv[[1]], argv[[2]], argv[[3]]))");
+        assertEval("argv <- list('a-cX', 'D-Fw', 'MiXeD cAsE 123'); .Internal(chartr(argv[[1]], argv[[2]], argv[[3]]))");
     }
 
     @Test
     public void testchartr4() {
-        assertEval(Ignored.Unimplemented, "argv <- list('.', '.', character(0)); .Internal(chartr(argv[[1]], argv[[2]], argv[[3]]))");
+        assertEval("argv <- list('.', '.', character(0)); .Internal(chartr(argv[[1]], argv[[2]], argv[[3]]))");
     }
 
     @Test
     public void testchartr6() {
-        assertEval(Ignored.Unimplemented, "argv <- structure(list(old = 'NA', new = 'na', x = c('NA', NA,     'BANANA')), .Names = c('old', 'new', 'x'));do.call('chartr', argv)");
+        assertEval("argv <- structure(list(old = 'NA', new = 'na', x = c('NA', NA,     'BANANA')), .Names = c('old', 'new', 'x'));do.call('chartr', argv)");
+    }
+
+    @Test
+    public void tests() {
+        assertEval("chartr(c('abq'), 'cd', c('agbc', 'efb'))");
+        assertEval("chartr(c('a','b'), c('c', 'e'), c('abc', 'efb'))");
+        assertEval("chartr(c('a','b'), c(3, 2), c('abc', 'efb'))");
+        assertEval("chartr(c(3, 2), c('q', 'c'), c('abc', 'efb'))");
+        assertEval("chartr('0-5', '0-3', 'ah3g4t')");
+        assertEval("chartr('0-5', '0-', 'ah3g4t')");
+        assertEval("chartr('0-5', '045', 'ah3g4t')");
     }
 }
-- 
GitLab