From 66f84f322c1e948d7eaa51570a207bd0038ff9eb Mon Sep 17 00:00:00 2001 From: logworthy Date: Thu, 26 Nov 2020 16:38:05 +1100 Subject: [PATCH 1/7] Updated error message in rbindlist to display the class attributes of mismatching columns - added 2 tests - added joinCharVec utility function to join each element of a character vector into a single C string - updated translations for the new error message - added *.sublime-project and *.sublime-workspace to .gitignore --- .gitignore | 4 ++++ inst/tests/tests.Rraw | 2 ++ po/data.table.pot | 20 ++++++++++---------- po/zh_CN.po | 23 ++++++++++++----------- src/data.table.h | 1 + src/rbindlist.c | 14 ++++++++++---- src/utils.c | 34 ++++++++++++++++++++++++++++++++++ 7 files changed, 73 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 51cc13cd6..04c838d16 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,10 @@ src/Makevars .emacs.desktop .emacs.desktop.lock +# Sublime Text IDE files +*.sublime-project +*.sublime-workspace + # RStudio IDE files .Rproj.user data.table.Rproj diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index 4dd2809f7..a938629da 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -6995,6 +6995,8 @@ DT1 = data.table(date=as.POSIXct("2014-06-22", format="%Y-%m-%d", tz="GMT")) DT2 = data.table(date=as.Date("2014-06-23")) test(1494.1, rbind(DT1, DT2), error="Class attribute on column") test(1494.2, rbind(DT2, DT1), error="Class attribute on column") +test(1494.3, rbind(DT1, DT2), error="Class attribute on column 1 (Date) of item 2 does not match with column 1 (POSIXct, POSIXt) of item 1") +test(1494.4, rbind(DT2, DT1), error="Class attribute on column 1 (POSIXct, POSIXt) of item 2 does not match with column 1 (Date) of item 1") # test 1495 has been added to melt's test section (fix for #1055) diff --git a/po/data.table.pot b/po/data.table.pot index cea9c55a5..60a4977d4 100644 --- a/po/data.table.pot +++ b/po/data.table.pot @@ -4066,34 +4066,34 @@ msgid "" "'message'|'warning'|'error'|'none'. See news item 5 in v1.12.2." msgstr "" -#: rbindlist.c:298 +#: rbindlist.c:300 #, c-format msgid "" "Column %d of item %d has type 'factor' but has no levels; i.e. malformed." msgstr "" -#: rbindlist.c:316 +#: rbindlist.c:320 #, c-format msgid "" -"Class attribute on column %d of item %d does not match with column %d of " -"item %d." +"Class attribute on column %d (%s) of item %d does not match with column %d " +"(%s) of item %d." msgstr "" -#: rbindlist.c:326 +#: rbindlist.c:332 #, c-format msgid "" "Internal error: column %d of result is determined to be integer64 but " "maxType=='%s' != REALSXP" msgstr "" -#: rbindlist.c:362 +#: rbindlist.c:368 #, c-format msgid "" "Failed to allocate working memory for %d ordered factor levels of result " "column %d" msgstr "" -#: rbindlist.c:383 +#: rbindlist.c:389 #, c-format msgid "" "Column %d of item %d is an ordered factor but level %d ['%s'] is missing " @@ -4102,7 +4102,7 @@ msgid "" "factor will be created for this column." msgstr "" -#: rbindlist.c:388 +#: rbindlist.c:394 #, c-format msgid "" "Column %d of item %d is an ordered factor with '%s'<'%s' in its levels. But " @@ -4110,14 +4110,14 @@ msgid "" "will be created for this column due to this ambiguity." msgstr "" -#: rbindlist.c:433 +#: rbindlist.c:439 #, c-format msgid "" "Failed to allocate working memory for %d factor levels of result column %d " "when reading item %d of item %d" msgstr "" -#: rbindlist.c:523 +#: rbindlist.c:529 #, c-format msgid "Column %d of item %d: %s" msgstr "" diff --git a/po/zh_CN.po b/po/zh_CN.po index d9b54a443..819b098fa 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -4397,7 +4397,7 @@ msgstr "" "options()$datatable.rbindlist.check=='%s' 不" "是'message'|'warning'|'error'|'none'。参见 v1.12.2 更新信息中的第 5 项。" -#: rbindlist.c:298 +#: rbindlist.c:300 #, c-format msgid "" "Column %d of item %d has type 'factor' but has no levels; i.e. malformed." @@ -4405,14 +4405,15 @@ msgstr "" "第%2$d 项的第 %1$d 列为因子('factor')类型却没有因子水平(levels),格式错" "误。" -#: rbindlist.c:316 +#: rbindlist.c:320 #, c-format msgid "" -"Class attribute on column %d of item %d does not match with column %d of " -"item %d." -msgstr "第 %2$d 项的第 %1$d 列的类属性与第 %4$d 项的第 %3$d列的不匹配。" +"Class attribute on column %d (%s) of item %d does not match with column %d " +"(%s) of item %d." +msgstr "第 %3$d 项的第 %1$d (%2$s) 列的类属性与第 %6$d 项的第 %4$d (%5$d) " +"列的不匹配。" -#: rbindlist.c:326 +#: rbindlist.c:332 #, c-format msgid "" "Internal error: column %d of result is determined to be integer64 but " @@ -4420,14 +4421,14 @@ msgid "" msgstr "" "内部错误:结果中的第 %d 列应为 integer64 类型,但maxType=='%s' != REALSXP" -#: rbindlist.c:362 +#: rbindlist.c:368 #, c-format msgid "" "Failed to allocate working memory for %d ordered factor levels of result " "column %d" msgstr "未能为结果中第 %d 列的 %d 个有序因子水平分配工作内存" -#: rbindlist.c:383 +#: rbindlist.c:389 #, c-format msgid "" "Column %d of item %d is an ordered factor but level %d ['%s'] is missing " @@ -4439,7 +4440,7 @@ msgstr "" "(level)['%4$s']在第 %6$d 项第 %5$d 列的有序因子水平中缺失。每组有序因子水平" "应为其中最长有序因子水平的子集。该列将被创建为一非有序因子列。" -#: rbindlist.c:388 +#: rbindlist.c:394 #, c-format msgid "" "Column %d of item %d is an ordered factor with '%s'<'%s' in its levels. But " @@ -4450,7 +4451,7 @@ msgstr "" "的有序因子水平中却 '%5$s'<'%6$s'。由于这种模糊性,该列将被创建为一非有序因子" "列。" -#: rbindlist.c:433 +#: rbindlist.c:439 #, c-format msgid "" "Failed to allocate working memory for %d factor levels of result column %d " @@ -4458,7 +4459,7 @@ msgid "" msgstr "" "当读取第%4$d项的第%3$d个子项时,无法为第%2$d列的%1$d个因素水平分配工作内存" -#: rbindlist.c:523 +#: rbindlist.c:529 #, c-format msgid "Column %d of item %d: %s" msgstr "第 %2$d 项的第 %1$d 列: %3$s" diff --git a/src/data.table.h b/src/data.table.h index d045d50f4..60d5faa15 100644 --- a/src/data.table.h +++ b/src/data.table.h @@ -241,6 +241,7 @@ bool islocked(SEXP x); SEXP islockedR(SEXP x); bool need2utf8(SEXP x); SEXP coerceUtf8IfNeeded(SEXP x); +char *joinCharVec(SEXP x, const char *sep); // types.c char *end(char *start); diff --git a/src/rbindlist.c b/src/rbindlist.c index bb42502be..9bcac716f 100644 --- a/src/rbindlist.c +++ b/src/rbindlist.c @@ -270,6 +270,8 @@ SEXP rbindlist(SEXP l, SEXP usenamesArg, SEXP fillArg, SEXP idcolArg) } SEXP coercedForFactor = NULL; + SEXP thisClass = R_NilValue; + SEXP firstClass = R_NilValue; for(int j=0; j Date: Thu, 26 Nov 2020 17:12:56 +1100 Subject: [PATCH 2/7] fixed call to strlen --- src/utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.c b/src/utils.c index 28b2064f9..e7ecf7275 100644 --- a/src/utils.c +++ b/src/utils.c @@ -389,7 +389,7 @@ char *joinCharVec (SEXP x, const char *sep) error(_("Internal error: unsupported type '%s' passed to joinCharVec()"), type2char(TYPEOF(x))); for (R_xlen_t i=0; i Date: Thu, 26 Nov 2020 20:12:11 +1100 Subject: [PATCH 3/7] incorporating PR feedback - renamed joinCharVec to concatCharVec - fixed spacing - added # nocov - fixed calls to 'free' - moved variable declarations to point of use --- src/data.table.h | 2 +- src/rbindlist.c | 19 ++++++++++------- src/utils.c | 54 +++++++++++++++++++++++++----------------------- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/data.table.h b/src/data.table.h index 60d5faa15..8c1ba881c 100644 --- a/src/data.table.h +++ b/src/data.table.h @@ -241,7 +241,7 @@ bool islocked(SEXP x); SEXP islockedR(SEXP x); bool need2utf8(SEXP x); SEXP coerceUtf8IfNeeded(SEXP x); -char *joinCharVec(SEXP x, const char *sep); +char *concatCharVec(SEXP x, const char *sep); // types.c char *end(char *start); diff --git a/src/rbindlist.c b/src/rbindlist.c index 9bcac716f..07832d420 100644 --- a/src/rbindlist.c +++ b/src/rbindlist.c @@ -270,8 +270,6 @@ SEXP rbindlist(SEXP l, SEXP usenamesArg, SEXP fillArg, SEXP idcolArg) } SEXP coercedForFactor = NULL; - SEXP thisClass = R_NilValue; - SEXP firstClass = R_NilValue; for(int j=0; j Date: Thu, 26 Nov 2020 21:46:17 +1100 Subject: [PATCH 4/7] more targeted used of # nocov within utils.c --- src/utils.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/utils.c b/src/utils.c index 414d6be15..00ac8b3e4 100644 --- a/src/utils.c +++ b/src/utils.c @@ -377,7 +377,6 @@ SEXP coerceUtf8IfNeeded(SEXP x) { // Concatenate a character vector into a single string, e.g. for printing error messages // adapted from https://stackoverflow.com/a/58163237 // make sure to *free* the returned string after use -// # nocov start char *concatCharVec (SEXP x, const char *sep) { char *concatenated = NULL; /* pointer to concatenated string w/sep */ @@ -387,14 +386,14 @@ char *concatCharVec (SEXP x, const char *sep) /* check that a character vector has been passed */ if (TYPEOF(x) != STRSXP) - error(_("Internal error: unsupported type '%s' passed to joinCharVec()"), type2char(TYPEOF(x))); + error(_("Internal error: unsupported type '%s' passed to concatCharVec()"), type2char(TYPEOF(x))); // # nocov for (R_xlen_t i=0; i Date: Mon, 30 Nov 2020 09:51:25 +1100 Subject: [PATCH 5/7] updated concatCharVec to return a CHARSXP --- src/data.table.h | 2 +- src/rbindlist.c | 14 ++------------ src/utils.c | 15 ++++++++------- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/data.table.h b/src/data.table.h index 8c1ba881c..8daa04812 100644 --- a/src/data.table.h +++ b/src/data.table.h @@ -241,7 +241,7 @@ bool islocked(SEXP x); SEXP islockedR(SEXP x); bool need2utf8(SEXP x); SEXP coerceUtf8IfNeeded(SEXP x); -char *concatCharVec(SEXP x, const char *sep); +SEXP concatCharVec(SEXP x, const char *sep); // types.c char *end(char *start); diff --git a/src/rbindlist.c b/src/rbindlist.c index 07832d420..467f08176 100644 --- a/src/rbindlist.c +++ b/src/rbindlist.c @@ -313,18 +313,8 @@ SEXP rbindlist(SEXP l, SEXP usenamesArg, SEXP fillArg, SEXP idcolArg) SEXP thisClass = PROTECT(getAttrib(thisCol, R_ClassSymbol)); SEXP firstClass = PROTECT(getAttrib(firstCol, R_ClassSymbol)); if (!R_compute_identical(thisClass, firstClass, 0)) { - #define BUFSIZE 1024 // 'error' function length limit in R's errors.c is 8192 - char thisClassBuf[BUFSIZE]; - char* thisClassStr = concatCharVec(thisClass, ", "); - strncpy(thisClassBuf, thisClassStr, BUFSIZE); - thisClassBuf[BUFSIZE-1] = '\0'; - free(thisClassStr); - char firstClassBuf[BUFSIZE]; - char* firstClassStr = concatCharVec(firstClass, ", "); - strncpy(firstClassBuf, firstClassStr, BUFSIZE); - firstClassBuf[BUFSIZE-1] = '\0'; - free(firstClassStr); - error(_("Class attribute on column %d (%s) of item %d does not match with column %d (%s) of item %d."), w+1, thisClassBuf, i+1, firstw+1, firstClassBuf, firsti+1); + error(_("Class attribute on column %d (%s) of item %d does not match with column %d (%s) of item %d."), + w+1, CHAR(concatCharVec(thisClass, ", ")), i+1, firstw+1, CHAR(concatCharVec(firstClass, ", ")), firsti+1); } UNPROTECT(2); } diff --git a/src/utils.c b/src/utils.c index 00ac8b3e4..b16a3490c 100644 --- a/src/utils.c +++ b/src/utils.c @@ -374,10 +374,10 @@ SEXP coerceUtf8IfNeeded(SEXP x) { return(ans); } -// Concatenate a character vector into a single string, e.g. for printing error messages +// Concatenate a character vector into a single CHARSXP, e.g. for printing error messages // adapted from https://stackoverflow.com/a/58163237 -// make sure to *free* the returned string after use -char *concatCharVec (SEXP x, const char *sep) +// make sure to *UNPROTECT* the returned CHARSXP after use +SEXP concatCharVec (SEXP x, const char *sep) { char *concatenated = NULL; /* pointer to concatenated string w/sep */ size_t lensep = strlen (sep), /* length of separator */ @@ -389,22 +389,23 @@ char *concatCharVec (SEXP x, const char *sep) error(_("Internal error: unsupported type '%s' passed to concatCharVec()"), type2char(TYPEOF(x))); // # nocov for (R_xlen_t i=0; i Date: Tue, 15 Dec 2020 00:46:21 +1100 Subject: [PATCH 6/7] reverted formatting issue --- src/utils.c | 801 +++++++++++++++++++++++++--------------------------- 1 file changed, 383 insertions(+), 418 deletions(-) diff --git a/src/utils.c b/src/utils.c index 9acdb812a..8a3598f57 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,418 +1,383 @@ -#include "data.table.h" - -bool isRealReallyInt(SEXP x) { - if (!isReal(x)) return(false); - R_xlen_t n=xlength(x), i=0; - double *dx = REAL(x); - while (inx) || (icols[i]<1)) - error(_("argument specifying columns specify non existing column(s): cols[%d]=%d"), i+1, icols[i]); // handles NAs also - } - } else if (isString(cols)) { - SEXP xnames = PROTECT(getAttrib(x, R_NamesSymbol)); protecti++; - if (isNull(xnames)) - error(_("'x' argument data.table has no names")); - ricols = PROTECT(chmatch(cols, xnames, 0)); protecti++; - int *icols = INTEGER(ricols); - for (int i=0; iINT32_MAX || rfill<=INT32_MIN) ? NA_INTEGER : (int32_t)rfill; - dfill[0] = (double)rfill; - i64fill[0] = rfill; - } - } else { - double rfill = REAL(fill)[0]; - if (ISNAN(rfill)) { - // NA -> NA, NaN -> NaN - ifill[0] = NA_INTEGER; dfill[0] = rfill; i64fill[0] = NA_INTEGER64; - } else { - ifill[0] = (!R_FINITE(rfill) || rfill>INT32_MAX || rfill<=INT32_MIN) ? NA_INTEGER : (int32_t)rfill; - dfill[0] = rfill; - i64fill[0] = (!R_FINITE(rfill) || rfill>(double)INT64_MAX || rfill<=(double)INT64_MIN) ? NA_INTEGER64 : (int64_t)rfill; - } - } - } else if (isLogical(fill) && LOGICAL(fill)[0]==NA_LOGICAL) { - ifill[0] = NA_INTEGER; dfill[0] = NA_REAL; i64fill[0] = NA_INTEGER64; - } else { - error(_("%s: fill argument must be numeric"), __func__); - } -} -SEXP coerceFillR(SEXP fill) { - int protecti=0; - double dfill=NA_REAL; - int32_t ifill=NA_INTEGER; - int64_t i64fill=NA_INTEGER64; - coerceFill(fill, &dfill, &ifill, &i64fill); - SEXP ans = PROTECT(allocVector(VECSXP, 3)); protecti++; - SET_VECTOR_ELT(ans, 0, allocVector(INTSXP, 1)); - SET_VECTOR_ELT(ans, 1, allocVector(REALSXP, 1)); - SET_VECTOR_ELT(ans, 2, allocVector(REALSXP, 1)); - INTEGER(VECTOR_ELT(ans, 0))[0] = ifill; - REAL(VECTOR_ELT(ans, 1))[0] = dfill; - ((int64_t *)REAL(VECTOR_ELT(ans, 2)))[0] = i64fill; - setAttrib(VECTOR_ELT(ans, 2), R_ClassSymbol, ScalarString(char_integer64)); - UNPROTECT(protecti); - return ans; -} - -inline bool INHERITS(SEXP x, SEXP char_) { - // Thread safe inherits() by pre-calling install() in init.c and then - // passing those char_* in here for simple and fast non-API pointer compare. - // The thread-safety aspect here is only currently actually needed for list columns in - // fwrite() where the class of the cell's vector is tested; the class of the column - // itself is pre-stored by fwrite (for example in isInteger64[] and isITime[]). - // Thread safe in the limited sense of correct and intended usage : - // i) no API call such as install() or mkChar() must be passed in. - // ii) no attrib writes must be possible in other threads. - SEXP klass; - if (isString(klass = getAttrib(x, R_ClassSymbol))) { - for (int i=0; i1?"s":""); - // GetVerbose() (slightly expensive call of all options) called here only when needed - } -} - -// lock, unlock and islocked at C level : -// 1) for speed to reduce overhead -// 2) to avoid an R level wrapper which bumps MAYBE_SHARED; see the unlock after eval(jval) in data.table.R, #1341 #2245 -SEXP lock(SEXP DT) { - setAttrib(DT, sym_datatable_locked, ScalarLogical(TRUE)); - return DT; -} -SEXP unlock(SEXP DT) { - setAttrib(DT, sym_datatable_locked, R_NilValue); - return DT; -} -bool islocked(SEXP DT) { - SEXP att = getAttrib(DT, sym_datatable_locked); - return isLogical(att) && LENGTH(att)==1 && LOGICAL(att)[0]==1; -} -SEXP islockedR(SEXP DT) { - return ScalarLogical(islocked(DT)); -} - -bool need2utf8(SEXP x) { - const int xlen = length(x); - SEXP *xd = STRING_PTR(x); - for (int i=0; i -SEXP dt_zlib_version() { - char out[51]; - snprintf(out, 50, "zlibVersion()==%s ZLIB_VERSION==%s", zlibVersion(), ZLIB_VERSION); - return ScalarString(mkChar(out)); -} +#include "data.table.h" + +bool isRealReallyInt(SEXP x) { + if (!isReal(x)) return(false); + R_xlen_t n=xlength(x), i=0; + double *dx = REAL(x); + while (inx) || (icols[i]<1)) + error(_("argument specifying columns specify non existing column(s): cols[%d]=%d"), i+1, icols[i]); // handles NAs also + } + } else if (isString(cols)) { + SEXP xnames = PROTECT(getAttrib(x, R_NamesSymbol)); protecti++; + if (isNull(xnames)) + error(_("'x' argument data.table has no names")); + ricols = PROTECT(chmatch(cols, xnames, 0)); protecti++; + int *icols = INTEGER(ricols); + for (int i=0; iINT32_MAX || rfill<=INT32_MIN) ? NA_INTEGER : (int32_t)rfill; + dfill[0] = (double)rfill; + i64fill[0] = rfill; + } + } else { + double rfill = REAL(fill)[0]; + if (ISNAN(rfill)) { + // NA -> NA, NaN -> NaN + ifill[0] = NA_INTEGER; dfill[0] = rfill; i64fill[0] = NA_INTEGER64; + } else { + ifill[0] = (!R_FINITE(rfill) || rfill>INT32_MAX || rfill<=INT32_MIN) ? NA_INTEGER : (int32_t)rfill; + dfill[0] = rfill; + i64fill[0] = (!R_FINITE(rfill) || rfill>(double)INT64_MAX || rfill<=(double)INT64_MIN) ? NA_INTEGER64 : (int64_t)rfill; + } + } + } else if (isLogical(fill) && LOGICAL(fill)[0]==NA_LOGICAL) { + ifill[0] = NA_INTEGER; dfill[0] = NA_REAL; i64fill[0] = NA_INTEGER64; + } else { + error(_("%s: fill argument must be numeric"), __func__); + } +} +SEXP coerceFillR(SEXP fill) { + int protecti=0; + double dfill=NA_REAL; + int32_t ifill=NA_INTEGER; + int64_t i64fill=NA_INTEGER64; + coerceFill(fill, &dfill, &ifill, &i64fill); + SEXP ans = PROTECT(allocVector(VECSXP, 3)); protecti++; + SET_VECTOR_ELT(ans, 0, allocVector(INTSXP, 1)); + SET_VECTOR_ELT(ans, 1, allocVector(REALSXP, 1)); + SET_VECTOR_ELT(ans, 2, allocVector(REALSXP, 1)); + INTEGER(VECTOR_ELT(ans, 0))[0] = ifill; + REAL(VECTOR_ELT(ans, 1))[0] = dfill; + ((int64_t *)REAL(VECTOR_ELT(ans, 2)))[0] = i64fill; + setAttrib(VECTOR_ELT(ans, 2), R_ClassSymbol, ScalarString(char_integer64)); + UNPROTECT(protecti); + return ans; +} + +inline bool INHERITS(SEXP x, SEXP char_) { + // Thread safe inherits() by pre-calling install() in init.c and then + // passing those char_* in here for simple and fast non-API pointer compare. + // The thread-safety aspect here is only currently actually needed for list columns in + // fwrite() where the class of the cell's vector is tested; the class of the column + // itself is pre-stored by fwrite (for example in isInteger64[] and isITime[]). + // Thread safe in the limited sense of correct and intended usage : + // i) no API call such as install() or mkChar() must be passed in. + // ii) no attrib writes must be possible in other threads. + SEXP klass; + if (isString(klass = getAttrib(x, R_ClassSymbol))) { + for (int i=0; i1?"s":""); + // GetVerbose() (slightly expensive call of all options) called here only when needed + } +} + +// lock, unlock and islocked at C level : +// 1) for speed to reduce overhead +// 2) to avoid an R level wrapper which bumps MAYBE_SHARED; see the unlock after eval(jval) in data.table.R, #1341 #2245 +SEXP lock(SEXP DT) { + setAttrib(DT, sym_datatable_locked, ScalarLogical(TRUE)); + return DT; +} +SEXP unlock(SEXP DT) { + setAttrib(DT, sym_datatable_locked, R_NilValue); + return DT; +} +bool islocked(SEXP DT) { + SEXP att = getAttrib(DT, sym_datatable_locked); + return isLogical(att) && LENGTH(att)==1 && LOGICAL(att)[0]==1; +} +SEXP islockedR(SEXP DT) { + return ScalarLogical(islocked(DT)); +} + +bool need2utf8(SEXP x) { + const int xlen = length(x); + SEXP *xd = STRING_PTR(x); + for (int i=0; i +SEXP dt_zlib_version() { + char out[51]; + snprintf(out, 50, "zlibVersion()==%s ZLIB_VERSION==%s", zlibVersion(), ZLIB_VERSION); + return ScalarString(mkChar(out)); +} + From f8150fe30a638d7dc44dc6d9d238ab72f403d601 Mon Sep 17 00:00:00 2001 From: logworthy Date: Tue, 15 Dec 2020 00:46:47 +1100 Subject: [PATCH 7/7] re-added concatCharVec function --- src/utils.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/utils.c b/src/utils.c index 8a3598f57..68ade8425 100644 --- a/src/utils.c +++ b/src/utils.c @@ -381,3 +381,38 @@ SEXP dt_zlib_version() { return ScalarString(mkChar(out)); } +// Concatenate a character vector into a single CHARSXP, e.g. for printing error messages +// adapted from https://stackoverflow.com/a/58163237 +// make sure to *UNPROTECT* the returned CHARSXP after use +SEXP concatCharVec (SEXP x, const char *sep) +{ + char *concatenated = NULL; /* pointer to concatenated string w/sep */ + size_t lensep = strlen (sep), /* length of separator */ + sz = 0; /* current stored size */ + int first = 1; /* flag whether first term */ + + /* check that a character vector has been passed */ + if (TYPEOF(x) != STRSXP) + error(_("Internal error: unsupported type '%s' passed to concatCharVec()"), type2char(TYPEOF(x))); // # nocov + + for (R_xlen_t i=0; i