The R Project SVN R

Rev

Rev 90254 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
19139 maechler 1
/*
2
 *  R : A Computer Language for Statistical Data Analysis
90288 ripley 3
 *  Copyright (C) 2002--2026     The R Core Team
19139 maechler 4
 *
5
 *  This program is free software; you can redistribute it and/or modify
6
 *  it under the terms of the GNU General Public License as published by
7
 *  the Free Software Foundation; either version 2 of the License, or
8
 *  (at your option) any later version.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU General Public License
42307 ripley 16
 *  along with this program; if not, a copy is available at
68947 ripley 17
 *  https://www.R-project.org/Licenses/
19139 maechler 18
 *
46947 ripley 19
 * Originally written by Jonathan Rougier
19139 maechler 20
*/
32380 ripley 21
 
19133 maechler 22
#ifdef HAVE_CONFIG_H
23
#include <config.h>
24
#endif
25
 
26
#include <Defn.h>
60667 ripley 27
#include <Internal.h>
41978 ripley 28
#include "RBufferUtils.h"
81479 ripley 29
#include <R_ext/RS.h> /* for R_Calloc/R_Free */
67837 murdoch 30
#ifdef Win32
31
#include <trioremap.h>
32
#endif
19133 maechler 33
 
34
#define MAXLINE MAXELTSIZE
48376 maechler 35
#define MAXNARGS 100
36
/*               ^^^ not entirely arbitrary, but strongly linked to allowing %$1 to %$99 !*/
19133 maechler 37
 
48376 maechler 38
/*
46947 ripley 39
   This is passed a format that started with % and may include other
40
   chars, e.g. '.2f abc'.  It's aim is to show that this is a valid
48376 maechler 41
   format from one of the types given in pattern.
46947 ripley 42
*/
43
 
44
static const char *findspec(const char *str)
45
{
46949 ripley 46
    /* This is not strict about checking where '.' is allowed.
48376 maechler 47
       It should allow  - + ' ' # 0 as flags
46949 ripley 48
       m m. .n n.m as width/precision
49
    */
46947 ripley 50
    const char *p = str;
51
 
52
    if(*p != '%') return p;
53
    for(p++; ; p++) {
48295 maechler 54
	if(*p == '-' || *p == '+' || *p == ' ' || *p == '#' || *p == '.' ) continue;
46949 ripley 55
	/* '*' will currently have got substituted before this */
56
	if(*p == '*' || (*p >= '0' && *p <= '9')) continue;
46947 ripley 57
	break;
58
    }
59
    return p;
60
}
61
 
62
 
87891 ripley 63
/*   false is success, true is an error: pattern *not* found . */
64
static bool checkfmt(const char *fmt, const char *pattern)
46947 ripley 65
{
66
    const char *p =fmt;
67
 
87891 ripley 68
    if(*p != '%') return true;
46947 ripley 69
    p = findspec(fmt);
87891 ripley 70
    return strcspn(p, pattern) ? true : false;
46947 ripley 71
}
72
 
59226 ripley 73
#define TRANSLATE_CHAR(_STR_, _i_)  \
74
   ((use_UTF8) ? translateCharUTF8(STRING_ELT(_STR_, _i_))  \
75
    : translateChar(STRING_ELT(_STR_, _i_)))
46947 ripley 76
 
59226 ripley 77
 
83446 ripley 78
attribute_hidden SEXP do_sprintf(SEXP call, SEXP op, SEXP args, SEXP env)
19133 maechler 79
{
48378 maechler 80
    int i, nargs, cnt, v, thislen, nfmt, nprotect = 0;
41978 ripley 81
    /* fmt2 is a copy of fmt with '*' expanded.
42001 ripley 82
       bit will hold numeric formats and %<w>s, so be quite small. */
45446 ripley 83
    char fmt[MAXLINE+1], fmt2[MAXLINE+10], *fmtp, bit[MAXLINE+1],
59226 ripley 84
	*outputString;
85
    const char *formatString;
19133 maechler 86
    size_t n, cur, chunk;
87
 
48415 maechler 88
    SEXP format, _this, a[MAXNARGS], ans /* -Wall */ = R_NilValue;
79355 kalibera 89
    int ns, maxlen, lens[MAXNARGS], nthis, nstar, star_arg = 0, nunused;
87891 ripley 90
    bool used[MAXNARGS];
41978 ripley 91
    static R_StringBuffer outbuff = {NULL, 0, MAXELTSIZE};
87891 ripley 92
    bool has_star, use_UTF8;
19133 maechler 93
 
48399 maechler 94
#define _my_sprintf(_X_)						\
95
    {									\
96
	int nc = snprintf(bit, MAXLINE+1, fmtp, _X_);			\
97
	if (nc > MAXLINE)						\
60844 ripley 98
	    error(_("required resulting string length %d is greater than maximal %d"), \
48399 maechler 99
		  nc, MAXLINE);						\
100
    }
101
 
48376 maechler 102
    nargs = length(args);
19133 maechler 103
    /* grab the format string */
104
    format = CAR(args);
48156 ripley 105
    if (!isString(format))
106
	error(_("'fmt' is not a character vector"));
48378 maechler 107
    nfmt = length(format);
108
    if (nfmt == 0) return allocVector(STRSXP, 0);
33117 ripley 109
    args = CDR(args); nargs--;
48376 maechler 110
    if(nargs >= MAXNARGS)
111
	error(_("only %d arguments are allowed"), MAXNARGS);
19133 maechler 112
 
44700 ripley 113
    /* record the args for possible coercion and later re-ordering */
48378 maechler 114
    for(i = 0; i < nargs; i++, args = CDR(args)) {
48415 maechler 115
	SEXPTYPE t_ai;
48378 maechler 116
	a[i] = CAR(args);
87891 ripley 117
	used[i] = false;
48415 maechler 118
	if((t_ai = TYPEOF(a[i])) == LANGSXP || t_ai == SYMSXP) /* << maybe add more .. */
119
	    error(_("invalid type of argument[%d]: '%s'"),
120
		  i+1, CHAR(type2str(t_ai)));
33432 ripley 121
	lens[i] = length(a[i]);
48156 ripley 122
	if(lens[i] == 0) return allocVector(STRSXP, 0);
33194 ripley 123
    }
48156 ripley 124
 
48378 maechler 125
#define CHECK_maxlen							\
126
    maxlen = nfmt;							\
127
    for(i = 0; i < nargs; i++)						\
128
	if(maxlen < lens[i]) maxlen = lens[i];				\
129
    if(maxlen % nfmt)							\
130
	error(_("arguments cannot be recycled to the same length"));	\
131
    for(i = 0; i < nargs; i++)						\
132
	if(maxlen % lens[i])						\
133
	    error(_("arguments cannot be recycled to the same length"))
134
 
135
    CHECK_maxlen;
136
 
48156 ripley 137
    outputString = R_AllocStringBuffer(0, &outbuff);
138
 
44711 ripley 139
    /* We do the format analysis a row at a time */
33194 ripley 140
    for(ns = 0; ns < maxlen; ns++) {
141
	outputString[0] = '\0';
45847 ripley 142
	use_UTF8 = getCharCE(STRING_ELT(format, ns % nfmt)) == CE_UTF8;
143
	if (!use_UTF8) {
144
	    for(i = 0; i < nargs; i++) {
145
		if (!isString(a[i])) continue;
146
		if (getCharCE(STRING_ELT(a[i], ns % lens[i])) == CE_UTF8) {
87891 ripley 147
		    use_UTF8 = true; break;
45847 ripley 148
		}
149
	    }
150
	}
48376 maechler 151
 
152
	formatString = TRANSLATE_CHAR(format, ns % nfmt);
33194 ripley 153
	n = strlen(formatString);
154
	if (n > MAXLINE)
44700 ripley 155
	    error(_("'fmt' length exceeds maximal format length %d"), MAXLINE);
45446 ripley 156
	/* process the format string */
44700 ripley 157
	for (cur = 0, cnt = 0; cur < n; cur += chunk) {
59226 ripley 158
	    const char *curFormat = formatString + cur, *ss;
159
	    char *starc;
41978 ripley 160
	    ss = NULL;
33194 ripley 161
	    if (formatString[cur] == '%') { /* handle special format command */
19133 maechler 162
 
33194 ripley 163
		if (cur < n - 1 && formatString[cur + 1] == '%') {
164
		    /* take care of %% in the format */
165
		    chunk = 2;
166
		    strcpy(bit, "%");
167
		}
168
		else {
169
		    /* recognise selected types from Table B-1 of K&R */
48376 maechler 170
		    /* NB: we deal with "%%" in branch above. */
33203 ripley 171
		    /* This is MBCS-OK, as we are in a format spec */
49773 ripley 172
		    chunk = strcspn(curFormat + 1, "diosfeEgGxXaA") + 2;
33194 ripley 173
		    if (cur + chunk > n)
48376 maechler 174
			error(_("unrecognised format specification '%s'"), curFormat);
33194 ripley 175
 
48376 maechler 176
		    strncpy(fmt, curFormat, chunk);
33194 ripley 177
		    fmt[chunk] = '\0';
178
 
179
		    nthis = -1;
180
		    /* now look for %n$ or %nn$ form */
181
		    if (strlen(fmt) > 3 && fmt[1] >= '1' && fmt[1] <= '9') {
182
			v = fmt[1] - '0';
183
			if(fmt[2] == '$') {
184
			    if(v > nargs)
41686 ripley 185
				error(_("reference to non-existent argument %d"), v);
33194 ripley 186
			    nthis = v-1;
187
			    memmove(fmt+1, fmt+3, strlen(fmt)-2);
59722 murdoch 188
			} else if(fmt[2] >= '0' && fmt[2] <= '9' && fmt[3] == '$') {
33194 ripley 189
			    v = 10*v + fmt[2] - '0';
190
			    if(v > nargs)
41686 ripley 191
				error(_("reference to non-existent argument %d"), v);
33194 ripley 192
			    nthis = v-1;
193
			    memmove(fmt+1, fmt+4, strlen(fmt)-3);
194
			}
33117 ripley 195
		    }
35625 urbaneks 196
 
42045 ripley 197
		    starc = Rf_strchr(fmt, '*');
48376 maechler 198
		    if (starc) { /* handle  *  format if present */
36948 ripley 199
			nstar = -1;
35625 urbaneks 200
			if (strlen(starc) > 3 && starc[1] >= '1' && starc[1] <= '9') {
201
			    v = starc[1] - '0';
202
			    if(starc[2] == '$') {
203
				if(v > nargs)
41686 ripley 204
				    error(_("reference to non-existent argument %d"), v);
35625 urbaneks 205
				nstar = v-1;
206
				memmove(starc+1, starc+3, strlen(starc)-2);
59722 murdoch 207
			    } else if(starc[2] >= '0' && starc[2] <= '9'
35625 urbaneks 208
				      && starc[3] == '$') {
209
				v = 10*v + starc[2] - '0';
210
				if(v > nargs)
41686 ripley 211
				    error(_("reference to non-existent argument %d"), v);
35625 urbaneks 212
				nstar = v-1;
213
				memmove(starc+1, starc+4, strlen(starc)-3);
214
			    }
215
			}
216
 
217
			if(nstar < 0) {
41686 ripley 218
			    if (cnt >= nargs) error(_("too few arguments"));
35625 urbaneks 219
			    nstar = cnt++;
220
			}
221
 
42045 ripley 222
			if (Rf_strchr(starc+1, '*'))
44711 ripley 223
			    error(_("at most one asterisk '*' is supported in each conversion specification"));
35625 urbaneks 224
 
39866 duncan 225
			_this = a[nstar];
87891 ripley 226
			used[nstar] = true;
44711 ripley 227
			if(ns == 0 && TYPEOF(_this) == REALSXP) {
39866 duncan 228
			    _this = coerceVector(_this, INTSXP);
44711 ripley 229
			    PROTECT(a[nstar] = _this);
230
			    nprotect++;
231
			}
39866 duncan 232
			if(TYPEOF(_this) != INTSXP || LENGTH(_this)<1 ||
233
			   INTEGER(_this)[ns % LENGTH(_this)] == NA_INTEGER)
44711 ripley 234
			    error(_("argument for '*' conversion specification must be a number"));
39866 duncan 235
			star_arg = INTEGER(_this)[ns % LENGTH(_this)];
87891 ripley 236
			has_star = true;
33194 ripley 237
		    }
48376 maechler 238
		    else
87891 ripley 239
			has_star = false;
19133 maechler 240
 
35625 urbaneks 241
		    if (fmt[strlen(fmt) - 1] == '%') {
242
			/* handle % with formatting options */
243
			if (has_star)
62580 ripley 244
			    snprintf(bit, MAXLINE+1, fmt, star_arg);
35625 urbaneks 245
			else
48399 maechler 246
			    strcpy(bit, fmt);
247
			/* was sprintf(..)  for which some compiler warn */
35625 urbaneks 248
		    } else {
87891 ripley 249
			bool did_this = false;
35625 urbaneks 250
			if(nthis < 0) {
41686 ripley 251
			    if (cnt >= nargs) error(_("too few arguments"));
35625 urbaneks 252
			    nthis = cnt++;
33194 ripley 253
			}
39866 duncan 254
			_this = a[nthis];
87891 ripley 255
			used[nthis] = true;
36948 ripley 256
			if (has_star) {
55743 luke 257
			    size_t nf; char *p, *q = fmt2;
36948 ripley 258
			    for (p = fmt; *p; p++)
83637 ripley 259
				if (*p == '*')
260
				    q += snprintf(q, sizeof(fmt2) - (q - fmt2),
261
						  "%d", star_arg);
36948 ripley 262
				else *q++ = *p;
263
			    *q = '\0';
48399 maechler 264
			    nf = strlen(fmt2);
265
			    if (nf > MAXLINE)
59181 murdoch 266
				error(_("'fmt' length exceeds maximal format length %d"),
48399 maechler 267
				      MAXLINE);
36948 ripley 268
			    fmtp = fmt2;
269
			} else fmtp = fmt;
45446 ripley 270
 
48415 maechler 271
#define CHECK_this_length						\
70440 maechler 272
			do {						\
273
			    PROTECT(_this);				\
274
			    thislen = length(_this);			\
275
			    if(thislen == 0)				\
276
				error(_("coercion has changed vector length to 0")); \
277
			} while (0)
48415 maechler 278
 
48376 maechler 279
			/* Now let us see if some minimal coercion
280
			   would be sensible, but only do so once, for ns = 0: */
44711 ripley 281
			if(ns == 0) {
87891 ripley 282
			    SEXP tmp;
283
			    bool do_check;
46947 ripley 284
			    switch(*findspec(fmtp)) {
44700 ripley 285
			    case 'd':
286
			    case 'i':
49773 ripley 287
			    case 'o':
44700 ripley 288
			    case 'x':
289
			    case 'X':
290
				if(TYPEOF(_this) == REALSXP) {
69115 ripley 291
				    // qdapTools manages to call this with NaN
87891 ripley 292
				    bool exactlyInteger = true;
72737 kalibera 293
				    R_xlen_t i = 0;
294
				    R_xlen_t n = XLENGTH(_this);
295
				    for(i = 0; i < n; i++) {
296
					double r = REAL(_this)[i];
297
					if (R_IsNA(r)) continue; // NA_REAL is ok
298
					if (!R_FINITE(r) || (double)((int) r) != r) {
87891 ripley 299
					    exactlyInteger = false;
72737 kalibera 300
					    break;
301
					}
302
				    } 
303
				    if(exactlyInteger)
44700 ripley 304
					_this = coerceVector(_this, INTSXP);
44711 ripley 305
				    PROTECT(a[nthis] = _this);
306
				    nprotect++;
44700 ripley 307
				}
308
				break;
46517 ripley 309
			    case 'a':
310
			    case 'A':
44700 ripley 311
			    case 'e':
312
			    case 'f':
313
			    case 'g':
314
			    case 'E':
315
			    case 'G':
48415 maechler 316
				if(TYPEOF(_this) != REALSXP &&
317
				   /* no automatic as.double(<string>) : */
318
				   TYPEOF(_this) != STRSXP) {
44700 ripley 319
				    PROTECT(tmp = lang2(install("as.double"), _this));
48378 maechler 320
#define COERCE_THIS_TO_A						\
321
				    _this = eval(tmp, env);		\
322
				    UNPROTECT(1);			\
323
				    PROTECT(a[nthis] = _this);		\
324
				    nprotect++;				\
87891 ripley 325
				    did_this = true;			\
48415 maechler 326
				    CHECK_this_length;			\
48378 maechler 327
				    do_check = (lens[nthis] == maxlen);	\
48415 maechler 328
				    lens[nthis] = thislen; /* may have changed! */ \
329
				    if(do_check && thislen < maxlen) {	\
48378 maechler 330
					CHECK_maxlen;			\
331
				    }
332
 
333
				    COERCE_THIS_TO_A
44700 ripley 334
				}
335
				break;
336
			    case 's':
337
				if(TYPEOF(_this) != STRSXP) {
50894 ripley 338
				    /* as.character method might call sprintf() */
55743 luke 339
				    size_t nc = strlen(outputString);
81479 ripley 340
				    char *z = R_Calloc(nc+1, char);
50894 ripley 341
				    strcpy(z, outputString);
71844 luke 342
				    PROTECT(tmp = lang2(R_AsCharacterSymbol, _this));
48378 maechler 343
 
344
				    COERCE_THIS_TO_A
84625 kalibera 345
				    outputString = R_AllocStringBuffer(nc + 1,
346
				                                       &outbuff);
50894 ripley 347
				    strcpy(outputString, z);
81479 ripley 348
				    R_Free(z);
44700 ripley 349
				}
350
				break;
351
			    default:
352
				break;
35625 urbaneks 353
			    }
48415 maechler 354
			} /* ns == 0 (first-time only) */
45446 ripley 355
 
48415 maechler 356
			if(!did_this)
357
			    CHECK_this_length;
45446 ripley 358
 
39866 duncan 359
			switch(TYPEOF(_this)) {
35625 urbaneks 360
			case LGLSXP:
361
			    {
39866 duncan 362
				int x = LOGICAL(_this)[ns % thislen];
46947 ripley 363
				if (checkfmt(fmtp, "di"))
48399 maechler 364
				    error(_("invalid format '%s'; %s"), fmtp,
35625 urbaneks 365
					  _("use format %d or %i for logical objects"));
366
				if (x == NA_LOGICAL) {
36948 ripley 367
				    fmtp[strlen(fmtp)-1] = 's';
48399 maechler 368
				    _my_sprintf("NA")
35625 urbaneks 369
				} else {
48399 maechler 370
				    _my_sprintf(x)
35625 urbaneks 371
				}
372
				break;
373
			    }
374
			case INTSXP:
375
			    {
39866 duncan 376
				int x = INTEGER(_this)[ns % thislen];
49773 ripley 377
				if (checkfmt(fmtp, "dioxX"))
48399 maechler 378
				    error(_("invalid format '%s'; %s"), fmtp,
49773 ripley 379
					  _("use format %d, %i, %o, %x or %X for integer objects"));
35625 urbaneks 380
				if (x == NA_INTEGER) {
36948 ripley 381
				    fmtp[strlen(fmtp)-1] = 's';
48399 maechler 382
				    _my_sprintf("NA")
35625 urbaneks 383
				} else {
48399 maechler 384
				    _my_sprintf(x)
35625 urbaneks 385
				}
386
				break;
387
			    }
388
			case REALSXP:
389
			    {
39866 duncan 390
				double x = REAL(_this)[ns % thislen];
46947 ripley 391
				if (checkfmt(fmtp, "aAfeEgG"))
48399 maechler 392
				    error(_("invalid format '%s'; %s"), fmtp,
46517 ripley 393
					  _("use format %f, %e, %g or %a for numeric objects"));
35625 urbaneks 394
				if (R_FINITE(x)) {
48399 maechler 395
				    _my_sprintf(x)
35625 urbaneks 396
				} else {
42045 ripley 397
				    char *p = Rf_strchr(fmtp, '.');
35625 urbaneks 398
				    if (p) {
399
					*p++ = 's'; *p ='\0';
400
				    } else
36948 ripley 401
					fmtp[strlen(fmtp)-1] = 's';
35625 urbaneks 402
				    if (ISNA(x)) {
36948 ripley 403
					if (strcspn(fmtp, " ") < strlen(fmtp))
48399 maechler 404
					    _my_sprintf(" NA")
36948 ripley 405
					else
48399 maechler 406
					    _my_sprintf("NA")
35625 urbaneks 407
				    } else if (ISNAN(x)) {
36948 ripley 408
					if (strcspn(fmtp, " ") < strlen(fmtp))
48399 maechler 409
					    _my_sprintf(" NaN")
36948 ripley 410
					else
48399 maechler 411
					    _my_sprintf("NaN")
35625 urbaneks 412
				    } else if (x == R_PosInf) {
36948 ripley 413
					if (strcspn(fmtp, "+") < strlen(fmtp))
48399 maechler 414
					    _my_sprintf("+Inf")
36948 ripley 415
					else if (strcspn(fmtp, " ") < strlen(fmtp))
48399 maechler 416
					    _my_sprintf(" Inf")
35625 urbaneks 417
					else
48399 maechler 418
					    _my_sprintf("Inf")
36948 ripley 419
				    } else if (x == R_NegInf)
48399 maechler 420
					_my_sprintf("-Inf")
35625 urbaneks 421
				}
422
				break;
423
			    }
424
			case STRSXP:
44711 ripley 425
			    /* NA_STRING will be printed as 'NA' */
46947 ripley 426
			    if (checkfmt(fmtp, "s"))
48399 maechler 427
				error(_("invalid format '%s'; %s"), fmtp,
428
				      _("use format %s for character objects"));
48376 maechler 429
 
430
			    ss = TRANSLATE_CHAR(_this, ns % thislen);
42001 ripley 431
			    if(fmtp[1] != 's') {
432
				if(strlen(ss) > MAXLINE)
44711 ripley 433
				    warning(_("likely truncation of character string to %d characters"),
434
					    MAXLINE-1);
48399 maechler 435
				_my_sprintf(ss)
42001 ripley 436
				bit[MAXLINE] = '\0';
437
				ss = NULL;
438
			    }
35625 urbaneks 439
			    break;
45446 ripley 440
 
35625 urbaneks 441
			default:
41686 ripley 442
			    error(_("unsupported type"));
35625 urbaneks 443
			    break;
33436 ripley 444
			}
33143 ripley 445
 
35625 urbaneks 446
			UNPROTECT(1);
33194 ripley 447
		    }
19717 ripley 448
		}
19133 maechler 449
	    }
33194 ripley 450
	    else { /* not '%' : handle string part */
90288 ripley 451
		const char *ch = use_UTF8 ? strchr(curFormat, '%')
76243 kalibera 452
				    /* MBCS-aware version used */
90288 ripley 453
		                    : Rf_strchr_const(curFormat, '%');
55743 luke 454
		chunk = (ch) ? (size_t) (ch - curFormat) : strlen(curFormat);
48376 maechler 455
		strncpy(bit, curFormat, chunk);
33194 ripley 456
		bit[chunk] = '\0';
457
	    }
41978 ripley 458
	    if(ss) {
45446 ripley 459
		outputString = R_AllocStringBuffer(strlen(outputString) +
41978 ripley 460
						   strlen(ss) + 1, &outbuff);
461
		strcat(outputString, ss);
462
	    } else {
45446 ripley 463
		outputString = R_AllocStringBuffer(strlen(outputString) +
41978 ripley 464
						   strlen(bit) + 1, &outbuff);
465
		strcat(outputString, bit);
466
	    }
48376 maechler 467
	}  /* end for ( each chunk ) */
468
 
48378 maechler 469
	if(ns == 0) { /* may have adjusted maxlen now ... */
470
	    PROTECT(ans = allocVector(STRSXP, maxlen));
471
	    nprotect++;
472
	}
48376 maechler 473
	SET_STRING_ELT(ans, ns, mkCharCE(outputString,
45847 ripley 474
					 use_UTF8 ? CE_UTF8 : CE_NATIVE));
48376 maechler 475
    } /* end for(ns ...) */
19133 maechler 476
 
79355 kalibera 477
    nunused = 0;
478
    for(i = 0; i < nargs; i++)
479
	if (!used[i]) nunused++;
79376 kalibera 480
    if (nunused > 0) {
481
	if (nfmt == 1) {
482
	    const char *f = translateChar(STRING_ELT(format, 0));
483
	    if (nunused == 1)
484
		warning(_("one argument not used by format '%s'"), f);
485
	    else if (nunused > 1)
486
		warning(_("%d arguments not used by format '%s'"), nunused, f);
487
	} else {
488
	    if (nunused == 1)
489
		warning(_("one argument not used by format"));
490
	    else if (nunused > 1)
491
		warning(_("%d arguments not used by format"), nunused);
492
	}
493
    }
79355 kalibera 494
 
44700 ripley 495
    UNPROTECT(nprotect);
41978 ripley 496
    R_FreeStringBufferL(&outbuff);
19133 maechler 497
    return ans;
498
}