Skip to content

Commit

Permalink
#420 ongoing
Browse files Browse the repository at this point in the history
  • Loading branch information
gagolews committed May 21, 2021
1 parent dbbcd05 commit 4eacafa
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 10 deletions.
2 changes: 1 addition & 1 deletion NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* TODO ... [NEW FEATURE] #434: `stri_datetime_format` and `stri_datetime_parse`
is now also vectorised with respect to the `format` argument.

* [INTERNAL] `stri_prepare_arg*` have been refactored, buffer overruns
* [INTERNAL] `stri_prepare_arg*`s have been refactored, buffer overruns
in the exception handling subsystem are now avoided.


Expand Down
25 changes: 25 additions & 0 deletions devel/printf-test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <stdio.h>
#include <math.h>

int main()
{
float vals1[] = {-INFINITY, -1.0, -0.0, 0.0, 0.5, 1.0, 1.5, 2.0, INFINITY, NAN};
for (int i=0; i<sizeof(vals1)/sizeof(float); ++i) {
float v = vals1[i];
printf("[%6.0f]\t[%0+6.1f]\t[%0 6.1f]\t[%-+6.1f]\t[%- 6.1f]\t[%.1f]\t[%6.1f]\n", v, v, v, v, v, v, v);
}

int vals3[] = {-1, 0, 1};
for (int i=0; i<sizeof(vals3)/sizeof(int); ++i) {
int v = vals3[i];
printf("[% 3d]\t[%+3d]\t[%03d]\t[% 03d]\t[%+03d]\t[%5.3d]\t[% 5.3d]\t[%+5.3d]\n", v, v, v, v, v, v, v, v);
printf("[%# 3X]\t[%#+3X]\t[%#03X]\t[%# 03X]\t[%#+03X]\t[%#5.3X]\t[%# 5.3X]\t[%#+5.3X]\n", v, v, v, v, v, v, v, v);
}

const char* vals2[] = {"", "abc", "abcdef", "abcdefghi"};
for (int i=0; i<sizeof(vals2)/sizeof(const char*); ++i) {
const char* v = vals2[i];
printf("[%05s]\t[% s]\t[%+s][%- 5s]\t[%5.3s]\n", v, v, v, v, v);
}
return 0;
}
10 changes: 10 additions & 0 deletions devel/tinytest/test-sprintf.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ expect_identical(stri_sprintf("%s", 1:10, character(0)), character(0))
expect_identical(stri_sprintf(rep("%s", 10), 1:10, character(0)), character(0))
expect_warning(stri_sprintf(rep("%s", 10), 1:5, 1:3))

sprintf("%10.3f", c(-Inf, -0, 0, Inf, NaN, NA_real_))
sprintf("%010.3f", c(-Inf, -0, 0, Inf, NaN, NA_real_))
sprintf("%+10.3f", c(-Inf, -0, 0, Inf, NaN, NA_real_))
sprintf("%- 10.3f", c(-Inf, -0, 0, Inf, NaN, NA_real_))

sprintf("% f", c(-Inf, -0, 0, Inf, NaN, NA_real_)) # space NaN/NA
sprintf("%+f", c(-Inf, -0, 0, Inf, NaN, NA_real_)) # no plus NaN/NA (bug glibc has it)
sprintf("% d", c(-1, 0, 1, NA_integer_)) # no space NA


'
sprintf("%2$s", 1, 2) # warning - unsused arg
sprintf("%3$s", 1, 2, 3) # warning - unsused arg
Expand Down
140 changes: 131 additions & 9 deletions src/stri_sprintf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,17 @@ struct StriSprintfFormatSpec
// gG uses eE if precision <= exponent < -4
}

std::string toString()

std::string toString(bool use_sign=true, bool use_pad=true)
{
normalise();
std::string f("%");
if (alternate_output) f.push_back('#');
if (sign_space) f.push_back(' ');
if (sign_plus) f.push_back('+');
if (pad_from_right) f.push_back('-');
if (pad_zero) f.push_back('0');
if (min_width != NA_INTEGER) f.append(std::to_string(min_width));
if (use_sign && sign_space) f.push_back(' ');
if (use_sign && sign_plus) f.push_back('+');
if (use_pad && pad_from_right) f.push_back('-');
if (use_pad && pad_zero) f.push_back('0');
if (use_pad && min_width != NA_INTEGER) f.append(std::to_string(min_width));
if (precision != NA_INTEGER) {
f.push_back('.');
f.append(std::to_string(precision));
Expand All @@ -119,6 +120,11 @@ struct StriSprintfFormatSpec

void normalise()
{
if (type_spec == 'i')
type_spec = 'd'; // synonym

// TODO: warnings when switching off the flags?

if (min_width != NA_INTEGER && min_width < 0) {
min_width = -min_width;
pad_from_right = true;
Expand All @@ -135,6 +141,22 @@ struct StriSprintfFormatSpec

if (sign_plus)
sign_space = false;

if (type == STRI_SPRINTF_TYPE_STRING) {
pad_zero = false; // [-Wformat=] even warns about this
sign_plus = false; // [-Wformat=] even warns about this
sign_space = false; // [-Wformat=] even warns about this
alternate_output = false;
precision = NA_INTEGER; // TODO: maximum width/length? see below for discussion
}
else if (type == STRI_SPRINTF_TYPE_INTEGER) {
// precision -- minimal number of digits that must appear

if (type_spec != 'd') { // and not i, because i->d
sign_plus = false; // [-Wformat=] even warns about this
sign_space = false; // [-Wformat=] even warns about this
}
}
}
};

Expand Down Expand Up @@ -485,10 +507,110 @@ SEXP stri__sprintf_1(
spec.normalise();

//Rprintf("*** spec=%s\n", spec.toString().c_str());
buf += spec.toString();
//buf += spec.toString();

std::string preformatted_datum;
if (spec.type_spec == 'd') {
int datum = data.getIntegerOrNA(which_datum);
if (datum != NA_INTEGER) {
if (datum >= 0) {
if (spec.sign_plus) preformatted_datum.push_back('+');
else if (spec.sign_space) preformatted_datum.push_back(' ');
}
else preformatted_datum.push_back('-');

spec.toString(/*use_sign*/false, /*use_pad*/false);
std::abs(datum);
// TODO......................................................
}
else {
if (spec.sign_plus) {
// glibc produces "+nan", but we will output " nan" instead
preformatted_datum.push_back(' ');
}
else if (spec.sign_space)
preformatted_datum.push_back(' ');

preformatted_datum.append(na_string.c_str());
}
}
else if (spec.type == STRI_SPRINTF_TYPE_INTEGER) { // integer, but not d
STRI_ASSERT(spec.type_spec != 'i'); // normalised i->d
STRI_ASSERT(!spec.sign_plus);
STRI_ASSERT(!spec.sign_space);

int datum = data.getIntegerOrNA(which_datum);
if (datum != NA_INTEGER) {
spec.toString(/*use_sign*/false, /*use_pad*/false);
(unsigned int)(datum);
// TODO......................................................
}
else {
preformatted_datum.append(na_string.c_str());
}

} else if (spec.type == STRI_SPRINTF_TYPE_DOUBLE) {
double datum = data.getDoubleOrNA(which_datum);
if (R_FINITE(datum)) {
if (datum >= 0.0) {
if (spec.sign_plus) preformatted_datum.push_back('+');
else if (spec.sign_space) preformatted_datum.push_back(' ');
}
else preformatted_datum.push_back('-');

spec.toString(/*use_sign*/false, /*use_pad*/false);
std::abs(datum);
// TODO......................................................
}
else {
// alternate_output has no effect (use inf_string etc. instead)

if (ISNA(datum) || ISNAN(datum)) {
if (spec.sign_plus) {
// glibc produces "+nan", but we will output " nan" instead
preformatted_datum.push_back(' ');
}
else if (spec.sign_space)
preformatted_datum.push_back(' ');
}
else if (datum < 0.0 /* minus infinity */)
preformatted_datum.push_back('-');
else { // plus infinity
if (spec.sign_plus)
preformatted_datum.push_back('+');
else if (spec.sign_space)
preformatted_datum.push_back(' ');
}

if (ISNA(datum))
preformatted_datum.append(na_string.c_str());
else if (ISNAN(datum))
preformatted_datum.append(nan_string.c_str());
else
preformatted_datum.append(inf_string.c_str());
}
} else { // string
STRI_ASSERT(!spec.pad_zero);
STRI_ASSERT(!spec.sign_plus);
STRI_ASSERT(!spec.sign_space);
STRI_ASSERT(!spec.alternate_output);
STRI_ASSERT(spec.precision == NA_INTEGER); // TODO: maximum width/length?
const String8& datum = data.getStringOrNA(which_datum);
if (!datum.isNA()) {
// TODO: if (spec.precision != NA_INTEGER)
// TODO: use_length - truncation can be tricky
// TODO: with characters of width 0 though
preformatted_datum.append(datum.c_str());
} else
preformatted_datum.append(na_string.c_str());
}

//TODO: pad_from_right always add spaces
//TODO: pad_zero "-00000" "+00000" " 00000" "0x0000" "0X0000" but not NA/Inf/... and only numerics
//TODO: min_width


// which_datum
// if (use_length_val) width = str.countCodePoints();
// if (use_length) width = str.countCodePoints();
// else width = stri__width_string(str.c_str(), str.length())
}
// NA_STRING
Expand Down

0 comments on commit 4eacafa

Please sign in to comment.