diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Sprintf.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Sprintf.java index fcadf58d328e1fd882f6721e5453e36135a1b4b0..0aa1c986c3a7f8c2b6c130c96458da60ea6e976e 100644 --- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Sprintf.java +++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Sprintf.java @@ -22,6 +22,7 @@ */ package com.oracle.truffle.r.nodes.builtin.base; +import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.stringValue; import static com.oracle.truffle.r.runtime.builtins.RBehavior.PURE; import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.INTERNAL; @@ -31,11 +32,14 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.r.nodes.builtin.RBuiltinNode; +import com.oracle.truffle.r.runtime.ArgumentsSignature; import com.oracle.truffle.r.runtime.RError; +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.RArgsValuesAndNames; import com.oracle.truffle.r.runtime.data.RDataFactory; +import com.oracle.truffle.r.runtime.data.RList; import com.oracle.truffle.r.runtime.data.RMissing; import com.oracle.truffle.r.runtime.data.RNull; import com.oracle.truffle.r.runtime.data.RStringVector; @@ -49,13 +53,19 @@ import com.oracle.truffle.r.runtime.data.model.RAbstractVector; public abstract class Sprintf extends RBuiltinNode.Arg2 { static { - Casts.noCasts(Sprintf.class); + Casts casts = new Casts(Sprintf.class); + casts.arg("fmt").mustBe(stringValue()).asStringVector(); } public abstract Object executeObject(String fmt, Object args); @Child private Sprintf sprintfRecursive; + @Specialization + protected RStringVector sprintf(RAbstractStringVector fmt, RList values) { + return sprintf(fmt, new RArgsValuesAndNames(values.getReadonlyData(), ArgumentsSignature.empty(values.getLength()))); + } + @Specialization protected RStringVector sprintf(@SuppressWarnings("unused") RAbstractStringVector fmt, @SuppressWarnings("unused") RNull x) { return RDataFactory.createEmptyStringVector(); @@ -272,7 +282,10 @@ public abstract class Sprintf extends RBuiltinNode.Arg2 { data[i] = (String) formattedObj; } else { RStringVector formatted = (RStringVector) formattedObj; - assert formatted.getLength() > 0; + if (formatted.getLength() == 0) { + // Any NULL inside args causes the whole result to be empty vector + return formatted; + } data[i] = formatted.getDataAt(i % formatted.getLength()); } } @@ -494,7 +507,11 @@ public abstract class Sprintf extends RBuiltinNode.Arg2 { widthAndPrecision(cs, j, fi); j = fi.nextChar; } else if (c == '.') { - // apparently precision can be specified without width as well + // apparently precision can be specified without width as well, but in + // such case the '0' prefix if any should be interpreted as width... + if (fi.padZero) { + fi.padZero = false; + } oneWidth(cs, j + 1, fi, false); j = fi.nextChar; } else { @@ -532,7 +549,7 @@ public abstract class Sprintf extends RBuiltinNode.Arg2 { if (isNumeric(cs[j])) { n = number(cs, j, fi); j = fi.nextChar; - } else { + } else if (cs[j] == '*') { assert cs[j] == '*'; if (width) { fi.widthIsArg = true; @@ -548,6 +565,11 @@ public abstract class Sprintf extends RBuiltinNode.Arg2 { } else { n = fi.argc++; } + } else { + if (!isConversion(cs[j])) { + throw RError.error(RError.NO_CALLER, Message.UNRECOGNIZED_FORMAT, new String(cs)); + } + n = 0; } if (width) { fi.width = n; 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 4231476011c26ece8a58fcf9761ecac6eda2daf6..b85c0d981e0f06154d27e9499e34045b0024d7da 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 @@ -67588,6 +67588,33 @@ $`2` [1] NA +##com.oracle.truffle.r.test.builtins.TestBuiltin_sprintf.testCornerCases#Ignored.ImplementationError# +#{ sprintf(c('hello %d %s', 'world %d %s'), list(2, 'x')) } +Error in sprintf(c("hello %d %s", "world %d %s"), list(2, "x")) : + unsupported type + +##com.oracle.truffle.r.test.builtins.TestBuiltin_sprintf.testGenericDispatch#Ignored.Unimplemented# +#{ as.character.myclass65231 <- function(x) '42'; y <- 2; class(y) <- 'myclass65231'; sprintf('%s', y); } +[1] "42" + +##com.oracle.truffle.r.test.builtins.TestBuiltin_sprintf.testGenericDispatch#Ignored.Unimplemented# +#{ as.double.myclass65321 <- function(x) 3.14; y <- 'str'; class(y) <- 'myclass65321'; sprintf('%g', y); } +Error in sprintf("%g", y) : + invalid format '%g'; use format %s for character objects + +##com.oracle.truffle.r.test.builtins.TestBuiltin_sprintf.testGenericDispatch#Ignored.Unimplemented# +#{ as.double.myclass65321 <- function(x) 3.14; y <- 3L; class(y) <- 'myclass65321'; sprintf('%g', y); } +[1] "3.14" + +##com.oracle.truffle.r.test.builtins.TestBuiltin_sprintf.testSprintf# +#{ asdfgerta <- sprintf('%.0f', 1); } + +##com.oracle.truffle.r.test.builtins.TestBuiltin_sprintf.testSprintf# +#{ asdfgerta <- sprintf('%0.0f', 1); } + +##com.oracle.truffle.r.test.builtins.TestBuiltin_sprintf.testSprintf# +#{ asdfgerta <- sprintf('%0.f', 1); } + ##com.oracle.truffle.r.test.builtins.TestBuiltin_sprintf.testSprintf# #{ sprintf("%.3g", 1.234) } [1] "1.23" @@ -67672,6 +67699,10 @@ character(0) #{ sprintf("foo") } [1] "foo" +##com.oracle.truffle.r.test.builtins.TestBuiltin_sprintf.testSprintf# +#{ sprintf('%s', list('hello world')) } +[1] "hello world" + ##com.oracle.truffle.r.test.builtins.TestBuiltin_sprintf.testSprintf# #{ sprintf('plot_%02g', 3L) } [1] "plot_03" @@ -67692,6 +67723,10 @@ character(0) #{ sprintf(c("foo", "bar")) } [1] "foo" "bar" +##com.oracle.truffle.r.test.builtins.TestBuiltin_sprintf.testSprintf# +#{ sprintf(c('hello', 'world'), NULL) } +character(0) + ##com.oracle.truffle.r.test.builtins.TestBuiltin_sprintf.testsprintf1# #argv <- list('%s is not TRUE', 'identical(fxy, c(1, 2, 3))'); .Internal(sprintf(argv[[1]], argv[[2]])) [1] "identical(fxy, c(1, 2, 3)) is not TRUE" diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_sprintf.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_sprintf.java index 4fee95e62edcbd039e3d07d33089fb58368b249e..de38c81297d0623969abaa6c5f35a5d8a162a612 100644 --- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_sprintf.java +++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_sprintf.java @@ -151,5 +151,28 @@ public class TestBuiltin_sprintf extends TestBase { assertEval("{ sprintf(c(\"foo %f %d\", \"bar %f %d\"), c(7,1), c(42L, 2L)) }"); assertEval("{ sprintf(\"%.3g\", 1.234) }"); assertEval("{ sprintf('plot_%02g', 3L) }"); + assertEval("{ sprintf(c('hello', 'world'), NULL) }"); + assertEval("{ sprintf('%s', list('hello world')) }"); + // Note: we save the result to variable to avoid diff because of different formatting, + // however, at least we test that the format expression is not causing any problems to + // FastR. + assertEval("{ asdfgerta <- sprintf('%0.f', 1); }"); + assertEval("{ asdfgerta <- sprintf('%0.0f', 1); }"); + assertEval("{ asdfgerta <- sprintf('%.0f', 1); }"); + } + + @Test + public void testGenericDispatch() { + // from the doc: sprintf uses as.character for %s arguments, and as.double for %f, %E, ... + assertEval(Ignored.Unimplemented, "{ as.character.myclass65231 <- function(x) '42'; y <- 2; class(y) <- 'myclass65231'; sprintf('%s', y); }"); + assertEval(Ignored.Unimplemented, "{ as.double.myclass65321 <- function(x) 3.14; y <- 3L; class(y) <- 'myclass65321'; sprintf('%g', y); }"); + // catch: probably before calling as.double there is numeric validation? Strings are not OK + // even, when they have as.double function + assertEval(Ignored.Unimplemented, "{ as.double.myclass65321 <- function(x) 3.14; y <- 'str'; class(y) <- 'myclass65321'; sprintf('%g', y); }"); + } + + @Test + public void testCornerCases() { + assertEval(Ignored.ImplementationError, "{ sprintf(c('hello %d %s', 'world %d %s'), list(2, 'x')) }"); } }