blob: acd0c35db7ddcc160954b5007a8ae1ac1a6e1eb1 [file] [log] [blame]
/************************************************************************
*
* printf.cpp - definitions of the snprintfa family of functions
*
* $Id$
*
************************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* Copyright 2005-2008 Rogue Wave Software, Inc.
*
**************************************************************************/
// expand _TEST_EXPORT macros
#define _RWSTD_TEST_SRC
#include "fmt_defs.h"
#include <rw_printf.h>
#include <rw_environ.h>
#include <ctype.h> // for isalpha(), ...
#include <errno.h> // for errno, errno constants
#include <float.h> // for floating point macros
#include <locale.h>
#include <signal.h> // for signal constant
#include <stdarg.h> // for va_list, va_start, ...
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h> // for struct tm
#ifndef _RWSTD_NO_WCHAR_H
# include <wchar.h>
#endif // _RWSTD_NO_WCHAR_H
#ifndef _RWSTD_NO_WCTYPE_H
# include <wctype.h> // for iswalpha(), ...
#endif // _RWSTD_NO_WCTYPE_H
#if defined (_WIN32)
# include <windows.h> // for GetLastError(), OutputDebugString()
#else
# include <dlfcn.h>
#endif // _WIN32
#include <ios>
#include <iostream>
#include <locale>
#include <string>
#ifndef WEOF
# define WEOF -1
#endif // WEOF
_RWSTD_NAMESPACE (__rw) {
_RWSTD_EXPORT _RWSTD_SSIZE_T
__rw_memattr (const void*, size_t, int);
}
/********************************************************************/
static const union {
int ival;
UChar bytes [sizeof (int)];
} _rw_one = { 1 };
static const int
_rw_big_endian = '\0' == _rw_one.bytes [0];
/********************************************************************/
_RWSTD_INTERNAL int
_rw_fmtlong (const FmtSpec&, Buffer&, long);
#ifdef _RWSTD_LONG_LONG
_RWSTD_INTERNAL int
_rw_fmtllong (const FmtSpec&, Buffer&, _RWSTD_LONG_LONG);
#endif // _RWSTD_LONG_LONG
struct VarArgs {
union {
va_list *pva_;
void *pargs_;
} arg_;
enum {
VA_list, VA_array
} kind_;
};
#define VA_ARG(va, T) \
(VarArgs::VA_list == (va).kind_ ? va_arg (*(va).arg_.pva_, T) \
: *((T*&)(va).arg_.pargs_)++)
static int
_rw_fmtinteger (FmtSpec*, size_t, Buffer&, VarArgs*);
static int
_rw_fmtfloating (const FmtSpec&, Buffer&, const void*);
_RWSTD_INTERNAL int
_rw_fmtptr (const FmtSpec&, Buffer&, const void*);
// format an invalid address
_RWSTD_INTERNAL int
_rw_fmtbadaddr (const FmtSpec&, Buffer&, const void*, size_t = 0);
static int
_rw_fmtfunptr (const FmtSpec&, Buffer&, funptr_t);
static int
_rw_fmtmemptr (const FmtSpec&, Buffer&, memptr_t);
static int
_rw_fmtchr (const FmtSpec&, Buffer&, int, int);
static int
_rw_fmtwchr (const FmtSpec&, Buffer&, wint_t, int);
static int
_rw_fmtwstr (const FmtSpec&, Buffer&, const wchar_t*, size_t);
// format an argv-style (null-terminated) array of character strings
static int
_rw_fmtstrarray (FmtSpec*, size_t, Buffer&, const char* const*);
// format struct tm
static int
_rw_fmttm (const FmtSpec&, Buffer&, const tm*);
// format an expression (parameter/environment variable)
static int
_rw_fmtexpr (FmtSpec&, Buffer&, VarArgs*);
// format a nested parametrized directive (%{@}, %{*.*@}, etc.)
static int
_rw_fmtnested (const FmtSpec&, Buffer&, const char*, VarArgs*);
// format an extension
static int
_rw_vasnprintf_ext (FmtSpec*, size_t, Buffer&, const char*, VarArgs*);
/********************************************************************/
typedef int _rw_usr_cb_t (char**, size_t*, const char*, ...);
static _rw_usr_cb_t* _rw_usr_fun [32];
static size_t _rw_usr_inx;
/********************************************************************/
/*
C99 format specifier:
% flags width [ . prec ] mod cvtspec
GNU glibc format specifier:
% [ paramno $] flags width [ . prec ] mod cvtspec
or
% [ paramno $] flags width . * [ paramno $] mod cvtspec
Extended format specifier:
% { [ paramno $] [ flags ] [ width [. prec ]] mod cvtspec }
or
% { [ paramno $] [ flags ] [@ base [. width [. prec ]]] mod cvtspec }
or
% { $ envvar }
*/
static int
_rw_fmtspec (FmtSpec *pspec, bool ext, const char *fmt, VarArgs *pva)
{
RW_ASSERT (0 != pspec);
RW_ASSERT (0 != fmt);
RW_ASSERT (0 != pva);
memset (pspec, 0, sizeof *pspec);
pspec->iwidth = -1; // integer width not specified
pspec->width = -1; // width not specified
pspec->prec = -1; // precision not specified
pspec->base = -1; // base not specified
pspec->paramno = -1; // paramno not specified
const char* const fmtbeg = fmt;
if (ext) {
if ('$' == *fmt && '}' != fmt [1]) {
// %{$<string>}: introduces the name of an environment
// variable (or parameter)
++fmt;
const char *str = 0;
size_t len = 0;
const char* const end = strchr (fmt, '}');
if (end) {
len = end - fmt;
str = fmt;
fmt = end;
}
if (str) {
char* const tmp = (char*)malloc (len + 1);
if (tmp) {
memcpy (tmp, str, len);
pspec->strarg = tmp;
pspec->strarg [len] = '\0';
return int (fmt - fmtbeg);
}
return -1;
}
}
}
// extension: extract the parameter number (if followed by '$')
if ('1' <= *fmt && *fmt <= '9') {
char *end;
const long tmp = strtol (fmt, &end, 10);
fmt = end;
if ('$' == *fmt) {
pspec->paramno = tmp;
++fmt;
}
else {
// when not followed by '$' interpret the number
// as the width (i.e., that follows the empty set
// of flags)
pspec->width = int (tmp);
}
}
if (-1 == pspec->width) {
// extract any number of optional flags
for ( ; ; ++fmt) {
switch (*fmt) {
case '-': pspec->fl_minus = true; continue;
case '+': pspec->fl_plus = true; continue;
case '#': pspec->fl_pound = true; continue;
case ' ': pspec->fl_space = true; continue;
case '0': pspec->fl_zero = true; continue;
}
break;
}
}
if (ext && '@' == *fmt && '}' != fmt [1]) {
// @<decimal-number>
++fmt;
// extract the numerical base
if ('0' <= fmt [1] && fmt [1] <= '9') {
char *end;
pspec->base = int (strtol (fmt, &end, 10));
fmt = end;
}
else if ('*' == *fmt) {
pspec->base = VA_ARG (*pva, int);
++fmt;
}
if ('.' == *fmt)
++fmt;
}
if (-1 == pspec->width) {
// extract the optional width
if ('0' <= *fmt && *fmt <= '9') {
char *end;
pspec->width = int (strtol (fmt, &end, 10));
fmt = end;
}
else if ('*' == *fmt) {
pspec->width = VA_ARG (*pva, int);
if (pspec->width < 0) {
// 7.19.6.1, p5 of ISO/IEC 9899:1999:
// A negative field width argument is taken
// as a - flag followed by a positive field width.
pspec->width = -pspec->width;
pspec->fl_minus = true;
}
++fmt;
}
}
// extract the optional precision
if ('.' == *fmt) {
++fmt;
if ('0' <= *fmt && *fmt <= '9') {
char *end;
pspec->prec = int (strtol (fmt, &end, 10));
fmt = end;
}
else if ('*' == *fmt) {
pspec->prec = VA_ARG (*pva, int);
++fmt;
}
}
// extract an optional modifier but avoid misinterpreting
// a formatting specifier as a modifier when extensions
// are enabled; e.g., treat the 't' in %{t} as a specifier
// and not a modifier for a non-existent specifier
const char mod = !ext || '}' != fmt [1] ? *fmt : '\0';
switch (mod) {
case 'A':
if (ext) {
++fmt;
pspec->mod = pspec->mod_ext_A;
break;
}
// fall thru
case 'h':
if ('h' == fmt [1] && '}' != fmt [2]) {
pspec->mod = pspec->mod_hh;
fmt += 2;
}
else if ('}' != fmt [1]) {
pspec->mod = pspec->mod_h;
++fmt;
}
break;
case 'l':
if ('l' == fmt [1] && '}' != fmt [2]) {
pspec->mod = pspec->mod_ll;
fmt += 2;
}
else if ('}' != fmt [1]) {
pspec->mod = pspec->mod_l;
++fmt;
}
break;
case 'j':
pspec->mod = pspec->mod_j;
++fmt;
break;
case 'z':
pspec->mod = pspec->mod_z;
++fmt;
break;
case 't':
pspec->mod = pspec->mod_t;
++fmt;
break;
case 'L':
pspec->mod = pspec->mod_L;
++fmt;
break;
case 'I':
if (ext) {
++fmt;
if ('8' == *fmt) {
pspec->iwidth = 1;
++fmt;
}
else if ('1' == fmt [0] && '6' == fmt [1]) {
pspec->iwidth = 2;
fmt += 2;
}
else if ('3' == fmt [0] && '2' == fmt [1]) {
pspec->iwidth = 3;
fmt += 2;
}
else if ('6' == fmt [0] && '4' == fmt [1]) {
pspec->iwidth = 4;
fmt += 2;
}
else {
pspec->mod = pspec->mod_ext_I;
}
break;
}
}
pspec->cvtspec = *fmt;
if (pspec->cvtspec)
++fmt;
return int (fmt - fmtbeg);
}
/********************************************************************/
_RWSTD_INTERNAL char*
_rw_bufcat (Buffer &buf, const char *str, size_t len)
{
RW_ASSERT (0 != buf.pbuf);
RW_ASSERT (0 != buf.pbufsize);
size_t buflen = buf.endoff;
const size_t bufree = *buf.pbuf ? *buf.pbufsize - buflen : 0;
if (bufree < len || !*buf.pbuf) {
// for guard block
static const char guard[] = "\xde\xad\xbe\xef";
const size_t guardsize = sizeof guard - 1;
size_t newbufsize = *buf.pbufsize * 2;
if (newbufsize < 16)
newbufsize = 16;
const size_t requiredsize = buflen + len + 1;
if (newbufsize < requiredsize)
newbufsize = requiredsize * 2;
// prevent buffer size from exceeding the maximum
if (buf.maxsize < newbufsize)
newbufsize = buf.maxsize;
if (newbufsize < requiredsize) {
#ifdef ENOMEM
errno = ENOMEM;
#endif // ENOMEM
return 0;
}
char* const newbuf = (char*)malloc (newbufsize + guardsize);
// return 0 on failure to allocate, let caller deal with it
if (0 == newbuf)
return 0;
memcpy (newbuf, *buf.pbuf, buflen);
// append a guard block to the end of the buffer
memcpy (newbuf + newbufsize, guard, guardsize);
if (*buf.pbuf) {
// verify that we didn't write past the end of the buffer and
// that the user didn't pass a local or static buffer without
// a maxsize format specifier.
RW_ASSERT (0 == memcmp (*buf.pbuf + *buf.pbufsize,
guard, guardsize));
free (*buf.pbuf);
}
*buf.pbuf = newbuf;
*buf.pbufsize = newbufsize;
}
if (0 != str) {
memcpy (*buf.pbuf + buflen, str, len);
buflen += len;
}
buf.endoff = buflen;
return *buf.pbuf + buflen;
}
/********************************************************************/
static int
_rw_vasnprintf_c99 (FmtSpec *pspec, // array of processed parameters
size_t paramno, // index of current parameter
Buffer &buf,
VarArgs *pva) // parameter list
{
_RWSTD_UNUSED (paramno);
RW_ASSERT (0 != pspec);
int len = -1;
FmtSpec &spec = pspec [paramno];
// retreve paramater either from the argument list (or array),
// or from the array of previously saved parameters in the case
// of a positional parameter
#define PARAM(name, pva) \
(0 < spec.paramno \
? pspec [spec.paramno - 1].param.name \
: VA_ARG (*pva, FmtSpec::name ## t))
switch (spec.cvtspec) {
case 'b': // extension: bool
case 'd':
case 'i':
case 'o':
case 'x':
case 'X':
case 'u':
len = _rw_fmtinteger (pspec, paramno, buf, pva);
break;
case 'e':
case 'E':
case 'f':
case 'F':
case 'g':
case 'G':
if (spec.mod == spec.mod_L) {
spec.param.ldbl_ = PARAM (ldbl_, pva);
len = _rw_fmtfloating (spec, buf, &spec.param.ldbl_);
}
else {
spec.param.dbl_ = PARAM (dbl_, pva);
len = _rw_fmtfloating (spec, buf, &spec.param.dbl_);
}
break;
case 'a':
RW_ASSERT (!"%a not implemented");
break;
case 'A':
RW_ASSERT (!"%A not implemented");
break;
case 'c':
// If no l length modifier is present, the int argument is converted
// to an unsigned char, and the resulting character is written. If
// an l length modifier is present, the wint_t argument is converted
// as if by an ls conversion specification with no precision and an
// argument that points to the initial element of a two-element array
// of wchar_t, the first element containing the wint_t argument to
// the lc conversion specification and the second a null wide
// character.
if (spec.mod == spec.mod_l) {
// wint_t argument formatted as wchar_t (no escapes)
spec.param.wint_ = PARAM (wint_, pva);
len = _rw_fmtwchr (spec, buf, spec.param.wint_, 1);
}
else {
// int argument formatted as char (no escapes)
spec.param.int_ = PARAM (int_, pva);
len = _rw_fmtchr (spec, buf, spec.param.int_, 1);
}
break;
case 's':
if (spec.mod == spec.mod_l) {
spec.param.ptr_ = PARAM (ptr_, pva);
const wchar_t* const str = (wchar_t*)spec.param.ptr_;
len = _rw_fmtwstr (spec, buf, str, _RWSTD_SIZE_MAX);
}
else {
spec.param.ptr_ = PARAM (ptr_, pva);
const char* const str = (char*)spec.param.ptr_;
len = _rw_fmtstr (spec, buf, str, _RWSTD_SIZE_MAX);
}
break;
case 'p': {
// The argument shall be a pointer to void. The value of the pointer
// is converted to a sequence of printing characters, in an
// implementation-defined manner.
spec.param.ptr_ = PARAM (ptr_, pva);
len = _rw_fmtptr (spec, buf, spec.param.ptr_);
break;
}
case 'm': { // %m (popular extension)
spec.param.int_ = errno;
len = _rw_fmterrno (spec, buf, spec.param.int_);
break;
}
case 'n': {
// The argument shall be a pointer to signed integer into which
// is written the number of characters written to the output
// stream so far by this call to fprintf. No argument is converted,
// but one is consumed. If the conversion specification includes
// any flags, a field width, or a precision, the behavior is
// undefined.
RW_ASSERT (0 != buf.pbuf);
RW_ASSERT (0 != *buf.pbuf);
// len = int (strlen (*buf.pbuf));
len = int (buf.endoff);
spec.param.ptr_ = PARAM (ptr_, pva);
if (spec.mod == spec.mod_hh) {
UChar* const ptr = (UChar*)spec.param.ptr_;
RW_ASSERT (0 != ptr);
*ptr = len;
}
else if (spec.mod == spec.mod_h) {
short* const ptr = (short*)spec.param.ptr_;
RW_ASSERT (0 != ptr);
*ptr = len;
}
else if (spec.mod == spec.mod_L) {
#ifdef _RWSTD_LONG_LONG
_RWSTD_LONG_LONG* const ptr = (_RWSTD_LONG_LONG*)spec.param.ptr_;
RW_ASSERT (0 != ptr);
*ptr = short (len);
#else // if !defined (_RWSTD_LONG_LONG)
RW_ASSERT (!"%Ln not implemented");
#endif // _RWSTD_LONG_LONG
}
else if (spec.mod == spec.mod_l) {
long* const ptr = (long*)spec.param.ptr_;
RW_ASSERT (0 != ptr);
*ptr = long (unsigned (len));
}
else if (spec.mod == spec.mod_t) {
ptrdiff_t* const ptr = (ptrdiff_t*)spec.param.ptr_;
RW_ASSERT (0 != ptr);
*ptr = ptrdiff_t (unsigned (len));
}
else {
int* const ptr = (int*)spec.param.ptr_;
RW_ASSERT (0 != ptr);
*ptr = len;
}
break;
}
case '%':
break;
}
return len;
}
/********************************************************************/
// implements rw_vasnprintf, but may be called recursively
static int
_rw_pvasnprintf (Buffer &buf, const char *fmt, VarArgs *pva)
{
// save the length of the initial subsequence already
// in the buffer
const size_t begoff = buf.endoff;
// local buffer for a small number of conversion specifiers
// will grow dynamically if their number exceeds its capacity
FmtSpec specbuf [32];
FmtSpec *pspec = specbuf;
// local buffer for backtrack offsets implementing conditionals
int backtrack [64];
int nextoff = 0;
char fmtspec [64];
size_t spec_bufsize = sizeof specbuf / sizeof *specbuf;
size_t paramno = 0;
if (0 == fmt || 0 > _RW::__rw_memattr (fmt, _RWSTD_SIZE_MAX, -1))
return _rw_fmtbadaddr (pspec [0], buf, fmt);
for (const char *fc = fmt; *fc; ) {
const char* const pcnt = strchr (fc, '%');
size_t nchars = pcnt ? pcnt - fmt : strlen (fc);
if (0 == _rw_bufcat (buf, fmt, nchars))
goto fail;
RW_ASSERT (0 != *buf.pbuf);
RW_ASSERT (0 != buf.pbufsize);
if (0 == pcnt)
break;
fc = pcnt + 1;
if ('%' == *fc) {
// handle "%%"
if (0 == _rw_bufcat (buf, "%", 1))
goto fail;
fmt = ++fc;
continue;
}
if (spec_bufsize == paramno) {
// grow the buffer of conversion specifiers
const size_t bytesize = spec_bufsize * sizeof (FmtSpec);
FmtSpec* const tmp = (FmtSpec*)malloc (bytesize * 2);
if (tmp) {
memcpy (tmp, pspec, bytesize);
if (pspec != specbuf)
free (pspec);
pspec = tmp;
}
else
goto fail;
spec_bufsize *= 2;
}
if ('{' == *fc) {
const char* const endbrace = strchr (++fc, '}');
if (0 == endbrace || fc == endbrace) {
const size_t flen = strlen (fc -= 2);
if (0 == _rw_bufcat (buf, fc, flen))
goto fail;
fc += flen;
continue;
}
const size_t fmtlen = endbrace - fc;
// FIXME: handle specifier strings that are longer
RW_ASSERT (fmtlen < sizeof fmtspec);
memcpy (fmtspec, fc, fmtlen);
fmtspec [fmtlen] = '\0';
// compute the length of the buffer so far
const size_t buflen = _rw_bufcat (buf, "", 0) - *buf.pbuf;
RW_ASSERT (paramno < spec_bufsize);
// initiaze the current format specification, setting
// all unused bits to 0
const int speclen = _rw_fmtspec (pspec + paramno, true, fc, pva);
// copy the current backtrack offset if one exists
// and set the condition and condition true bits
if (nextoff) {
// set the condition valid bit
pspec [paramno].cond = 1;
if (backtrack [nextoff - 1] < 0) {
// negative offset indicates an active clause
pspec [paramno].cond_true = 1;
}
else {
// non-negative offset indicates an inactive clause
// no-op
}
}
// append formatted string to the end of the buffer
// reallocating it if necessary; callee may change
// the specification as necessary (e.g., based on
// the if/else clause)
int len =
_rw_vasnprintf_ext (pspec, paramno, buf, fmtspec, pva);
// the callback returns a negative value on error
if (len < 0)
goto fail;
RW_ASSERT (size_t (len) <= *buf.pbufsize);
RW_ASSERT (buf.endoff <= *buf.pbufsize);
const size_t offinx = size_t (nextoff) - 1;
if (pspec [paramno].cond_end && pspec [paramno].cond_begin) {
// change from an if to an else clause
RW_ASSERT (0 < nextoff);
RW_ASSERT (0 == len);
RW_ASSERT (offinx < sizeof backtrack / sizeof *backtrack);
if (pspec [paramno].cond_true) {
// change from an inactive if to an active else
// (same as the end of an inactive clause)
RW_ASSERT (0 <= backtrack [offinx]);
// set the length so as to backtrack to the position
// saved on the top of the backtrack stack
len = -int (buflen) + backtrack [offinx];
buf.endoff += len;
// invert the offset to indicate an active clause
backtrack [offinx] = ~backtrack [offinx];
}
else {
// change from an active if to an inactive else
RW_ASSERT (backtrack [offinx] < 0);
// save the current length of the buffer
// as the new backtrack offset
backtrack [offinx] = int (buflen);
}
}
else if (pspec [paramno].cond_begin) {
// start of a new if clause
// push it on the stack of backtracking offsets using
// negative values to indicate active clauses and
// non-negative values inactive ones
if (pspec [paramno].cond_true)
backtrack [nextoff++] = ~int (buflen);
else
backtrack [nextoff++] = int (buflen);
// store the value of the conditional expression
// extracted from the argument list
++paramno;
}
else if (pspec [paramno].cond_end) {
// the end of an if/else clause
if (!pspec [paramno].cond_true) {
// the end of an inactive clause
RW_ASSERT (offinx < sizeof backtrack / sizeof *backtrack);
RW_ASSERT (backtrack [offinx] <= int (buflen));
// set the length so as to backtrack to the position
// saved on the top of the backtrack stack
len = -int (buflen) + backtrack [offinx];
buf.endoff += len;
}
// pop it off the top of the stack
--nextoff;
}
else {
// store the value of the argument extracted from the list
++paramno;
}
RW_ASSERT (len + buflen <= *buf.pbufsize);
fc += speclen + 1;
if (fc < endbrace)
fc = endbrace + 1;
}
else {
const int speclen =
_rw_fmtspec (pspec + paramno, false, fc, pva);
if (speclen) {
const int len = _rw_vasnprintf_c99 (pspec, paramno, buf, pva);
if (-1 == len)
goto fail;
// store the argument extracted from the argument list
// but discard positional specifiers
if (-1 == pspec [paramno].paramno)
++paramno;
fc += speclen;
}
else if (0 == _rw_bufcat (buf, "%", 1)) {
goto fail;
}
}
fmt = fc;
}
for (size_t i = 0; i != paramno; ++i)
free (pspec [i].strarg);
// deallocate if dynamically allocated
if (pspec != specbuf)
free (pspec);
RW_ASSERT (begoff <= buf.endoff);
// return the number of characters appended to the buffer
// buffer isn't necessarily NUL terminated at this point
return int (buf.endoff - begoff);
fail: // function failed
for (size_t i = 0; i != paramno; ++i)
free (pspec [i].strarg);
if (pspec != specbuf)
free (pspec);
return -1;
}
_TEST_EXPORT int
rw_vasnprintf (char **pbuf, size_t *pbufsize, const char *fmt, va_list varg)
{
va_list *pva;
va_list vacpy;
_RWSTD_VA_COPY (vacpy, varg);
pva = &vacpy;
// do not use varg or vacpy below this point -- use *pva instead
#define varg DONT_TOUCH_ME
#define vacpy DONT_TOUCH_ME
size_t default_bufsize = 1024;
if (0 == pbufsize)
pbufsize = &default_bufsize;
Buffer buf = { pbuf, pbufsize, _RWSTD_SIZE_MAX, 0 };
// save the initial value of `pbuf'
char* const pbuf_save = *buf.pbuf;
RW_ASSERT (0 != buf.pbuf);
RW_ASSERT (0 != buf.pbufsize);
if ( fmt
&& '%' == fmt [0] && '{' == fmt [1]
&& '+' == fmt [2] && '}' == fmt [3]) {
// when the format string begins with the special %{+}
// directive append to the buffer instead of writing
// over it
fmt += 4;
buf.endoff = *buf.pbuf ? strlen (*buf.pbuf) : 0;
RW_ASSERT (buf.endoff < *buf.pbufsize || !*buf.pbuf);
}
else if (*buf.pbuf)
**buf.pbuf = '\0';
if (fmt && '%' == fmt [0] && '{' == fmt [1]) {
if ('*' == fmt [2] && '}' == fmt [3]) {
const int n = va_arg (*pva, int);
if (n < 0) {
#ifdef EINVAL
errno = EINVAL;
#endif // EINVAL
return -1;
}
buf.maxsize = size_t (n);
fmt += 4;
}
else if (isdigit (fmt [2])) {
char* end = 0;
const ULong n = strtoul (fmt + 2, &end, 0);
if ('}' == *end) {
buf.maxsize = n;
fmt = end + 1;
}
}
}
// format buffer w/o appending terminating NUL
VarArgs args;
args.kind_ = VarArgs::VA_list;
args.arg_.pva_ = pva;
const int len = _rw_pvasnprintf (buf, fmt, &args);
// append terminating NUL
if (len < 0 || !_rw_bufcat (buf, "", 1)) {
const int error = errno;
fprintf (stderr, "%s:%d: rw_vasnprintf(%p, %p, \"%s\", va_list) "
"error: errno = %d: %s\n",
__FILE__, __LINE__, (void*)buf.pbuf, (void*)buf.pbufsize,
fmt, error, strerror (error));
if (*buf.pbuf != pbuf_save) {
// free any allocated memory
free (*buf.pbuf);
*buf.pbuf = 0;
}
if (errno != error) {
// reset errno if it's been modified since it was saved
errno = error;
}
return len < 0 ? len : -1;
}
return len;
#undef varg
#undef vacpy
}
/********************************************************************/
static const char _rw_digits[] = {
"0123456789abcdefghijklmnopqrstuvwxyz"
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
};
_RWSTD_INTERNAL int
_rw_fmtlong (const FmtSpec &spec, Buffer &buf, long val)
{
char buffer [130]; // big enough for a 128-bit long in base 2
char *end = buffer;
// 7.19.6.1 of ISO/IEC 9899:1999:
// The result of converting a zero value with a precision
// of zero is no characters.
if (spec.prec || val) {
const long upoff = 'X' == spec.cvtspec ? 36 : 0;
const int base = 1 < spec.base && spec.base < 37 ? spec.base : 10;
const bool is_signed = 'd' == spec.cvtspec || 'i' == spec.cvtspec;
const bool neg = 0 > val && is_signed;
ULong uval = ULong (neg ? -val : val);
do {
*end++ = _rw_digits [upoff + uval % base];
} while (uval /= base);
// insert as many zeros as specified by precision
// FIXME: prevent buffer overrun
for (int i = int (end - buffer); i < spec.prec; ++i)
*end++ = '0';
// insert octal or hex prefix for non-zero values
if (spec.fl_pound && val) {
switch (spec.base) {
case 16:
*end++ = upoff ? 'X' : 'x';
// fall through
case 8:
*end++ = '0';
break;
}
}
if (neg)
*end++ = '-';
else if (spec.fl_plus && is_signed)
*end++ = '+';
}
// not NUL-terminated
RW_ASSERT (buffer <= end);
const size_t size = size_t (end - buffer);
for (char *pc = buffer; pc < --end; ++pc) {
const char tmp = *pc;
*pc = *end;
*end = tmp;
}
// reset precision to -1 (already handled above)
FmtSpec newspec (spec);
newspec.fl_pound = 0;
newspec.prec = -1;
// handle justification by formatting the resulting string
return _rw_fmtstr (newspec, buf, buffer, size);
}
/********************************************************************/
#ifdef _RWSTD_LONG_LONG
_RWSTD_INTERNAL int
_rw_fmtllong (const FmtSpec &spec, Buffer &buf, _RWSTD_LONG_LONG val)
{
char buffer [130]; // big enough for a 128-bit long long in base 2
char *end = buffer;
// 7.19.6.1 of ISO/IEC 9899:1999:
// The result of converting a zero value with a precision
// of zero is no characters.
if (spec.prec || val) {
const long upoff = 'X' == spec.cvtspec ? 36 : 0;
const int base = 1 < spec.base && spec.base < 37 ? spec.base : 10;
const bool is_signed = 'd' == spec.cvtspec || 'i' == spec.cvtspec;
const bool neg = 0 > val && is_signed;
typedef unsigned _RWSTD_LONG_LONG ULLong;
ULLong uval = ULLong (neg ? -val : val);
do {
*end++ = _rw_digits [upoff + uval % base];
} while (uval /= base);
// insert as many zeros as specified by precision
// FIXME: prevent buffer overrun
for (int i = int (end - buffer); i < spec.prec; ++i)
*end++ = '0';
// insert octal or hex prefix for non-zero values
if (spec.fl_pound && val) {
switch (spec.base) {
case 16:
*end++ = upoff ? 'X' : 'x';
// fall through
case 8:
*end++ = '0';
break;
}
}
if (neg)
*end++ = '-';
else if (spec.fl_plus && is_signed)
*end++ = '+';
}
// not NUL-terminated
RW_ASSERT (buffer <= end);
const size_t size = size_t (end - buffer);
for (char *pc = buffer; pc < --end; ++pc) {
const char tmp = *pc;
*pc = *end;
*end = tmp;
}
// reset precision to -1 (already handled above)
FmtSpec newspec (spec);
newspec.fl_pound = 0;
newspec.prec = -1;
// handle justification by formatting the resulting string
return _rw_fmtstr (newspec, buf, buffer, size);
}
#endif // _RWSTD_LONG_LONG
/********************************************************************/
template <class IntT>
inline int
rw_fmtinteger (const FmtSpec &spec, Buffer &buf, IntT val)
{
#ifdef _RWSTD_LONG_LONG
typedef _RWSTD_LONG_LONG LLong;
const int len = sizeof (val) <= sizeof (long) ?
::_rw_fmtlong (spec, buf, long (val))
: ::_rw_fmtllong (spec, buf, LLong (val));
#else // if !defined (_RWSTD_LONG_LONG)
const int len = ::_rw_fmtlong (spec, buf, long (val));
#endif // _RWSTD_LONG_LONG
return len;
}
/********************************************************************/
static int
_rw_fmtinteger (FmtSpec *pspec, size_t paramno, Buffer &buf, VarArgs *pva)
{
int len = -1;
FmtSpec &spec = pspec [paramno];
switch (spec.cvtspec) {
case 'b': // extension: int argument formatted as bool
spec.param.int_ = PARAM (int_, pva);
len = _rw_fmtstr (spec, buf,
spec.param.int_ ? "true" : "false",
_RWSTD_SIZE_MAX);
break;
case 'd':
case 'i':
if (spec.mod == spec.mod_hh) {
// promoted signed char argument
spec.param.int_ = PARAM (int_, pva);
const signed char val = spec.param.int_;
len = rw_fmtinteger (spec, buf, val);
}
else if (spec.mod == spec.mod_h) {
// promoted signed short argument
spec.param.int_ = PARAM (int_, pva);
const short val = spec.param.int_;
len = rw_fmtinteger (spec, buf, val);
}
else if (spec.mod == spec.mod_l) { // %li
spec.param.long_ = PARAM (long_, pva);
len = rw_fmtinteger (spec, buf, spec.param.long_);
}
else if (spec.mod == spec.mod_ll) { // %lli
#ifdef _RWSTD_LONG_LONG
spec.param.llong_ = PARAM (llong_, pva);
len = rw_fmtinteger (spec, buf, spec.param.llong_);
#elif 8 == _RWSTD_LONG_SIZE
spec.param.long_ = PARAM (long_, pva);
len = rw_fmtinteger (spec, buf, spec.param.long_);
#else
RW_ASSERT (!"%lld, %lli: long long not supported");
#endif // _RWSTD_LONG_LONG
}
else if (spec.mod == spec.mod_t) {
spec.param.diff_ = PARAM (diff_, pva);
len = rw_fmtinteger (spec, buf, spec.param.diff_);
}
else if (spec.mod == spec.mod_z) {
spec.param.diff_ = PARAM (diff_, pva);
len = rw_fmtinteger (spec, buf, spec.param.diff_);
}
else if (1 == spec.iwidth) {
spec.param.int_ = PARAM (int_, pva);
const _RWSTD_INT8_T val = spec.param.int_;
len = rw_fmtinteger (spec, buf, val);
}
else if (2 == spec.iwidth) {
spec.param.int_ = PARAM (int_, pva);
const _RWSTD_INT16_T val = spec.param.int_;
len = rw_fmtinteger (spec, buf, val);
}
else if (3 == spec.iwidth) {
spec.param.i32_ = PARAM (i32_, pva);
const long val = long (spec.param.i32_);
len = rw_fmtinteger (spec, buf, val);
}
else if (4 == spec.iwidth) {
#ifdef _RWSTD_INT64_T
spec.param.i64_ = PARAM (i64_, pva);
#else // if !defined (_RWSTD_INT64_T)
RW_ASSERT (!"%I64d, %I64i: 64-bit types not supported");
#endif // _RWSTD_INT64_T
#if 8 == _RWSTD_LONG_SIZE
const long val = spec.param.i64_;
len = rw_fmtinteger (spec, buf, val);
#elif defined (_RWSTD_LONG_LONG)
const _RWSTD_LONG_LONG val = spec.param.i64_;
len = rw_fmtinteger (spec, buf, val);
#else
RW_ASSERT (!"%I64d, %I64i: 64-bit types not supported");
#endif
}
else { // %i
spec.param.int_ = PARAM (int_, pva);
len = rw_fmtinteger (spec, buf, spec.param.int_);
}
break;
case 'o':
RW_ASSERT (-1 == spec.base);
spec.base = 8;
// fall thru
case 'x':
if (-1 == spec.base)
spec.base = 16;
// fall thru
case 'X':
if (-1 == spec.base)
spec.base = 16;
case 'u':
if (spec.mod == spec.mod_hh) {
// promoted unsigned char argument
spec.param.int_ = PARAM (int_, pva);
const UChar val = spec.param.int_;
len = rw_fmtinteger (spec, buf, val);
}
else if (spec.mod == spec.mod_h) {
// promoted unsigned short argument
spec.param.int_ = PARAM (int_, pva);
const UShrt val = spec.param.int_;
len = rw_fmtinteger (spec, buf, val);
}
else if (spec.mod == spec.mod_ll) {
#ifdef _RWSTD_LONG_LONG
spec.param.llong_ = PARAM (llong_, pva);
const unsigned _RWSTD_LONG_LONG val = spec.param.llong_;
len = rw_fmtinteger (spec, buf, val);
#elif 8 == _RWSTD_LONG_SIZE
spec.param.long_ = PARAM (long_, pva);
const ULong val = spec.param.long_;
len = rw_fmtinteger (spec, buf, val);
#else
RW_ASSERT (!"long long not supported");
#endif // _RWSTD_LONG_LONG
}
else if (spec.mod == spec.mod_l) {
spec.param.long_ = PARAM (long_, pva);
const ULong val = spec.param.long_;
len = rw_fmtinteger (spec, buf, val);
}
else if (spec.mod == spec.mod_t) {
spec.param.size_ = PARAM (size_, pva);
len = rw_fmtinteger (spec, buf, spec.param.size_);
}
else if (spec.mod == spec.mod_z) {
spec.param.size_ = PARAM (size_, pva);
len = rw_fmtinteger (spec, buf, spec.param.size_);
}
else if (1 == spec.iwidth) {
spec.param.int_ = PARAM (int_, pva);
const _RWSTD_UINT8_T val = spec.param.int_;
len = rw_fmtinteger (spec, buf, val);
}
else if (2 == spec.iwidth) {
spec.param.int_ = PARAM (int_, pva);
const long val = UShrt (spec.param.int_);
len = rw_fmtinteger (spec, buf, val);
}
else if (3 == spec.iwidth) {
spec.param.i32_ = PARAM (i32_, pva);
const long val = long (unsigned (spec.param.int_));
len = rw_fmtinteger (spec, buf, val);
}
else if (4 == spec.iwidth) {
#ifdef _RWSTD_INT64_T
spec.param.i64_ = PARAM (i64_, pva);
#else // if defined 9_RWSTD_INT64_T)
RW_ASSERT (!"%I64o, %I64u, %I64x: 64-bit types not supported");
#endif // _RWSTD_INT64_T
#if 8 == _RWSTD_LONG_SIZE
const ULong val = spec.param.i64_;
len = rw_fmtinteger (spec, buf, val);
#elif defined (_RWSTD_LONG_LONG)
const unsigned _RWSTD_LONG_LONG val = spec.param.i64_;
len = rw_fmtinteger (spec, buf, val);
#else
RW_ASSERT (!"%I64o, %I64u, %I64x: 64-bit types not supported");
#endif
}
else {
spec.param.int_ = PARAM (int_, pva);
const unsigned val = spec.param.int_;
len = rw_fmtinteger (spec, buf, val);
}
break;
}
return len;
}
/********************************************************************/
static int
_rw_fmtfloating (const FmtSpec &spec, Buffer &buf, const void *pval)
{
char fmt [128];
char *pf = fmt;
*pf++ = '%';
if (spec.fl_minus)
*pf++ = '-';
if (spec.fl_plus)
*pf++ = '+';
if (spec.fl_pound)
*pf++ = '#';
if (spec.fl_space)
*pf++ = ' ';
if (spec.fl_zero)
*pf++ = '0';
if (spec.mod == spec.mod_h)
*pf++ = 'h';
else if (spec.mod == spec.mod_hh) {
*pf++ = 'h';
*pf++ = 'h';
}
else if (spec.mod == spec.mod_l)
*pf++ = 'l';
else if (spec.mod == spec.mod_ll) {
*pf++ = 'l';
*pf++ = 'l';
}
else if (spec.mod == spec.mod_j)
*pf++ = 'j';
else if (spec.mod == spec.mod_z)
*pf++ = 'z';
else if (spec.mod == spec.mod_t)
*pf++ = 't';
else if (spec.mod == spec.mod_L) {
strcpy (pf, _RWSTD_LDBL_PRINTF_PREFIX);
pf += strlen (pf);
}
else if (spec.mod == spec.mod_ext_A && _RWSTD_LDBL_SIZE == spec.width) {
strcpy (pf, _RWSTD_LDBL_PRINTF_PREFIX);
pf += strlen (pf);
}
if (spec.mod != spec.mod_ext_A && 0 <= spec.width) {
pf += sprintf (pf, "%i", spec.width);
}
if (0 <= spec.prec)
pf += sprintf (pf, ".%i", spec.prec);
*pf++ = char (spec.cvtspec);
*pf = '\0';
// verify that the format buffer hasn't overflowed
RW_ASSERT (size_t (pf - fmt) + 1 < sizeof fmt);
// this might make the buffer almost 5KB
char buffer [_RWSTD_LDBL_MAX_10_EXP + _RWSTD_LDBL_DIG + 3];
int len = -1;
if (spec.mod == spec.mod_ext_A) {
if (_RWSTD_FLT_SIZE == spec.width) {
len = sprintf (buffer, fmt, *(const float*)pval);
}
else if (_RWSTD_DBL_SIZE == spec.width) {
len = sprintf (buffer, fmt, *(const double*)pval);
}
else if (_RWSTD_LDBL_SIZE == spec.width) {
len = sprintf (buffer, fmt, *(const long double*)pval);
}
else {
RW_ASSERT (!"unknown floating point size");
}
}
else if (spec.mod == spec.mod_L)
len = sprintf (buffer, fmt, *(const long double*)pval);
else
len = sprintf (buffer, fmt, *(const double*)pval);
RW_ASSERT (size_t (len) < sizeof buffer);
#if defined (_MSC_VER) || defined (__MINGW32__)
if (5 < len) {
// remove redundant zeros from the exponent (if present)
if ( ('e' == buffer [len - 5] || 'E' == buffer [len - 5])
&& '0' == buffer [len - 3]) {
buffer [len - 3] = buffer [len - 2];
buffer [len - 2] = buffer [len - 1];
buffer [len - 1] = buffer [len];
}
}
#endif // _MSC_VER || __MINGW32__
if (-1 < len && 0 == _rw_bufcat (buf, buffer, size_t (len)))
return -1;
return len;
}
/********************************************************************/
// formats a data, function, or class member pointer as follows
// 0x<hex-long-0>[:<hex-long-1>[:...[:<hex-long-N>]]]
// where N is the size of the pointer in long ints
static int
_rw_fmtpointer (const FmtSpec &spec, Buffer &buf,
const void *pptr, size_t nelems)
{
FmtSpec newspec (spec);
// always format data pointers in hexadecimal
newspec.base = 16;
// set the number of digits
newspec.prec = _RWSTD_LONG_SIZE * 2;
int len = 0;
if (newspec.fl_pound) {
// prepend the 0x prefix even to null pointers
if (0 == _rw_bufcat (buf, "0x", size_t (len = 2))) {
return -1;
}
// reset the pound flag to prevent the integer formatter
// from inserting it again
newspec.fl_pound = 0;
}
const union {
const void *ptr;
const ULong *lptr;
} uptr = { pptr };
for (size_t i = 0; i != nelems; ++i) {
const size_t inx = _rw_big_endian ? i : nelems - i - 1;
int n = _rw_fmtlong (newspec, buf, uptr.lptr [inx]);
if (n < 0)
return -1;
len += n;
}
return len;
}
/********************************************************************/
_RWSTD_INTERNAL int
_rw_fmtptr (const FmtSpec &spec, Buffer &buf, const void *val)
{
return _rw_fmtpointer (spec, buf, &val,
sizeof val / sizeof (long));
}
/********************************************************************/
_RWSTD_INTERNAL int
_rw_fmtbadaddr (const FmtSpec &spec, Buffer &buf,
const void *addr, size_t objsize /* = 0 */)
{
if (0 == addr)
return _rw_bufcat (buf, "(null)", 6) ? 6 : -1;
const size_t off = buf.endoff;
const size_t num_addr = (size_t)addr;
if (objsize && (num_addr & (objsize - 1))) {
if (0 == _rw_bufcat (buf, "(misaligned address ", 20))
return -1;
}
else if (0 == _rw_bufcat (buf, "(invalid address ", 17))
return -1;
FmtSpec newspec (spec);
newspec.fl_pound = 1;
if (-1 == _rw_fmtptr (newspec, buf, addr))
return -1;
if (0 == _rw_bufcat (buf, ")", 1))
return -1;
return int (buf.endoff - off);
}
/********************************************************************/
static int
_rw_fmtfunptr (const FmtSpec &spec, Buffer &buf, funptr_t val)
{
if (spec.mod == spec.mod_l) {
#if 0 // disabled until this is implemented on other platforms
#ifdef _RWSTD_OS_SUNOS
char buffer [256];
Dl_info dli;
// find the symbol corresponding to the address
if (dladdr ((void*)val, &dli)) {
if (!dli.dli_sname)
dli.dli_fname = "unknown";
const size_t sym_off = size_t (dli.dli_saddr);
// compute the offset of the address from the address
// of the symbol dladdr() found in the address space
// of the calling process
const size_t addr_off = size_t (val) < sym_off ?
sym_off - size_t (val) : size_t (val) - sym_off;
// format the address folowed by the name of the symbol
// followed by the offset
const int len = sprintf (buffer, "%#x=%s%c%lu",
size_t (val), dli.dli_sname,
size_t (val) < sym_off ? '-' : '+',
addr_off);
FmtSpec newspec (spec);
newspec.mod_l = newspec.mod_none;
return _rw_fmtstr (newspec, buf, buffer, size_t (len));
}
#endif // _RWSTD_OS_SUNOS
#endif // 0/1
}
return _rw_fmtpointer (spec, buf, &val,
sizeof val / sizeof (long));
}
/********************************************************************/
static int
_rw_fmtmemptr (const FmtSpec &spec, Buffer &buf, memptr_t val)
{
return _rw_fmtpointer (spec, buf, &val,
sizeof val / sizeof (long));
}
/********************************************************************/
static int
_rw_fmttm (const FmtSpec &spec, Buffer &buf, const tm *tmb)
{
if ( 0 == tmb || 0 > _RW::__rw_memattr (tmb, sizeof *tmb, -1)
|| (size_t)tmb & (sizeof (int) - 1)) {
return _rw_fmtbadaddr (spec, buf, tmb, sizeof (int));
}
static const char* const months[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static const char* const wdays[] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
char vbuf [1024]; // verbose format
int verbose = spec.fl_pound;
int invalid = 0;
if ( tmb->tm_sec < 0 || 60 < tmb->tm_sec
|| tmb->tm_min < 0 || 59 < tmb->tm_min
|| tmb->tm_hour < 0 || 23 < tmb->tm_hour
|| tmb->tm_wday < 0 || 6 < tmb->tm_wday
|| tmb->tm_mday < 1 || 31 < tmb->tm_mday
|| _RWSTD_INT_MAX - 1900 < tmb->tm_year
|| tmb->tm_mon < 0 || 11 < tmb->tm_mon
|| tmb->tm_yday < 0 || 365 < tmb->tm_yday) {
invalid = 1;
verbose = 1;
}
if (!invalid) {
strcpy (vbuf, wdays [tmb->tm_wday]);
strcat (vbuf, " ");
strcat (vbuf, months [tmb->tm_mon]);
strcat (vbuf, " ");
sprintf (vbuf + strlen (vbuf), "%2d %02d:%02d:%02d ",
tmb->tm_mday, tmb->tm_hour, tmb->tm_min, tmb->tm_sec);
if (tmb->tm_isdst)
strcat (vbuf, "DST ");
if (tmb->tm_year < -1899) {
sprintf (vbuf + strlen (vbuf), "%d BC",
-(tmb->tm_year + 1899));
}
else {
sprintf (vbuf + strlen (vbuf), "%d",
tmb->tm_year + 1900);
}
}
if (verbose) {
static const char comma[] = ", ";
const char *sep = "";
strcpy (vbuf, "{ ");
if (tmb->tm_sec) {
sprintf (vbuf + strlen (vbuf), "%stm_sec=%d", sep, tmb->tm_sec);
if (tmb->tm_sec < 0 || 60 < tmb->tm_sec)
strcat (vbuf, " [0,60]");
sep = comma;
}
if (tmb->tm_min) {
sprintf (vbuf + strlen (vbuf), "%stm_min=%d", sep, tmb->tm_min);
if (tmb->tm_min < 0 || 59 < tmb->tm_min)
strcat (vbuf, " [0,59]");
sep = comma;
}
if (tmb->tm_hour) {
sprintf (vbuf + strlen (vbuf), "%stm_hour=%d", sep, tmb->tm_hour);
if (tmb->tm_hour < 0 || 23 < tmb->tm_hour)
strcat (vbuf, " [0,23]");
sep = comma;
}
sprintf (vbuf + strlen (vbuf), "%stm_mday=%d", sep, tmb->tm_mday);
if (tmb->tm_mday < 1 || 31 < tmb->tm_mday)
strcat (vbuf, " [1,31]");
sep = comma;
if (tmb->tm_mon) {
sprintf (vbuf + strlen (vbuf), "%stm_mon=%d", sep, tmb->tm_mon);
if (tmb->tm_mon < 0 || 11 < tmb->tm_mon)
strcat (vbuf, " [0,11]");
else {
sprintf (vbuf + strlen (vbuf), " %s",
months [unsigned (tmb->tm_mon) % 7]);
}
}
if (tmb->tm_year) {
sprintf (vbuf + strlen (vbuf), "%stm_year=%d", sep, tmb->tm_year);
if (_RWSTD_INT_MAX - 1900 < tmb->tm_year)
sprintf (vbuf + strlen (vbuf), " [%d,%d]",
_RWSTD_INT_MIN, _RWSTD_INT_MAX - 1900);
}
if (tmb->tm_wday) {
sprintf (vbuf + strlen (vbuf), "%stm_wday=%d", sep, tmb->tm_wday);
if (tmb->tm_wday < 0 || 6 < tmb->tm_wday)
strcat (vbuf, " [0,6]");
else {
sprintf (vbuf + strlen (vbuf), " %s",
wdays [unsigned (tmb->tm_wday) % 7]);
}
}
if (tmb->tm_yday) {
sprintf (vbuf + strlen (vbuf), "%stm_yday=%d", sep, tmb->tm_yday);
if (tmb->tm_yday < 0 || 365 < tmb->tm_yday)
strcat (vbuf, " [0,365]");
}
if (tmb->tm_isdst)
sprintf (vbuf + strlen (vbuf), "%stm_isdst=%d", sep, tmb->tm_isdst);
strcat (vbuf, " }");
}
const size_t len = strlen (vbuf);
if (0 == _rw_bufcat (buf, vbuf, len))
return -1;
return int (len);
}
/********************************************************************/
template <class charT>
int _rw_quotechar (char *buf, charT wc, int noesc)
{
// wchar_t is signed, convert its value to unsigned long
// without widening (i.e., treat it as an unsigned type)
const _RWSTD_UWCHAR_INT_T wi =
_RWSTD_STATIC_CAST (_RWSTD_UWCHAR_INT_T, wc);
if ((1 == sizeof wc || wi < 0x100U) && noesc) {
buf [0] = char (wc);
buf [1] = '\0';
return 1;
}
int len = 3;
buf [0] = '\\';
buf [2] = '\0';
switch (wc) {
case '\a': buf [1] = 'a'; buf [len = 2] = '\0'; break;
case '\b': buf [1] = 'b'; buf [len = 2] = '\0'; break;
case '\f': buf [1] = 'f'; buf [len = 2] = '\0'; break;
case '\n': buf [1] = 'n'; buf [len = 2] = '\0'; break;
case '\r': buf [1] = 'r'; buf [len = 2] = '\0'; break;
case '\t': buf [1] = 't'; buf [len = 2] = '\0'; break;
case '\v': buf [1] = 'v'; buf [len = 2] = '\0'; break;
case '\\': buf [1] = '\\'; buf [len = 2] = '\0'; break;
case '"' : buf [1] = '"'; buf [len = 2] = '\0'; break;
default:
if (wc < ' ' || wc > '~') {
if (0 == wc) {
buf [1] = '0';
buf [2] = '\0';
len = 2;
}
else if (1 == sizeof wc) {
len = 1 + sprintf (buf + 1, "x%02x", UChar (wc));
}
else {
const int width =
wi > 0xfffffffUL ? 8 : wi > 0xffffffUL ? 7
: wi > 0xfffffUL ? 6 : wi > 0xffffUL ? 5
: wi > 0xfffUL ? 4 : wi > 0xffUL ? 3
: wi > 0xfUL ? 2 : 2;
len = 1 + sprintf (buf + 1, "x%0*lx", width, ULong (wi));
}
}
else {
buf [0] = char (wc);
buf [1] = '\0';
len = 1;
}
}
return len;
}
enum {
A_ESC = 1, // use escape sequences
A_CHAR = 1 << 1, // format each element as a char
A_WCHAR = 1 << 2, // format each element as a wchar_t
A_ARRAY = 1 << 3 // generic array formatting
};
template <class elemT>
int _rw_fmtarray (const FmtSpec &spec,
Buffer &buf,
const elemT *array, // pointer to first element
size_t nelems, // number of elements
int flags)
{
RW_ASSERT (0 != buf.pbuf);
const size_t memlen = _RWSTD_SIZE_MAX == nelems
? _RWSTD_SIZE_MAX : nelems * sizeof *array;
if ( 0 == array
|| 0 > _RW::__rw_memattr (array, memlen, -1)
|| ((size_t)array & (sizeof *array - 1))) {
// qualify the name of the static function in order to
// allow it to be found when referenced from a template
return ::_rw_fmtbadaddr (spec, buf, array, sizeof *array);
}
if (_RWSTD_SIZE_MAX == nelems) {
// compute the length of the NUL-terminate string
nelems = 0;
for (const elemT *pc = array; *pc; ++pc, ++nelems);
}
// separator string to insert between numerical elements
const char* const sep = flags & (A_CHAR | A_WCHAR) ?
"" : spec.strarg ? spec.strarg : ",";
// length of the separator string
const size_t seplen = strlen (sep);
// compute the size of the buffer to preallocate
const size_t bufsize = (nelems + 1) * (65 + seplen) + 3;
// preallocate a buffer large enough for the formatted array
char *next = _rw_bufcat (buf, 0, bufsize);
const char* const bufend = next;
if (0 == next)
return -1;
if (0 == nelems) {
if (!(flags & A_ESC)) {
#if 0 // width handling disabled (width used for array formatting)
for (int w = 0; w < spec.width; ++w)
*next++ = ' ';
#endif // 0/1
}
else {
// append 'L' for a generic array (i.e., one whose type
// is not known to be wchar_t) that is the same size as
// wchar_t (bot not for an array of wchar_t's being
// formatted using the {%ls} directive -- the caller
// can easily stick the 'L' there themselves)
if (flags & A_ARRAY && _RWSTD_WCHAR_SIZE == sizeof (elemT))
*next++ = 'L';
*next++ = '"';
#if 0 // width handling disabled (width used for array formatting)
for (int w = 0; w < spec.width; ++w)
*next++ = ' ';
#endif // 0/1
*next++ = '"';
}
buf.endoff += size_t (next - bufend);
return int (next - bufend);
}
char *s = next;
const elemT *last = array;
bool any_repeats = false;
long last_repeat = flags & A_ESC ? -1L : 0L;
// large enough for a 64-bit integer in binary notation
char elemstr [65];
const ptrdiff_t N = ptrdiff_t (flags & A_ESC ? 20 : 0);
for (const elemT *pelem = last + 1; ; ++pelem) {
if (size_t (pelem - array) < nelems && *pelem == *last) {
// if the last processed element repeats, continue
// until a different element is encountered
continue;
}
if (N > 1 && pelem - last > N) {
// if the last processed element repeats N or more
// times, format the repeat count instead of all the
// repeated occurrences of the element to conserve
// space and make the string more readable
const long repeat = long (pelem - last);
if (flags & (A_CHAR | A_WCHAR)) {
// format element into elemstr as a character
_rw_quotechar (elemstr, *last, !(flags & A_ESC));
s += sprintf (s, "%s'%s' <repeats %ld times>",
-1 == last_repeat ? ""
: 0 == last_repeat ? "\", " : ", ",
elemstr, repeat);
}
else {
// format element into elemstr as an integer
char *localbuf = elemstr;
size_t localbufsize = sizeof elemstr;
Buffer bufspec = {
&localbuf, &localbufsize, sizeof elemstr, 0
};
*localbuf = '\0';
const int intlen = rw_fmtinteger (spec, bufspec, *last);
// no error should happen
RW_ASSERT (0 < intlen);
localbuf [intlen] = '\0';
// verify that rw_fmtinteger didn't try to reallocate
RW_ASSERT (localbuf == elemstr);
s += sprintf (s, "%s%s <repeats %ld times>%s",
last_repeat < 1 ? "" : sep, elemstr, repeat, sep);
}
last = pelem;
any_repeats = true;
last_repeat = repeat;
}
else {
// otherwise (if the last processed element repeats
// fewer than N times) format the element that many
// times
if (last_repeat < 0) {
if (flags & (A_CHAR | A_WCHAR)) {
if ( (flags & A_ARRAY)
&& _RWSTD_WCHAR_SIZE == sizeof (elemT))
*s++ = 'L';
// insert an opening quote
*s++ = '\"';
}
}
else if (last_repeat > 0) {
if (flags & (A_CHAR | A_WCHAR)) {
// append a comma after a preceding "<repeats N times>"
*s++ = ',';
*s++ = ' ';
// append an opening quote for character arrays
*s++ = '\"';
}
}
if (flags & (A_CHAR | A_WCHAR)) {
// format element into elemstr as a character
_rw_quotechar (elemstr, *last, !(flags & A_ESC));
}
else {
// format element into elemstr as an integer
char *localbuf = elemstr;
size_t localbufsize = sizeof elemstr;
Buffer bufspec = {
&localbuf, &localbufsize, sizeof elemstr, 0
};
*localbuf = '\0';
const int intlen = rw_fmtinteger (spec, bufspec, *last);
// no error should happen
RW_ASSERT (0 < intlen);
localbuf [intlen] = '\0';
// verify that rw_fmtinteger didn't try to reallocate
RW_ASSERT (localbuf == elemstr);
}
const size_t elemlen = strlen (elemstr);
for ( ; last != pelem; ++last) {
memcpy (s, elemstr, elemlen);
s += elemlen;
memcpy (s, sep, seplen + 1);
s += seplen;
}
last_repeat = 0;
if (size_t (pelem - array) == nelems) {
if (flags & (A_CHAR | A_WCHAR) && flags & A_ESC)
*s++ = '\"';
// eliminate the last separator
s -= seplen;
*s = '\0';
break;
}
}
if (size_t (pelem - array) == nelems) {
// eliminate the last separator
s -= seplen;
*s = '\0';
break;
}
}
if (any_repeats) {
// enclose the whole thing in curly braces if any repeated elements
// have been formatted using the "... <repeats N times>" syntax
const size_t len = strlen (next);
memmove (next + 2, next, len);
next [0] = '{';
next [1] = ' ';
next [len + 2] = ' ';
next [len + 3] = '}';
next [len + 4] = '\0';
s = next + len + 4;
}
buf.endoff += size_t (s - bufend);
return int (s - bufend);
}
#ifndef _RWSTD_NO_EXPLICIT_INSTANTIATION
template int _rw_quotechar (char*, UChar, int);
template int _rw_quotechar (char*, char, int);
template int _rw_quotechar (char*, UShrt, int);
template int _rw_quotechar (char*, UInt, int);
template int _rw_quotechar (char*, ULong, int);
template int _rw_fmtarray (const FmtSpec&, Buffer&, const UChar*, size_t, int);
template int _rw_fmtarray (const FmtSpec&, Buffer&, const char*, size_t, int);
template int _rw_fmtarray (const FmtSpec&, Buffer&, const UShrt*, size_t, int);
template int _rw_fmtarray (const FmtSpec&, Buffer&, const UInt*, size_t, int);
template int _rw_fmtarray (const FmtSpec&, Buffer&, const ULong*, size_t, int);
# ifndef _RWSTD_NO_NATIVE_WCHAR_T
// avoid instantiating when wchar_t is not a fundamental type
// (since it's most likely not distinct from one of the types
// the templates are instantiated on above)
template int _rw_quotechar (char*, wchar_t, int);
template int _rw_fmtarray(const FmtSpec&, Buffer&, const wchar_t*, size_t, int);
# endif // _RWSTD_NO_NATIVE_WCHAR_T
#endif // _RWSTD_NO_EXPLICIT_INSTANTIATION
/********************************************************************/
static int
_rw_fmtarray (FmtSpec *pspec, size_t paramno, Buffer &buf, VarArgs *pva)
{
RW_ASSERT (0 != pspec);
FmtSpec &spec = pspec [paramno];
// save width and set to unspecified (i.e., 1)
const int width = spec.width;
spec.width = -1;
const int flags = A_ARRAY
| ('c' == spec.cvtspec || 'S' == spec.cvtspec ?
A_CHAR | A_ESC : spec.fl_pound ? A_ESC : 0);
// to format an array of integers using the 0 or 0x/0X prefix
// both the pound and the zero flags must be set; clear the pound
// flag unless zero is also set
const unsigned pound = spec.fl_pound;
if (0 == spec.fl_zero)
spec.fl_pound = 0;
const size_t nelems = size_t (spec.prec);
spec.prec = -1;
int len = -1;
// array formatting: width determines the width of each array element,
// precision the number of elements (when negative the array is taken
// to extend up to but not including the first 0 element)
if (-1 == width || 1 == width) {
spec.param.ptr_ = PARAM (ptr_, pva);
const _RWSTD_UINT8_T* const array = (_RWSTD_UINT8_T*)spec.param.ptr_;
// note that when no precision is specified in the format string
// (e.g., "%{Ac}") its value will be -1 and the function will format
// all elements up to but excluding the terminating 0
len = _rw_fmtarray (spec, buf, array, nelems, flags);
}
else if (2 == width) {
spec.param.ptr_ = PARAM (ptr_, pva);
const _RWSTD_UINT16_T* const array = (_RWSTD_UINT16_T*)spec.param.ptr_;
len = _rw_fmtarray (spec, buf, array, nelems, flags);
}
else if (4 == width) {
spec.param.ptr_ = PARAM (ptr_, pva);
const _RWSTD_UINT32_T* const array = (_RWSTD_UINT32_T*)spec.param.ptr_;
len = _rw_fmtarray (spec, buf, array, nelems, flags);
}
#ifdef _RWSTD_UINT64_T
else if (8 == width) {
spec.param.ptr_ = PARAM (ptr_, pva);
const _RWSTD_UINT64_T* const array = (_RWSTD_UINT64_T*)spec.param.ptr_;
len = _rw_fmtarray (spec, buf, array, nelems, flags);
}
#endif // _RWSTD_UINT64_T
else {
RW_ASSERT (!"%{Ac} not implemented for this character size");
}
// restore modified members
spec.fl_pound = pound;
spec.prec = int (nelems);
spec.width = width;
return len;
}
/********************************************************************/
// formats an argv-style array of character strings
static int
_rw_fmtstrarray (FmtSpec *pspec, size_t paramno, Buffer &buf,
const char* const* argv)
{
FmtSpec &spec = pspec [paramno];
int len = 0;
if (0 == argv || 0 > _RW::__rw_memattr (argv, sizeof *argv, -1))
return _rw_fmtbadaddr (spec, buf, argv);
size_t argc = spec.prec;
for (size_t i = 0; i != argc; ++i) {
if (_RWSTD_SIZE_MAX == argc && 0 == argv [i])
break;
int n = _rw_fmtstr (spec, buf, argv [i], _RWSTD_SIZE_MAX);
if (n < 0)
return n;
len += n;
if (i + 1 == argc || (_RWSTD_SIZE_MAX == argc && 0 == argv [i + 1]))
break;
const unsigned pound = spec.fl_pound;
spec.fl_pound = 0;
n = _rw_fmtstr (spec, buf, spec.fl_space ? " " : ",", 1);
spec.fl_pound = pound;
if (n < 0)
return n;
len += n;
}
return len;
}
/********************************************************************/
// implements %c and %{c} (narrow character formatting, both without
// and with escape sequences, respectively)
static int
_rw_fmtchr (const FmtSpec &spec, Buffer &buf, int val, int noesc)
{
const UChar uc = UChar (val);
char buffer [8];
int len = _rw_quotechar (buffer + spec.fl_pound, uc, noesc);
if (spec.fl_pound) {
buffer [0] = buffer [len + 1] = '\'';
buffer [len + 2] = '\0';
len += 2;
}
FmtSpec newspec (spec);
newspec.fl_pound = 0;
return _rw_fmtstr (newspec, buf, buffer, size_t (len));
}
/********************************************************************/
// implements %lc and %{lc} (wide character formatting, both without
// and with escape sequences, respectively)
static int
_rw_fmtwchr (const FmtSpec &spec, Buffer &buf, wint_t val, int noesc)
{
char buffer [16];
int len;
if (0 == noesc && WEOF == val) {
// format EOF
buffer [0] = 'E';
buffer [1] = 'O';
buffer [2] = 'F';
buffer [3] = '\0';
len = 3;
}
else {
const wchar_t wc = wchar_t (val);
len = _rw_quotechar (buffer + spec.fl_pound, wc, noesc);
if (spec.fl_pound) {
buffer [0] = buffer [len + 1] = '\'';
buffer [len + 2] = '\0';
len += 2;
}
}
FmtSpec newspec (spec);
newspec.fl_pound = 0;
return _rw_fmtstr (newspec, buf, buffer, size_t (len));
}
/********************************************************************/
// extern so it can be used in other translation units of the driver
// although it's not mneant to be called directly except by the
// implementation of rw_printf() and friends
/* extern */ int
_rw_fmtstr (const FmtSpec &spec, Buffer &buf, const char *str, size_t len)
{
if (spec.fl_pound)
return _rw_fmtarray (spec, buf, str, len, A_CHAR | A_ESC);
if (0 == str || 0 > _RW::__rw_memattr (str, len, -1))
return _rw_fmtbadaddr (spec, buf, str);
if (_RWSTD_SIZE_MAX == len)
len = strlen (str);
// compute the minimum number of characters to be generated
if (-1 < spec.prec && size_t (spec.prec) < len)
len = size_t (spec.prec);
// the number of generated characters depends on three variables:
// -- the optional field width,
// -- the optional precision, and
// -- the length of the argument
// compute the field width
const size_t width =
-1 < spec.width && len < size_t (spec.width) ?
size_t (spec.width) : len;
// compute the size of padding
const size_t pad = len < width ? width - len : 0;
// [re]allocate enough space in the buffer
if (0 == _rw_bufcat (buf, 0, pad + len))
return -1;
RW_ASSERT (0 != *buf.pbuf);
char *next = *buf.pbuf + buf.endoff;
if (!spec.fl_minus) {
for (size_t i = 0; i != pad; ++i)
*next++ = ' ';
}
memcpy (next, str, len);
next += len;
if (spec.fl_minus) {
for (size_t i = 0; i != pad; ++i)
*next++ = ' ';
}
len += pad;
buf.endoff += len;
return int (len);
}
/********************************************************************/
#ifndef _RWSTD_NO_WCHAR_T
static int
_rw_fmtwstr (const FmtSpec &spec, Buffer &buf,
const wchar_t *wstr, size_t len)
{
if (spec.fl_pound) {
// set up flags so that %{#*S} for generic basic_string quoted
// formatting (i.e., parametrized on the size of the character
// type) prepends a 'L' to the quoted string when the character
// type is (likely to be) wchar_t
const int flags =
('S' == spec.cvtspec && -1 != spec.width ? A_ARRAY : 0)
| A_WCHAR | A_ESC;
return _rw_fmtarray (spec, buf, wstr, len, flags);
}
const size_t memlen = _RWSTD_SIZE_MAX == len
? _RWSTD_SIZE_MAX : len * sizeof *wstr;
if ( 0 == wstr
|| 0 > _RW::__rw_memattr (wstr, memlen, -1)
|| ((size_t)wstr & (sizeof *wstr - 1)))
return _rw_fmtbadaddr (spec, buf, wstr, sizeof *wstr);
if (_RWSTD_SIZE_MAX == len) {
#ifndef _RWSTD_NO_WCSLEN
len = wcslen (wstr);
#else // if defined (_RWSTD_NO_WCSLEN)
len = 0;
for (const wchar_t *pwc = wstr; *pwc; ++pwc, ++len);
#endif // _RWSTD_NO_WCSLEN
}
// compute the minimum number of characters to be generated
if (-1 < spec.prec && size_t (spec.prec) < len)
len = size_t (spec.prec);
// the number of generated characters depends on three variables:
// -- the optional field width,
// -- the optional precision, and
// -- the length of the argument
// compute the field width
const size_t width =
-1 < spec.width && len < size_t (spec.width) ?
size_t (spec.width) : len;
// compute the size of padding
const size_t pad = len < width ? width - len : 0;
// [re]allocate enough space in the buffer
if (0 == _rw_bufcat (buf, 0, pad + len * 10))
return -1;
RW_ASSERT (0 != *buf.pbuf);
char *next = *buf.pbuf + buf.endoff;
if (!spec.fl_minus) {
for (size_t i = 0; i != pad; ++i)
*next++ = ' ';
}
// wmemcpy (next, wstr, len);
for (const wchar_t *pwc = wstr; pwc != wstr + len; ++pwc) {
const int n = _rw_quotechar (next, *pwc, 0);
if (n < 0)
return n;
next += n;
}
if (spec.fl_minus) {
for (size_t i = 0; i != pad; ++i)
*next++ = ' ';
}
len += pad;
buf.endoff += len;
return int (len);
}
#endif // _RWSTD_NO_WCHAR_T
/********************************************************************/
static int
_rw_vasnprintf_ext (FmtSpec *pspec,
size_t paramno,
Buffer &buf,
const char *fmt,
VarArgs *pva)
{
RW_ASSERT (0 != pva);
RW_ASSERT (0 != pspec);
// for convenience
FmtSpec &spec = pspec [paramno];
// the length of the sequence appended to the buffer to return
// to the caller, or a negative value (such as -1) on error
int len = -1;
for (size_t i = _rw_usr_inx; i-- && _rw_usr_fun [i]; ) {
// process the stack of user-defined formatting functions
// top to bottom
if (_rw_usr_fun [i]) {
if (0 == _rw_bufcat (buf, 0, 1))
return -1;
(*buf.pbuf)[buf.endoff] = '\0';
// user-defined extension?
if (0 < spec.paramno) {
// pass the address of the paramno argument
len = (_rw_usr_fun [i])(buf.pbuf, buf.pbufsize, fmt,
&pspec [spec.paramno - 1].param);
}
else {
// pass the address of the variable argument list
// followed by the address of the current parameter
len = (_rw_usr_fun [i])(buf.pbuf, buf.pbufsize, fmt,
pva->arg_.pva_, &spec.param);
}
// special value indicating that the user-defined function
// decided not to process the format string and is passing
// control back to us
if (_RWSTD_INT_MIN != len) {
if (-1 != len)
buf.endoff += len;
return len;
}
}
}
len = -1;
switch (spec.cvtspec) {
case '!': // %{!}, %{+!}, %{-!}
// set a user-defined formatting function
if (spec.fl_plus) {
// push a new user-defined handler on top of the stack
spec.param.funptr_ = PARAM (funptr_, pva);
_rw_usr_fun [_rw_usr_inx++] = (_rw_usr_cb_t*)spec.param.funptr_;
}
if (spec.fl_minus) {
// pop the stack of user-defined handlers
if (_rw_usr_inx)
--_rw_usr_inx;
}
if (!spec.fl_plus && !spec.fl_minus) {
// replace the top of the stack of user-defined handlers
if (0 == _rw_usr_inx)
++_rw_usr_inx;
spec.param.funptr_ = PARAM (funptr_, pva);
_rw_usr_fun [_rw_usr_inx - 1] = (_rw_usr_cb_t*)spec.param.funptr_;
}
len = 0;
break;
case '@': { // %{@}
// parametrized (nested) formatting directive
spec.param.ptr_ = PARAM (ptr_, pva);
const char* const tmp_fmt = (const char*)spec.param.ptr_;
len = _rw_fmtnested (spec, buf, tmp_fmt, pva);
break;
}
case '?': // %{?}
// beginning of an if clause
spec.param.int_ = PARAM (int_, pva);
spec.cond = 1;
spec.cond_begin = 1;
spec.cond_true = 0 != spec.param.int_;
len = 0;
break;
case ':': // %{:}
if (spec.cond) {
// end of an active if clause and the beginning
// of an inactive else clause
spec.cond_begin = 1; // beginning of an else clause
spec.cond_end = 1; // end of an if clause
spec.cond_true = !spec.cond_true;
len = 0;
}
else {
// misplaced "%{:}"?
static const char str[] = "%{:}";
len = _rw_fmtstr (spec, buf, str, _RWSTD_SIZE_MAX);
}
break;
case ';': // %{;}
if (spec.cond) {
spec.cond_end = 1; // end of an if or else clause
len = 0;
}
else {
// misplaced "%{;}"?
static const char str[] = "%{;}";
len = _rw_fmtstr (spec, buf, str, _RWSTD_SIZE_MAX);
}
break;
case 'B': // %{B}
// std::bitset
spec.param.ptr_ = PARAM (ptr_, pva);
if ( 0 == spec.param.ptr_
|| 0 > _RW::__rw_memattr (spec.param.ptr_, spec.prec, -1))
len = _rw_fmtbadaddr (spec, buf, spec.param.ptr_, sizeof (long));
else
len = _rw_fmtbits (spec, buf, spec.param.ptr_, sizeof (long));
break;
case 'b': // %{b}
spec.param.ptr_ = PARAM (ptr_, pva);
if ( 0 == spec.param.ptr_
|| 0 > _RW::__rw_memattr (spec.param.ptr_, spec.prec, -1))
len = _rw_fmtbadaddr (spec, buf, spec.param.ptr_);
else
len = _rw_fmtbits (spec, buf, spec.param.ptr_, 1);
break;
case 'c': // %{c}, %{Ac}, %{Lc}, %{lc}
if (spec.mod == spec.mod_ext_A) { // array of characters
len = _rw_fmtarray (pspec, paramno, buf, pva);
}
else if (spec.mod == spec.mod_L) {
// locale category or LC_XXX constant
spec.param.int_ = PARAM (int_, pva);
len = _rw_fmtlc (spec, buf, spec.param.int_);
}
else if (spec.mod == spec.mod_l) {
// wint_t argument formatted as wchar_t with non-printable
// characters represented using traditional C escape
// sequences
spec.param.wint_ = PARAM (wint_, pva);
return _rw_fmtwchr (spec, buf, spec.param.wint_, 0);
}
else {
// int argument formatted as char with non-printable
// characters represented using traditional C escape
// sequences
spec.param.int_ = PARAM (int_, pva);
return _rw_fmtchr (spec, buf, spec.param.int_, 0);
}
break;
case 'C': // %{C}, %{LC}
if (spec.mod == spec.mod_L) {
// ctype_base::mask value
spec.param.int_ = PARAM (int_, pva);
len = _rw_fmtmask (spec, buf, spec.param.int_);
}
else {
// ctype_base::mask of a character
spec.param.int_ = PARAM (int_, pva);
len = _rw_fmtcharmask (spec, buf, spec.param.int_);
}
break;
case 'E': // %{E} -- Windows GetLastError(), errno elsewhere
#ifdef _WIN32
spec.param.int_ = -1 == spec.width ? GetLastError () : spec.width;
#else // if !defined (_WIN32)
spec.param.int_ = -1 == spec.width ? errno : spec.width;
#endif // _WIN32
len = _rw_fmtlasterror (spec, buf, spec.param.int_);
break;
case 'e': // %{e}, %{Ae}
if (spec.mod == spec.mod_ext_A) { // array of floating point values
spec.param.ptr_ = PARAM (ptr_, pva);
len = _rw_fmtfloating (spec, buf, spec.param.ptr_);
}
else if (spec.mod == spec.mod_ext_I) { // ios::copyfmt_event
spec.param.int_ = PARAM (int_, pva);
len = _rw_fmtevent (spec, buf, spec.param.int_);
}
break;
case 'f': // %{f}, %{Af}, %{If}
if (spec.mod == spec.mod_ext_A) { // array of floating point values
spec.param.ptr_ = PARAM (ptr_, pva);
len = _rw_fmtfloating (spec, buf, spec.param.ptr_);
}
else if (spec.mod == spec.mod_ext_I) { // ios::fmtflags
spec.param.int_ = PARAM (int_, pva);
len = _rw_fmtflags (spec, buf, spec.param.int_);
}
else { // function pointer
spec.param.funptr_ = PARAM (funptr_, pva);
len = _rw_fmtfunptr (spec, buf, spec.param.funptr_);
}
break;
case 'g': // %{g}, %{Ag}
if (spec.mod == spec.mod_ext_A) { // array of floating point values
spec.param.ptr_ = PARAM (ptr_, pva);
len = _rw_fmtfloating (spec, buf, spec.param.ptr_);
}
else {
RW_ASSERT (!"%{g} not implemented");
}
break;
case 'd': // %{Id}
case 'i': // %{Ii}
if (-1 == spec.base)
spec.base = 10;
// fall through
case 'o': // %{Io}
if (spec.mod == spec.mod_ext_I) { // ios::openmode
spec.param.int_ = PARAM (int_, pva);
len = _rw_fmtopenmode (spec, buf, spec.param.int_);
break;
}
if (-1 == spec.base)
spec.base = 8;
// fall through
case 'x': // %{x}, %{Ax}
case 'X': // %{X}, %{AX}
if (-1 == spec.base)
spec.base = 16;
// fall through
case 'u': // %{u}, %{Au}
if (spec.mod == spec.mod_ext_A) { // array
if (-1 == spec.base)
spec.base = 10;
len = _rw_fmtarray (pspec, paramno, buf, pva);
}
else {
// reset base set above
spec.base = -1;
len = _rw_fmtinteger (pspec, paramno, buf, pva);
}
break;
case 'K': // %{K} -- signal
spec.param.int_ = PARAM (int_, pva);
len = _rw_fmtsignal (spec, buf, spec.param.int_);
break;
case 'm': // %{m} -- errno
spec.param.int_ = -1 == spec.width ? errno : spec.width;
len = _rw_fmterrno (spec, buf, spec.param.int_);
break;
case 'M': // %{M}, %{LM}
if (spec.mod == spec.mod_L) { // money_base::pattern
spec.param.ptr_ = PARAM (ptr_, pva);
len = _rw_fmtmonpat (spec, buf, (char*)spec.param.ptr_);
}
else { // member pointer
spec.param.memptr_ = PARAM (memptr_, pva);
len = _rw_fmtmemptr (spec, buf, spec.param.memptr_);
}
break;
case 'n': { // %{n}
// The argument shall be a pointer to signed integer into which
// is written the size of the buffer allocated by this call to
// fprintf. No argument is converted, but one is consumed. If
// the conversion specification includes any flags, a field
// width, or a precision, the behavior is undefined.
RW_ASSERT (0 != buf.pbuf);
RW_ASSERT (0 != *buf.pbuf);
RW_ASSERT (0 != buf.pbufsize);
const size_t nbytes = *buf.pbufsize;
spec.param.ptr_ = PARAM (ptr_, pva);
if (spec.mod == spec.mod_hh) {
UChar* const ptr = (UChar*)spec.param.ptr_;
RW_ASSERT (0 != ptr);
*ptr = UChar (nbytes);
}
else if (spec.mod == spec.mod_h) {
short* const ptr = (short*)spec.param.ptr_;
RW_ASSERT (0 != ptr);
*ptr = short (nbytes);
}
else if (spec.mod == spec.mod_L) {
#ifdef _RWSTD_LONG_LONG
_RWSTD_LONG_LONG* const ptr = (_RWSTD_LONG_LONG*)spec.param.ptr_;
RW_ASSERT (0 != ptr);
*ptr = (_RWSTD_LONG_LONG)(nbytes);
#else // if !defined (_RWSTD_LONG_LONG)
RW_ASSERT (!"%{Ln} not implemented");
#endif // _RWSTD_LONG_LONG
}
else if (spec.mod == spec.mod_l) {
long* const ptr = (long*)spec.param.ptr_;
RW_ASSERT (0 != ptr);
*ptr = long (nbytes);
}
else if (spec.mod == spec.mod_t) {
ptrdiff_t* const ptr = (ptrdiff_t*)spec.param.ptr_;
RW_ASSERT (0 != ptr);
*ptr = ptrdiff_t (nbytes);
}
else {
int* const ptr = (int*)spec.param.ptr_;
RW_ASSERT (0 != ptr);
*ptr = int (nbytes);
}
len = 0;
break;
}
case 'P': // %{P}
// rw_pid_t is _RWSTD_SSIZE_T which is equal to _RWSTD_PTRDIFF_T
spec.param.diff_ = PARAM (diff_, pva);
len = rw_fmtinteger (spec, buf, spec.param.diff_);
break;
case 's': // %{s}, %{As}, %{Is}, %{ls}
if (spec.mod == spec.mod_ext_A) { // array of strings
spec.param.ptr_ = PARAM (ptr_, pva);
const char* const* const argv = (char**)spec.param.ptr_;
len = _rw_fmtstrarray (pspec, paramno, buf, argv);
}
else if (spec.mod == spec.mod_ext_I) { // ios::iostate
spec.param.int_ = PARAM (int_, pva);
len = _rw_fmtiostate (spec, buf, spec.param.int_);
}
else if (spec.mod == spec.mod_l) { // wchar_t*
spec.param.ptr_ = PARAM (ptr_, pva);
const wchar_t* const wstr = (wchar_t*)spec.param.ptr_;
const size_t wstr_len =
spec.width < 0 ? _RWSTD_SIZE_MAX : size_t (spec.width);
len = _rw_fmtarray (spec, buf, wstr, wstr_len, A_WCHAR | A_ESC);
}
else { // char*
spec.param.ptr_ = PARAM (ptr_, pva);
const char* const str = (char*)spec.param.ptr_;
const size_t str_len =
spec.width < 0 ? _RWSTD_SIZE_MAX : size_t (spec.width);
len = _rw_fmtarray (spec, buf, str, str_len, A_CHAR | A_ESC);
}
break;
case 'S': // %{S}, %{lS}, %{#*S}
if ( spec.mod == spec.mod_l
|| ( spec.mod == FmtSpec::mod_none
&& spec.fl_pound && sizeof (wchar_t) == spec.width)) {
// std::wstring
spec.param.ptr_ = PARAM (ptr_, pva);
const std::wstring* const pstr = (std::wstring*)spec.param.ptr_;
if ( 0 == pstr || 0 > _RW::__rw_memattr (pstr, sizeof *pstr, -1)
|| (size_t)pstr & (sizeof (pstr) - 1)) {
len = _rw_fmtbadaddr (spec, buf, pstr, sizeof pstr);
}
else {
const wchar_t* const wstr = pstr->data ();
const std::wstring::size_type size = pstr->size ();
len = _rw_fmtwstr (spec, buf, wstr, size);
}
}
else { // std::string
spec.param.ptr_ = PARAM (ptr_, pva);
const std::string* const pstr = (std::string*)spec.param.ptr_;
if ( 0 == pstr || 0 > _RW::__rw_memattr (pstr, sizeof *pstr, -1)
|| (size_t)pstr & (sizeof (pstr) - 1)) {
len = _rw_fmtbadaddr (spec, buf, pstr, sizeof pstr);
}
else {
const char* const str = pstr->data ();
const std::string::size_type size = pstr->size ();
len = _rw_fmtstr (spec, buf, str, size);
}
}
break;
case 't': { // %{t}
spec.param.ptr_ = PARAM (ptr_, pva);
const tm* const tmb = (tm*)spec.param.ptr_;
len = _rw_fmttm (spec, buf, tmb);
break;
}
case 'w': { // %{Iw}
if (spec.mod == spec.mod_ext_I) { // ios::seekdir
spec.param.int_ = PARAM (int_, pva);
len = _rw_fmtseekdir (spec, buf, spec.param.int_);
break;
}
}
default:
if (spec.strarg) {
return _rw_fmtexpr (spec, buf, pva);
}
else {
char text [80];
len = sprintf (text, "%%{%.*s}", int (sizeof text - 3), fmt);
if (0 == _rw_bufcat (buf, text, size_t (len)))
return -1;
}
}
return len;
}
/********************************************************************/
static int
_rw_fmtexpr (FmtSpec &spec, Buffer &buf, VarArgs *pva)
{
RW_ASSERT (0 != spec.strarg);
char oper [2] = "";
char *param = spec.strarg;
// look for the first operator character (if any)
char* word = strpbrk (param, ":+-=?!");
if (word) {
if (':' == *word) {
if ( '+' == word [1] || '-' == word [1]
|| '=' == word [1] || '?' == word [1]) {
oper [0] = word [0];
oper [1] = word [1];
*word = '\0';
word += 2;
}
else {
// ':' without an immediately following '+', '-',
// '=', or '?' is not special
}
}
else if ('!' == word [0] && ':' == word [1]) {
// extension
oper [0] = word [0];
oper [1] = word [1];
*word = '\0';
word += 2;
}
else {
oper [0] = *word;
*word++ = '\0';
}
}
else
word = oper;
if ('*' == *param) {
// extract the name of the parameter from the argument list
param = VA_ARG (*pva, char*);
}
char* fmtword = 0;
if ('*' == *word) {
// extract "word" from the argument list
word = VA_ARG (*pva, char*);
}
else if ('@' == *word) {
// extract formatting directive from the argument list
// and set word to the result of processing it
const char* const fmt = VA_ARG (*pva, char*);
size_t dummy_size = 0; // unused
Buffer tmpbuf = { &fmtword, &dummy_size, _RWSTD_SIZE_MAX, 0 };
const int len = _rw_pvasnprintf (tmpbuf, fmt, pva);
if (len < 0)
return -1;
// add terminating NUL
if (0 == _rw_bufcat (tmpbuf, "", 1)) {
free (fmtword);
return -1;
}
// set word to the formatted string
word = *tmpbuf.pbuf;
}
// retrieve the value of the parameter from the environments
const char* val = getenv (param);
//////////////////////////////////////////////////////////////////
// From IEEE Std 1003.1, 2004 Edition:
// http://www.opengroup.org/onlinepubs/009695399/utilities/
// xcu_chap02.html#tag_02_06_02
// +--------------------+-------------+-------------+-------------+
// | | parameter | parameter | parameter |
// | +-------------+-------------+-------------+
// | |Set, Not Null| Set, Null | Unset |
// +--------------------+-------------+-------------+-------------+
// | ${parameter:-word} | parameter | word | word |
// | ${parameter-word} | parameter | null | word |
// | ${parameter:=word} | parameter | assign word | assign word |
// | ${parameter=word} | parameter | null | assign word |
// | ${parameter:?word} | parameter | error | error |
// | ${parameter?word} | parameter | null | error |
// | ${parameter:+word} | word | null | null |
// | ${parameter+word} | word | word | null |
// +--------------------+-------------+-------------+-------------+
// Extension:
// +--------------------+-------------+-------------+-------------+
// | ${parameter!:word} | assign word | assign word | assign word |
// +--------------------+-------------+-------------+-------------+
int assign = 0;
int error = 0;
switch (oper [0]) {
case ':': // process two-character operators
if ('-' == oper [1]) {
if (0 == val || '\0' == *val)
val = word;
}
else if ('=' == oper [1]) {
if (0 == val || '\0' == *val) {
val = word;
assign = 1;
}
}
else if ('?' == oper [1]) {
if (0 == val || '\0' == *val) {
val = "";
error = 1;
}
}
else if ('+' == oper [1]) {
if (0 == val || '\0' == *val)
val = "";
else
val = word;
}
else {
// should never happen
RW_ASSERT (!"logic error");
}
break;
case '+':
val = val ? word : "";
break;
case '-':
if (0 == val)
val = word;
break;
case '?':
if (0 == val) {
val = "";
error = 1;
}
break;
case '=':
if (0 == val) {
val = word;
assign = 1;
}
break;
case '!':
val = word ? word : "";
assign = 1;
break;
default:
if (0 == val) {
// undefined variable
val = "";
}
break;
}
RW_ASSERT (0 != val);
int len = int (strlen (val));
if (assign) {
// set/assign the specified value to the variable
char varbuf [256];
char *pbuf = varbuf;
const size_t varlen = strlen (param) + strlen (val) + 2;
if (sizeof varbuf < varlen) {
pbuf = (char*)malloc (varlen);
if (0 == pbuf)
return -1;
}
strcpy (pbuf, param);
strcat (pbuf, "=");
strcat (pbuf, val);
rw_putenv (pbuf);
if (pbuf != varbuf)
free (pbuf);
}
if (error) {
// on error simply print the directive
if (oper [0])
spec.strarg [strlen (spec.strarg)] = oper [0];
char text [256];
len = sprintf (text, "%%{$%.*s}", int (sizeof text - 3), spec.strarg);
if (0 == _rw_bufcat (buf, text, size_t (len))) {
free (fmtword);
return -1;
}
}
else {
// format the value of the variable (after assignment
// if it takes place)
if (0 == _rw_bufcat (buf, val, size_t (len))) {
free (fmtword);
return -1;
}
}
// free the formatted word (if any)
free (fmtword);
// free the string allocated in _rw_fmtspec()
free (spec.strarg);
spec.strarg = 0;
spec.param.ptr_ = _RWSTD_CONST_CAST (char*, val);
return len;
}
/********************************************************************/
static int
_rw_fmtnested (const FmtSpec &spec,
Buffer &buf,
const char *fmt,
VarArgs *pva)
{
RW_ASSERT ('@' == spec.cvtspec);
// optional precision specifies the number of times
// for the directive to be applied
const int nelems = spec.prec < 0 ? 1 : spec.prec;
// separator between array elements
const char *sepstr = "";
size_t seplen = 0;
if (spec.fl_space) {
sepstr = " ";
seplen = 1;
}
VarArgs tmparg (*pva);
if (spec.mod_ext_A == spec.mod) {
tmparg.kind_ = VarArgs::VA_array;
tmparg.arg_.pargs_ = VA_ARG (*pva, void*);
}
int len = 0;
for (int i = 0; i != nelems; ++i) {
len += _rw_pvasnprintf (buf, fmt, &tmparg);
if (seplen && i + 1 < nelems && 0 == _rw_bufcat (buf, sepstr, seplen))
return -1;
}
return len;
}
/********************************************************************/
_TEST_EXPORT int
rw_asnprintf (char **pbuf, size_t *pbufsize, const char *fmt, ...)
{
RW_ASSERT (0 == pbuf || 0 == *pbuf || pbufsize);
va_list va;
va_start (va, fmt);
char* bufptr = 0;
size_t bufsize = 0;
if (0 == pbuf) {
// if pbyf is 0 (i.e., this is a request for the size of the
// buffer necessary to format all the arguments), set pbuf to
// point to a local buf
pbuf = &bufptr;
}
if (0 == pbufsize) {
// pbuf may be 0 regardless of the value of pbuf
pbufsize = &bufsize;
}
const int nchars = rw_vasnprintf (pbuf, pbufsize, fmt, va);
va_end (va);
// verify that the length of the fomatted buffer is less than
// its size (this test is unreliable if there are any embedded
// NULs in the output)
RW_ASSERT (nchars < 0 || strlen (*pbuf) < *pbufsize);
if (pbuf == &bufptr) {
// free the character buffer if pbuf was initially 0
free (pbuf);
}
return nchars;
}
/********************************************************************/
_TEST_EXPORT char*
rw_snprintfa (char *buf, size_t bufsize, const char* fmt, ...)
{
if (buf)
*buf = '\0';
va_list va;
va_start (va, fmt);
const int nchars = rw_vasnprintf (&buf, &bufsize, fmt, va);
va_end (va);
// verify that the length of the fomatted buffer is less than
// its size (this test is unreliable if there are any embedded
// NULs in the output)
RW_ASSERT (nchars < 0 || strlen (buf) < bufsize);
_RWSTD_UNUSED (nchars);
return nchars < 0 ? 0 : buf;
}
/********************************************************************/
_TEST_EXPORT char*
rw_sprintfa (const char *fmt, ...)
{
char* buf = 0;
size_t bufsize = 0;
va_list va;
va_start (va, fmt);
const int nchars = rw_vasnprintf (&buf, &bufsize, fmt, va);
va_end (va);
// verify that the length of the fomatted buffer is less than
// its size (this test is unreliable if there are any embedded
// NULs in the output)
RW_ASSERT (nchars < 0 || (buf && strlen (buf) < bufsize));
_RWSTD_UNUSED (nchars);
return buf;
}
/********************************************************************/
_TEST_EXPORT int
rw_sprintf (char *buf, const char *fmt, ...)
{
char *tmpbuf = 0;
size_t tmpsize = 0;
va_list va;
va_start (va, fmt);
// FIXME: format directly into buf to avoid dynamic allocation
const int nchars = rw_vasnprintf (&tmpbuf, &tmpsize, fmt, va);
va_end (va);
if (-1 < nchars)
memcpy (buf, tmpbuf, size_t (nchars) + 1 /* NUL */);
free (tmpbuf);
return nchars;
}
/********************************************************************/
_TEST_EXPORT int
rw_snprintf (char *buf, size_t bufsize, const char *fmt, ...)
{
char *tmpbuf = 0;
size_t tmpsize = 0;
va_list va;
va_start (va, fmt);
// FIXME: format directly into buf to avoid dynamic allocation
const int nchars = rw_vasnprintf (&tmpbuf, &tmpsize, fmt, va);
va_end (va);
if (size_t (nchars) < bufsize) {
memcpy (buf, tmpbuf, size_t (nchars) + 1 /* NUL */);
}
else {
// buffer isn't big enough
memcpy (buf, tmpbuf, bufsize);
if (bufsize)
buf [bufsize - 1] = '\0';
}
free (tmpbuf);
return nchars;
}
/********************************************************************/
// avoid re-declaring these as exported here and instead rely on the
// declaration in the header #included above in order to prevent the
// bogus MSVC error:
// error C2201: 'rw_stdout' : must have external linkage in order to
// be exported/imported
/* _TEST_EXPORT */ rw_file* const
rw_stdout = _RWSTD_REINTERPRET_CAST (rw_file*, stdout);
/* _TEST_EXPORT */ rw_file* const
rw_stderr = _RWSTD_REINTERPRET_CAST (rw_file*, stderr);
/********************************************************************/
static int
_rw_vfprintf (rw_file *file, const char *fmt, va_list va)
{
char* buf = 0;
size_t bufsize = 0;
const int nchars = rw_vasnprintf (&buf, &bufsize, fmt, va);
int nwrote = nchars;
if (0 < nchars) {
// avoid formatting when there's nothing to output
if (file) {
// allow null file argument (to determine
// the length of the formatted string)
// FIXME: implement this in terms of POSIX write()
// for async-signal safety
FILE* const stdio_file = _RWSTD_REINTERPRET_CAST (FILE*, file);
nwrote = int (fwrite (buf, 1, size_t (nchars), stdio_file));
// flush in case stderr isn't line-buffered (e.g., when
// it's determined not to refer to a terminal device,
// for example after it has been redirected to a file)
fflush (stdio_file);
#ifdef _WIN32
// write string to the attached debugger (if any)
OutputDebugString (buf);
#endif // _WIN32
}
}
free (buf);
return nwrote;
}
/********************************************************************/
_TEST_EXPORT int
rw_fprintf (rw_file *file, const char *fmt, ...)
{
va_list va;
va_start (va, fmt);
const int nchars = _rw_vfprintf (file, fmt, va);
va_end (va);
return nchars;
}
/********************************************************************/
_TEST_EXPORT int
rw_printf (const char *fmt, ...)
{
va_list va;
va_start (va, fmt);
const int nchars = _rw_vfprintf (rw_stdout, fmt, va);
va_end (va);
return nchars;
}