Rev 42045 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
/** R : A Computer Language for Statistical Data Analysis* Copyright (C) 2002-7 the R Development Core Team** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License as published by* the Free Software Foundation; either version 2 of the License, or* (at your option) any later version.** This program 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 for more details.** You should have received a copy of the GNU General Public License* along with this program; if not, write to the Free Software* Foundation, Inc., 51 Franklin Street Fifth Floor, Boston, MA 02110-1301 USA** Originally written by Jonathan Rougier, email J.C.Rougier@durham.ac.uk*/#ifdef HAVE_CONFIG_H#include <config.h>#endif#include <Defn.h>#include "RBufferUtils.h"#define MAXLINE MAXELTSIZESEXP attribute_hidden do_sprintf(SEXP call, SEXP op, SEXP args, SEXP env){int i, nargs, cnt, v, thislen, nfmt;const char *formatString, *ss; char *starc;/* fmt2 is a copy of fmt with '*' expanded.bit will hold numeric formats and %<w>s, so be quite small. */char fmt[MAXLINE+1], fmt2[MAXLINE+10], *fmtp, bit[MAXLINE+1],*outputString;size_t n, cur, chunk;SEXP format, ans, _this, a[100], tmp;int ns, maxlen, lens[100], nthis, has_star, star_arg = 0, nstar;static R_StringBuffer outbuff = {NULL, 0, MAXELTSIZE};outputString = R_AllocStringBuffer(0, &outbuff);/* grab the format string */nargs = length(args);format = CAR(args);if (!isString(format) || length(format) == 0)error(_("'fmt' is not a non-empty character vector"));args = CDR(args); nargs--;if(nargs >= 100)error(_("only 100 arguments are allowed"));/* record the args for later re-ordering */for(i = 0; i < nargs; i++, args = CDR(args)) a[i] = CAR(args);maxlen = nfmt = length(format);for(i = 0; i < nargs; i++) {lens[i] = length(a[i]);if(lens[i] == 0)error(_("zero-length argument"));if(maxlen < lens[i]) maxlen = lens[i];}if(maxlen % length(format))error(_("arguments cannot be recycled to the same length"));for(i = 0; i < nargs; i++)if(maxlen % lens[i])error(_("arguments cannot be recycled to the same length"));/* FIXME? pull format analysis and conversion outside the loop ifthere is only one format */PROTECT(ans = allocVector(STRSXP, maxlen));for(ns = 0; ns < maxlen; ns++) {cnt = 0;outputString[0] = '\0';formatString = translateChar(STRING_ELT(format, ns % nfmt));n = strlen(formatString);if (n > MAXLINE)error(_("'fmt' length exceeds maximal format length %d"),MAXLINE);/* process the format string */for (cur = 0; cur < n; cur += chunk) {ss = NULL;if (formatString[cur] == '%') { /* handle special format command */if (cur < n - 1 && formatString[cur + 1] == '%') {/* take care of %% in the format */chunk = 2;strcpy(bit, "%");}else {/* recognise selected types from Table B-1 of K&R *//* This is MBCS-OK, as we are in a format spec */chunk = strcspn(formatString + cur + 1, "disfeEgGxX%") + 2;if (cur + chunk > n)error(_("unrecognised format at end of string"));strncpy(fmt, formatString + cur, chunk);fmt[chunk] = '\0';nthis = -1;/* now look for %n$ or %nn$ form */if (strlen(fmt) > 3 && fmt[1] >= '1' && fmt[1] <= '9') {v = fmt[1] - '0';if(fmt[2] == '$') {if(v > nargs)error(_("reference to non-existent argument %d"), v);nthis = v-1;memmove(fmt+1, fmt+3, strlen(fmt)-2);} else if(fmt[2] >= '1' && fmt[2] <= '9'&& fmt[3] == '$') {v = 10*v + fmt[2] - '0';if(v > nargs)error(_("reference to non-existent argument %d"), v);nthis = v-1;memmove(fmt+1, fmt+4, strlen(fmt)-3);}}has_star = 0;starc = Rf_strchr(fmt, '*');if (starc) { /* handle * format if present */nstar = -1;if (strlen(starc) > 3 && starc[1] >= '1' && starc[1] <= '9') {v = starc[1] - '0';if(starc[2] == '$') {if(v > nargs)error(_("reference to non-existent argument %d"), v);nstar = v-1;memmove(starc+1, starc+3, strlen(starc)-2);} else if(starc[2] >= '1' && starc[2] <= '9'&& starc[3] == '$') {v = 10*v + starc[2] - '0';if(v > nargs)error(_("reference to non-existent argument %d"), v);nstar = v-1;memmove(starc+1, starc+4, strlen(starc)-3);}}if(nstar < 0) {if (cnt >= nargs) error(_("too few arguments"));nstar = cnt++;}if (Rf_strchr(starc+1, '*'))error(_("at most one asterisk `*' is supported in each conversion specification"));_this = a[nstar];if(TYPEOF(_this) == REALSXP)_this = coerceVector(_this, INTSXP);if(TYPEOF(_this) != INTSXP || LENGTH(_this)<1 ||INTEGER(_this)[ns % LENGTH(_this)] == NA_INTEGER)error(_("argument for `*' conversion specification must be a number"));has_star = 1;star_arg = INTEGER(_this)[ns % LENGTH(_this)];}if (fmt[strlen(fmt) - 1] == '%') {/* handle % with formatting options */if (has_star)sprintf(bit, fmt, star_arg);elsesprintf(bit, fmt);} else {if(nthis < 0) {if (cnt >= nargs) error(_("too few arguments"));nthis = cnt++;}_this = a[nthis];if (has_star) {char *p, *q = fmt2;for (p = fmt; *p; p++)if (*p == '*') q += sprintf(q, "%d", star_arg);else *q++ = *p;*q = '\0';fmtp = fmt2;} else fmtp = fmt;/* Now let us see if some minimal coercion would be sensible.*/switch(fmtp[strlen(fmtp) - 1]) {case 'd':case 'i':case 'x':case 'X':if(TYPEOF(_this) == REALSXP) {double r = REAL(_this)[0];if((double)((int) r) == r)_this = coerceVector(_this, INTSXP);}break;case 'e':case 'f':case 'g':case 'E':case 'G':if(TYPEOF(_this) != REALSXP) {PROTECT(tmp = lang2(install("as.double"), _this));_this = eval(tmp, env);UNPROTECT(1);}break;case 's':if(TYPEOF(_this) != STRSXP) {PROTECT(tmp =lang2(install("as.character"), _this));_this = eval(tmp, env);UNPROTECT(1);}break;default:break;}PROTECT(_this);thislen = length(_this);if(thislen == 0)error(_("coercion has changed vector length to 0"));switch(TYPEOF(_this)) {case LGLSXP:{int x = LOGICAL(_this)[ns % thislen];if (strcspn(fmtp, "di") >= strlen(fmtp))error("%s",_("use format %d or %i for logical objects"));if (x == NA_LOGICAL) {fmtp[strlen(fmtp)-1] = 's';sprintf(bit, fmtp, "NA");} else {sprintf(bit, fmtp, x);}break;}case INTSXP:{int x = INTEGER(_this)[ns % thislen];if (strcspn(fmtp, "dixX") >= strlen(fmtp))error("%s",_("use format %d, %i, %x or %X for integer objects"));if (x == NA_INTEGER) {fmtp[strlen(fmtp)-1] = 's';sprintf(bit, fmtp, "NA");} else {sprintf(bit, fmtp, x);}break;}case REALSXP:{double x = REAL(_this)[ns % thislen];if (strcspn(fmtp, "feEgG") >= strlen(fmtp))error("%s",_("use format %f, %e or %g for numeric objects"));if (R_FINITE(x)) {sprintf(bit, fmtp, x);} else {char *p = Rf_strchr(fmtp, '.');if (p) {*p++ = 's'; *p ='\0';} elsefmtp[strlen(fmtp)-1] = 's';if (ISNA(x)) {if (strcspn(fmtp, " ") < strlen(fmtp))sprintf(bit, fmtp, " NA");elsesprintf(bit, fmtp, "NA");} else if (ISNAN(x)) {if (strcspn(fmtp, " ") < strlen(fmtp))sprintf(bit, fmtp, " NaN");elsesprintf(bit, fmtp, "NaN");} else if (x == R_PosInf) {if (strcspn(fmtp, "+") < strlen(fmtp))sprintf(bit, fmtp, "+Inf");else if (strcspn(fmtp, " ") < strlen(fmtp))sprintf(bit, fmtp, " Inf");elsesprintf(bit, fmtp, "Inf");} else if (x == R_NegInf)sprintf(bit, fmtp, "-Inf");}break;}case STRSXP:/* NA_STRING will be printed as `NA' */if (strcspn(fmtp, "s") >= strlen(fmtp))error("%s", _("use format %s for character objects"));ss = translateChar(STRING_ELT(_this, ns % thislen));if(fmtp[1] != 's') {if(strlen(ss) > MAXLINE)warning(_("Likely truncation of character string"));snprintf(bit, MAXLINE, fmtp, ss);bit[MAXLINE] = '\0';ss = NULL;}break;default:error(_("unsupported type"));break;}UNPROTECT(1);}}}else { /* not '%' : handle string part */char *ch = Rf_strchr(formatString + cur, '%'); /* MBCS-awareversion used */if(ch) chunk = ch - formatString - cur;else chunk = strlen(formatString + cur);strncpy(bit, formatString + cur, chunk);bit[chunk] = '\0';}if(ss) {outputString = R_AllocStringBuffer(strlen(outputString) +strlen(ss) + 1, &outbuff);strcat(outputString, ss);} else {outputString = R_AllocStringBuffer(strlen(outputString) +strlen(bit) + 1, &outbuff);strcat(outputString, bit);}}SET_STRING_ELT(ans, ns, mkChar(outputString));}UNPROTECT(1);R_FreeStringBufferL(&outbuff);return ans;}/* Local Variables: *//* indent-tabs-mode: t *//* c-basic-offset: 4 *//* End: */