2011-04-20 14:42:16 +02:00
|
|
|
/*
|
|
|
|
* Copyright 2011 Piotr Caban for CodeWeavers
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef PRINTF_WIDE
|
|
|
|
#define APICHAR MSVCRT_wchar_t
|
|
|
|
#define CONVCHAR char
|
|
|
|
#define FUNC_NAME(func) func ## _w
|
|
|
|
#else
|
|
|
|
#define APICHAR char
|
|
|
|
#define CONVCHAR MSVCRT_wchar_t
|
|
|
|
#define FUNC_NAME(func) func ## _a
|
|
|
|
#endif
|
|
|
|
|
2015-03-25 14:22:02 +01:00
|
|
|
#ifndef signbit
|
|
|
|
#define signbit(x) ((x) < 0)
|
|
|
|
#endif
|
|
|
|
|
2011-04-20 14:42:16 +02:00
|
|
|
typedef struct FUNC_NAME(pf_flags_t)
|
|
|
|
{
|
|
|
|
APICHAR Sign, LeftAlign, Alternate, PadZero;
|
|
|
|
int FieldLength, Precision;
|
2015-11-03 19:40:39 +01:00
|
|
|
APICHAR IntegerLength, IntegerDouble, IntegerNative;
|
2016-08-08 14:43:04 +02:00
|
|
|
APICHAR WideString, NaturalString;
|
2011-04-20 14:42:16 +02:00
|
|
|
APICHAR Format;
|
|
|
|
} FUNC_NAME(pf_flags);
|
|
|
|
|
2011-04-20 14:42:48 +02:00
|
|
|
struct FUNC_NAME(_str_ctx) {
|
|
|
|
MSVCRT_size_t len;
|
|
|
|
APICHAR *buf;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int FUNC_NAME(puts_clbk_str)(void *ctx, int len, const APICHAR *str)
|
|
|
|
{
|
|
|
|
struct FUNC_NAME(_str_ctx) *out = ctx;
|
|
|
|
|
|
|
|
if(!out->buf)
|
|
|
|
return len;
|
|
|
|
|
|
|
|
if(out->len < len) {
|
2013-05-06 22:21:03 +02:00
|
|
|
memcpy(out->buf, str, out->len*sizeof(APICHAR));
|
2011-04-20 14:42:48 +02:00
|
|
|
out->buf += out->len;
|
|
|
|
out->len = 0;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(out->buf, str, len*sizeof(APICHAR));
|
|
|
|
out->buf += len;
|
2011-06-06 14:02:30 +02:00
|
|
|
out->len -= len;
|
2011-04-20 14:42:48 +02:00
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2011-04-20 14:42:16 +02:00
|
|
|
static inline const APICHAR* FUNC_NAME(pf_parse_int)(const APICHAR *fmt, int *val)
|
|
|
|
{
|
|
|
|
*val = 0;
|
|
|
|
|
|
|
|
while(isdigit(*fmt)) {
|
|
|
|
*val *= 10;
|
|
|
|
*val += *fmt++ - '0';
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* pf_fill: takes care of signs, alignment, zero and field padding */
|
|
|
|
static inline int FUNC_NAME(pf_fill)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ctx,
|
|
|
|
int len, FUNC_NAME(pf_flags) *flags, BOOL left)
|
|
|
|
{
|
|
|
|
int i, r = 0, written;
|
|
|
|
|
2015-11-03 19:40:38 +01:00
|
|
|
if(flags->Sign && !strchr("diaeEfFgG", flags->Format))
|
2011-04-20 14:42:16 +02:00
|
|
|
flags->Sign = 0;
|
|
|
|
|
|
|
|
if(left && flags->Sign) {
|
|
|
|
flags->FieldLength--;
|
|
|
|
if(flags->PadZero)
|
|
|
|
r = pf_puts(puts_ctx, 1, &flags->Sign);
|
|
|
|
}
|
|
|
|
written = r;
|
|
|
|
|
|
|
|
if((!left && flags->LeftAlign) || (left && !flags->LeftAlign)) {
|
|
|
|
APICHAR ch;
|
|
|
|
|
|
|
|
if(left && flags->PadZero)
|
|
|
|
ch = '0';
|
|
|
|
else
|
|
|
|
ch = ' ';
|
|
|
|
|
|
|
|
for(i=0; i<flags->FieldLength-len && r>=0; i++) {
|
|
|
|
r = pf_puts(puts_ctx, 1, &ch);
|
|
|
|
written += r;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(r>=0 && left && flags->Sign && !flags->PadZero) {
|
|
|
|
r = pf_puts(puts_ctx, 1, &flags->Sign);
|
|
|
|
written += r;
|
|
|
|
}
|
|
|
|
|
|
|
|
return r>=0 ? written : r;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int FUNC_NAME(pf_output_wstr)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ctx,
|
2011-05-24 17:22:32 +02:00
|
|
|
const MSVCRT_wchar_t *str, int len, MSVCRT_pthreadlocinfo locinfo)
|
2011-04-20 14:42:16 +02:00
|
|
|
{
|
|
|
|
#ifdef PRINTF_WIDE
|
|
|
|
return pf_puts(puts_ctx, len, str);
|
|
|
|
#else
|
|
|
|
LPSTR out;
|
2016-11-15 20:36:44 +01:00
|
|
|
BOOL def_char;
|
|
|
|
int len_a = WideCharToMultiByte(locinfo->lc_codepage, WC_NO_BEST_FIT_CHARS,
|
|
|
|
str, len, NULL, 0, NULL, &def_char);
|
|
|
|
if(def_char)
|
|
|
|
return 0;
|
2011-04-20 14:42:16 +02:00
|
|
|
|
|
|
|
out = HeapAlloc(GetProcessHeap(), 0, len_a);
|
|
|
|
if(!out)
|
|
|
|
return -1;
|
|
|
|
|
2016-11-15 20:36:44 +01:00
|
|
|
WideCharToMultiByte(locinfo->lc_codepage, WC_NO_BEST_FIT_CHARS,
|
|
|
|
str, len, out, len_a, NULL, NULL);
|
2011-04-20 14:42:16 +02:00
|
|
|
len = pf_puts(puts_ctx, len_a, out);
|
|
|
|
HeapFree(GetProcessHeap(), 0, out);
|
|
|
|
return len;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int FUNC_NAME(pf_output_str)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ctx,
|
2011-05-24 17:22:32 +02:00
|
|
|
const char *str, int len, MSVCRT_pthreadlocinfo locinfo)
|
2011-04-20 14:42:16 +02:00
|
|
|
{
|
|
|
|
#ifdef PRINTF_WIDE
|
|
|
|
LPWSTR out;
|
2011-05-24 17:22:32 +02:00
|
|
|
int len_w = MultiByteToWideChar(locinfo->lc_codepage, 0, str, len, NULL, 0);
|
2011-04-20 14:42:16 +02:00
|
|
|
|
|
|
|
out = HeapAlloc(GetProcessHeap(), 0, len_w*sizeof(WCHAR));
|
|
|
|
if(!out)
|
|
|
|
return -1;
|
|
|
|
|
2011-05-24 17:22:32 +02:00
|
|
|
MultiByteToWideChar(locinfo->lc_codepage, 0, str, len, out, len_w);
|
2011-04-20 14:42:16 +02:00
|
|
|
len = pf_puts(puts_ctx, len_w, out);
|
|
|
|
HeapFree(GetProcessHeap(), 0, out);
|
|
|
|
return len;
|
|
|
|
#else
|
|
|
|
return pf_puts(puts_ctx, len, str);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int FUNC_NAME(pf_output_format_wstr)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ctx,
|
2011-05-24 17:22:32 +02:00
|
|
|
const MSVCRT_wchar_t *str, int len, FUNC_NAME(pf_flags) *flags, MSVCRT_pthreadlocinfo locinfo)
|
2011-04-20 14:42:16 +02:00
|
|
|
{
|
|
|
|
int r, ret;
|
|
|
|
|
2017-08-31 19:22:55 +02:00
|
|
|
if(len < 0) {
|
|
|
|
/* Do not search past the length specified by the precision. */
|
|
|
|
if(flags->Precision>=0)
|
|
|
|
len = MSVCRT_wcsnlen(str, flags->Precision);
|
|
|
|
else
|
|
|
|
len = strlenW(str);
|
|
|
|
}
|
2011-04-20 14:42:16 +02:00
|
|
|
|
|
|
|
if(flags->Precision>=0 && flags->Precision<len)
|
|
|
|
len = flags->Precision;
|
|
|
|
|
|
|
|
r = FUNC_NAME(pf_fill)(pf_puts, puts_ctx, len, flags, TRUE);
|
|
|
|
ret = r;
|
|
|
|
if(r >= 0) {
|
2011-05-24 17:22:32 +02:00
|
|
|
r = FUNC_NAME(pf_output_wstr)(pf_puts, puts_ctx, str, len, locinfo);
|
2011-04-20 14:42:16 +02:00
|
|
|
ret += r;
|
|
|
|
}
|
|
|
|
if(r >= 0) {
|
|
|
|
r = FUNC_NAME(pf_fill)(pf_puts, puts_ctx, len, flags, FALSE);
|
|
|
|
ret += r;
|
|
|
|
}
|
|
|
|
|
|
|
|
return r>=0 ? ret : r;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int FUNC_NAME(pf_output_format_str)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ctx,
|
2011-05-24 17:22:32 +02:00
|
|
|
const char *str, int len, FUNC_NAME(pf_flags) *flags, MSVCRT_pthreadlocinfo locinfo)
|
2011-04-20 14:42:16 +02:00
|
|
|
{
|
|
|
|
int r, ret;
|
|
|
|
|
2017-08-31 19:22:55 +02:00
|
|
|
if(len < 0) {
|
|
|
|
/* Do not search past the length specified by the precision. */
|
|
|
|
if(flags->Precision>=0)
|
|
|
|
len = MSVCRT_strnlen(str, flags->Precision);
|
|
|
|
else
|
|
|
|
len = strlen(str);
|
|
|
|
}
|
2011-04-20 14:42:16 +02:00
|
|
|
|
|
|
|
if(flags->Precision>=0 && flags->Precision<len)
|
|
|
|
len = flags->Precision;
|
|
|
|
|
|
|
|
r = FUNC_NAME(pf_fill)(pf_puts, puts_ctx, len, flags, TRUE);
|
|
|
|
ret = r;
|
|
|
|
if(r >= 0) {
|
2011-05-24 17:22:32 +02:00
|
|
|
r = FUNC_NAME(pf_output_str)(pf_puts, puts_ctx, str, len, locinfo);
|
2011-04-20 14:42:16 +02:00
|
|
|
ret += r;
|
|
|
|
}
|
|
|
|
if(r >= 0) {
|
|
|
|
r = FUNC_NAME(pf_fill)(pf_puts, puts_ctx, len, flags, FALSE);
|
|
|
|
ret += r;
|
|
|
|
}
|
|
|
|
|
|
|
|
return r>=0 ? ret : r;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int FUNC_NAME(pf_handle_string)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ctx,
|
2015-11-03 19:40:38 +01:00
|
|
|
const void *str, int len, FUNC_NAME(pf_flags) *flags, MSVCRT_pthreadlocinfo locinfo, BOOL legacy_wide)
|
2011-04-20 14:42:16 +02:00
|
|
|
{
|
2016-08-08 14:43:04 +02:00
|
|
|
BOOL api_is_wide = sizeof(APICHAR) == sizeof(MSVCRT_wchar_t);
|
|
|
|
BOOL complement_is_narrow = legacy_wide ? api_is_wide : FALSE;
|
2011-04-20 14:42:16 +02:00
|
|
|
#ifdef PRINTF_WIDE
|
|
|
|
static const MSVCRT_wchar_t nullW[] = {'(','n','u','l','l',')',0};
|
|
|
|
|
|
|
|
if(!str)
|
2011-05-24 17:22:32 +02:00
|
|
|
return FUNC_NAME(pf_output_format_wstr)(pf_puts, puts_ctx, nullW, 6, flags, locinfo);
|
2011-04-20 14:42:16 +02:00
|
|
|
#else
|
|
|
|
if(!str)
|
2011-05-24 17:22:32 +02:00
|
|
|
return FUNC_NAME(pf_output_format_str)(pf_puts, puts_ctx, "(null)", 6, flags, locinfo);
|
2011-04-20 14:42:16 +02:00
|
|
|
#endif
|
|
|
|
|
2016-08-08 14:43:04 +02:00
|
|
|
if((flags->NaturalString && api_is_wide) || flags->WideString || flags->IntegerLength=='l')
|
2011-05-24 17:22:32 +02:00
|
|
|
return FUNC_NAME(pf_output_format_wstr)(pf_puts, puts_ctx, str, len, flags, locinfo);
|
2016-08-08 14:43:04 +02:00
|
|
|
if((flags->NaturalString && !api_is_wide) || flags->IntegerLength == 'h')
|
2011-05-24 17:22:32 +02:00
|
|
|
return FUNC_NAME(pf_output_format_str)(pf_puts, puts_ctx, str, len, flags, locinfo);
|
2011-04-20 14:42:16 +02:00
|
|
|
|
2015-11-03 19:40:38 +01:00
|
|
|
if((flags->Format=='S' || flags->Format=='C') == complement_is_narrow)
|
2011-05-24 17:22:32 +02:00
|
|
|
return FUNC_NAME(pf_output_format_str)(pf_puts, puts_ctx, str, len, flags, locinfo);
|
2011-04-20 14:42:16 +02:00
|
|
|
else
|
2011-05-24 17:22:32 +02:00
|
|
|
return FUNC_NAME(pf_output_format_wstr)(pf_puts, puts_ctx, str, len, flags, locinfo);
|
2011-04-20 14:42:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline void FUNC_NAME(pf_rebuild_format_string)(char *p, FUNC_NAME(pf_flags) *flags)
|
|
|
|
{
|
|
|
|
*p++ = '%';
|
|
|
|
if(flags->Alternate)
|
|
|
|
*p++ = flags->Alternate;
|
|
|
|
if(flags->Precision >= 0) {
|
2017-02-19 15:17:57 +01:00
|
|
|
p += sprintf(p, ".%d", flags->Precision);
|
2011-04-20 14:42:16 +02:00
|
|
|
}
|
|
|
|
*p++ = flags->Format;
|
|
|
|
*p++ = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* pf_integer_conv: prints x to buf, including alternate formats and
|
|
|
|
additional precision digits, but not field characters or the sign */
|
2011-04-27 12:09:51 +02:00
|
|
|
static inline void FUNC_NAME(pf_integer_conv)(APICHAR *buf, int buf_len,
|
2011-04-20 14:42:16 +02:00
|
|
|
FUNC_NAME(pf_flags) *flags, LONGLONG x)
|
|
|
|
{
|
|
|
|
unsigned int base;
|
|
|
|
const char *digits;
|
|
|
|
int i, j, k;
|
|
|
|
|
|
|
|
if(flags->Format == 'o')
|
|
|
|
base = 8;
|
|
|
|
else if(flags->Format=='x' || flags->Format=='X')
|
|
|
|
base = 16;
|
|
|
|
else
|
|
|
|
base = 10;
|
|
|
|
|
|
|
|
if(flags->Format == 'X')
|
|
|
|
digits = "0123456789ABCDEFX";
|
|
|
|
else
|
|
|
|
digits = "0123456789abcdefx";
|
|
|
|
|
|
|
|
if(x<0 && (flags->Format=='d' || flags->Format=='i')) {
|
|
|
|
x = -x;
|
|
|
|
flags->Sign = '-';
|
|
|
|
}
|
|
|
|
|
|
|
|
i = 0;
|
2014-03-20 16:20:26 +01:00
|
|
|
if(x == 0) {
|
|
|
|
flags->Alternate = 0;
|
|
|
|
if(flags->Precision)
|
|
|
|
buf[i++] = '0';
|
|
|
|
} else {
|
2011-04-20 14:42:16 +02:00
|
|
|
while(x != 0) {
|
|
|
|
j = (ULONGLONG)x%base;
|
|
|
|
x = (ULONGLONG)x/base;
|
2011-04-27 12:09:27 +02:00
|
|
|
buf[i++] = digits[j];
|
2011-04-20 14:42:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
k = flags->Precision-i;
|
|
|
|
while(k-- > 0)
|
2011-04-27 12:09:27 +02:00
|
|
|
buf[i++] = '0';
|
2011-04-20 14:42:16 +02:00
|
|
|
if(flags->Alternate) {
|
|
|
|
if(base == 16) {
|
2011-04-27 12:09:27 +02:00
|
|
|
buf[i++] = digits[16];
|
|
|
|
buf[i++] = '0';
|
|
|
|
} else if(base==8 && buf[i-1]!='0')
|
|
|
|
buf[i++] = '0';
|
2011-04-20 14:42:16 +02:00
|
|
|
}
|
|
|
|
|
2011-04-27 12:09:51 +02:00
|
|
|
/* Adjust precision so pf_fill won't truncate the number later */
|
|
|
|
flags->Precision = i;
|
|
|
|
|
2011-04-27 12:09:27 +02:00
|
|
|
buf[i] = '\0';
|
2011-04-20 14:42:16 +02:00
|
|
|
j = 0;
|
2011-04-27 12:09:51 +02:00
|
|
|
while(--i > j) {
|
|
|
|
APICHAR tmp = buf[j];
|
2011-04-27 12:09:27 +02:00
|
|
|
buf[j] = buf[i];
|
|
|
|
buf[i] = tmp;
|
|
|
|
j++;
|
|
|
|
}
|
2011-04-20 14:42:16 +02:00
|
|
|
}
|
|
|
|
|
2015-11-03 19:40:38 +01:00
|
|
|
static inline void FUNC_NAME(pf_fixup_exponent)(char *buf, BOOL three_digit_exp)
|
2011-04-20 14:42:16 +02:00
|
|
|
{
|
|
|
|
char* tmp = buf;
|
|
|
|
|
|
|
|
while(tmp[0] && toupper(tmp[0])!='E')
|
|
|
|
tmp++;
|
|
|
|
|
|
|
|
if(tmp[0] && (tmp[1]=='+' || tmp[1]=='-') &&
|
|
|
|
isdigit(tmp[2]) && isdigit(tmp[3])) {
|
2015-11-03 19:40:38 +01:00
|
|
|
#if _MSVCR_VER >= 140
|
|
|
|
BOOL two_digit_exp = !three_digit_exp;
|
|
|
|
#else
|
2014-04-16 16:15:07 +02:00
|
|
|
BOOL two_digit_exp = (MSVCRT__get_output_format() == MSVCRT__TWO_DIGIT_EXPONENT);
|
2015-11-03 19:40:38 +01:00
|
|
|
#endif
|
2013-03-27 10:39:30 +01:00
|
|
|
|
|
|
|
tmp += 2;
|
|
|
|
if(isdigit(tmp[2])) {
|
|
|
|
if(two_digit_exp && tmp[0]=='0') {
|
|
|
|
tmp[0] = tmp[1];
|
|
|
|
tmp[1] = tmp[2];
|
|
|
|
tmp[2] = tmp[3];
|
|
|
|
}
|
2011-04-20 14:42:16 +02:00
|
|
|
|
|
|
|
return; /* Exponent already 3 digits */
|
2013-03-27 10:39:30 +01:00
|
|
|
}else if(two_digit_exp) {
|
|
|
|
return;
|
|
|
|
}
|
2011-04-20 14:42:16 +02:00
|
|
|
|
2013-03-27 10:39:30 +01:00
|
|
|
tmp[3] = tmp[2];
|
2011-04-20 14:42:16 +02:00
|
|
|
tmp[2] = tmp[1];
|
|
|
|
tmp[1] = tmp[0];
|
|
|
|
tmp[0] = '0';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int FUNC_NAME(pf_printf)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ctx, const APICHAR *fmt,
|
2015-11-03 19:40:36 +01:00
|
|
|
MSVCRT__locale_t locale, DWORD options,
|
2011-05-11 15:39:07 +02:00
|
|
|
args_clbk pf_args, void *args_ctx, __ms_va_list *valist)
|
2011-04-20 14:42:16 +02:00
|
|
|
{
|
2011-05-24 17:22:32 +02:00
|
|
|
MSVCRT_pthreadlocinfo locinfo;
|
2011-04-20 14:42:16 +02:00
|
|
|
const APICHAR *q, *p = fmt;
|
2011-04-27 12:09:51 +02:00
|
|
|
APICHAR buf[32];
|
2011-04-20 14:42:16 +02:00
|
|
|
int written = 0, pos, i;
|
|
|
|
FUNC_NAME(pf_flags) flags;
|
2015-11-03 19:40:36 +01:00
|
|
|
BOOL positional_params = options & MSVCRT_PRINTF_POSITIONAL_PARAMS;
|
|
|
|
BOOL invoke_invalid_param_handler = options & MSVCRT_PRINTF_INVOKE_INVALID_PARAM_HANDLER;
|
2015-11-03 19:40:38 +01:00
|
|
|
#if _MSVCR_VER >= 140
|
|
|
|
BOOL legacy_wide = options & UCRTBASE_PRINTF_LEGACY_WIDE_SPECIFIERS;
|
|
|
|
BOOL legacy_msvcrt_compat = options & UCRTBASE_PRINTF_LEGACY_MSVCRT_COMPATIBILITY;
|
|
|
|
BOOL three_digit_exp = options & UCRTBASE_PRINTF_LEGACY_THREE_DIGIT_EXPONENTS;
|
|
|
|
#else
|
|
|
|
BOOL legacy_wide = TRUE, legacy_msvcrt_compat = TRUE, three_digit_exp = TRUE;
|
|
|
|
#endif
|
2011-04-20 14:42:16 +02:00
|
|
|
|
|
|
|
TRACE("Format is: %s\n", FUNC_NAME(debugstr)(fmt));
|
|
|
|
|
|
|
|
if(!locale)
|
2011-05-24 17:22:32 +02:00
|
|
|
locinfo = get_locinfo();
|
|
|
|
else
|
|
|
|
locinfo = locale->locinfo;
|
2011-04-20 14:42:16 +02:00
|
|
|
|
|
|
|
while(*p) {
|
|
|
|
/* output characters before '%' */
|
|
|
|
for(q=p; *q && *q!='%'; q++);
|
|
|
|
if(p != q) {
|
|
|
|
i = pf_puts(puts_ctx, q-p, p);
|
|
|
|
if(i < 0)
|
|
|
|
return i;
|
|
|
|
|
|
|
|
written += i;
|
|
|
|
p = q;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* *p == '%' here */
|
|
|
|
p++;
|
|
|
|
|
|
|
|
/* output a single '%' character */
|
|
|
|
if(*p == '%') {
|
|
|
|
i = pf_puts(puts_ctx, 1, p++);
|
|
|
|
if(i < 0)
|
|
|
|
return i;
|
|
|
|
|
|
|
|
written += i;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check parameter position */
|
|
|
|
if(positional_params && (q = FUNC_NAME(pf_parse_int)(p, &pos)) && *q=='$')
|
|
|
|
p = q+1;
|
|
|
|
else
|
|
|
|
pos = -1;
|
|
|
|
|
|
|
|
/* parse the flags */
|
|
|
|
memset(&flags, 0, sizeof(flags));
|
|
|
|
while(*p) {
|
|
|
|
if(*p=='+' || *p==' ') {
|
|
|
|
if(flags.Sign != '+')
|
|
|
|
flags.Sign = *p;
|
|
|
|
} else if(*p == '-')
|
|
|
|
flags.LeftAlign = *p;
|
|
|
|
else if(*p == '0')
|
|
|
|
flags.PadZero = *p;
|
|
|
|
else if(*p == '#')
|
|
|
|
flags.Alternate = *p;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
|
2014-12-22 02:16:59 +01:00
|
|
|
/* parse the width */
|
2011-04-20 14:42:16 +02:00
|
|
|
if(*p == '*') {
|
|
|
|
p++;
|
|
|
|
if(positional_params && (q = FUNC_NAME(pf_parse_int)(p, &i)) && *q=='$')
|
|
|
|
p = q+1;
|
|
|
|
else
|
|
|
|
i = -1;
|
|
|
|
|
2011-05-11 15:39:07 +02:00
|
|
|
flags.FieldLength = pf_args(args_ctx, i, VT_INT, valist).get_int;
|
2011-04-20 14:42:16 +02:00
|
|
|
if(flags.FieldLength < 0) {
|
|
|
|
flags.LeftAlign = '-';
|
|
|
|
flags.FieldLength = -flags.FieldLength;
|
|
|
|
}
|
|
|
|
} else while(isdigit(*p)) {
|
|
|
|
flags.FieldLength *= 10;
|
|
|
|
flags.FieldLength += *p++ - '0';
|
|
|
|
}
|
|
|
|
|
|
|
|
/* parse the precision */
|
|
|
|
flags.Precision = -1;
|
|
|
|
if(*p == '.') {
|
|
|
|
flags.Precision = 0;
|
|
|
|
p++;
|
|
|
|
if(*p == '*') {
|
|
|
|
p++;
|
|
|
|
if(positional_params && (q = FUNC_NAME(pf_parse_int)(p, &i)) && *q=='$')
|
|
|
|
p = q+1;
|
|
|
|
else
|
|
|
|
i = -1;
|
|
|
|
|
2011-05-11 15:39:07 +02:00
|
|
|
flags.Precision = pf_args(args_ctx, i, VT_INT, valist).get_int;
|
2011-04-20 14:42:16 +02:00
|
|
|
} else while(isdigit(*p)) {
|
|
|
|
flags.Precision *= 10;
|
|
|
|
flags.Precision += *p++ - '0';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* parse argument size modifier */
|
|
|
|
while(*p) {
|
|
|
|
if(*p=='l' && *(p+1)=='l') {
|
|
|
|
flags.IntegerDouble++;
|
|
|
|
p += 2;
|
|
|
|
} else if(*p=='h' || *p=='l' || *p=='L') {
|
|
|
|
flags.IntegerLength = *p;
|
|
|
|
p++;
|
|
|
|
} else if(*p == 'I') {
|
|
|
|
if(*(p+1)=='6' && *(p+2)=='4') {
|
|
|
|
flags.IntegerDouble++;
|
|
|
|
p += 3;
|
|
|
|
} else if(*(p+1)=='3' && *(p+2)=='2')
|
|
|
|
p += 3;
|
|
|
|
else if(isdigit(*(p+1)) || !*(p+1))
|
|
|
|
break;
|
|
|
|
else
|
2015-11-03 19:40:39 +01:00
|
|
|
flags.IntegerNative = *p++;
|
2011-04-20 14:42:16 +02:00
|
|
|
} else if(*p == 'w')
|
|
|
|
flags.WideString = *p++;
|
2015-11-03 19:40:40 +01:00
|
|
|
#if _MSVCR_VER >= 140
|
|
|
|
else if(*p == 'z')
|
|
|
|
flags.IntegerNative = *p++;
|
2016-08-08 14:43:04 +02:00
|
|
|
else if(*p == 'T')
|
|
|
|
flags.NaturalString = *p++;
|
2015-11-03 19:40:40 +01:00
|
|
|
#endif
|
2015-11-03 19:40:38 +01:00
|
|
|
else if((*p == 'F' || *p == 'N') && legacy_msvcrt_compat)
|
2011-04-20 14:42:16 +02:00
|
|
|
p++; /* ignore */
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
flags.Format = *p;
|
|
|
|
|
|
|
|
if(flags.Format == 's' || flags.Format == 'S') {
|
|
|
|
i = FUNC_NAME(pf_handle_string)(pf_puts, puts_ctx,
|
2011-05-11 15:39:07 +02:00
|
|
|
pf_args(args_ctx, pos, VT_PTR, valist).get_ptr,
|
2015-11-03 19:40:38 +01:00
|
|
|
-1, &flags, locinfo, legacy_wide);
|
2011-04-20 14:42:16 +02:00
|
|
|
} else if(flags.Format == 'c' || flags.Format == 'C') {
|
2011-05-11 15:39:07 +02:00
|
|
|
int ch = pf_args(args_ctx, pos, VT_INT, valist).get_int;
|
2011-04-20 14:42:16 +02:00
|
|
|
|
2015-11-03 19:40:38 +01:00
|
|
|
i = FUNC_NAME(pf_handle_string)(pf_puts, puts_ctx, &ch, 1, &flags, locinfo, legacy_wide);
|
2011-04-20 14:42:16 +02:00
|
|
|
} else if(flags.Format == 'p') {
|
2011-04-27 12:09:37 +02:00
|
|
|
flags.Format = 'X';
|
|
|
|
flags.PadZero = '0';
|
|
|
|
i = flags.Precision;
|
|
|
|
flags.Precision = 2*sizeof(void*);
|
2011-04-27 12:09:51 +02:00
|
|
|
FUNC_NAME(pf_integer_conv)(buf, sizeof(buf)/sizeof(APICHAR), &flags,
|
2011-12-21 16:05:04 +01:00
|
|
|
(ULONG_PTR)pf_args(args_ctx, pos, VT_PTR, valist).get_ptr);
|
2011-04-20 14:42:16 +02:00
|
|
|
flags.PadZero = 0;
|
2011-04-27 12:09:37 +02:00
|
|
|
flags.Precision = i;
|
2011-04-20 14:42:16 +02:00
|
|
|
|
2011-04-27 12:09:51 +02:00
|
|
|
#ifdef PRINTF_WIDE
|
2011-05-24 17:22:32 +02:00
|
|
|
i = FUNC_NAME(pf_output_format_wstr)(pf_puts, puts_ctx, buf, -1, &flags, locinfo);
|
2011-04-27 12:09:51 +02:00
|
|
|
#else
|
2011-05-24 17:22:32 +02:00
|
|
|
i = FUNC_NAME(pf_output_format_str)(pf_puts, puts_ctx, buf, -1, &flags, locinfo);
|
2011-04-27 12:09:51 +02:00
|
|
|
#endif
|
2011-04-20 14:42:16 +02:00
|
|
|
} else if(flags.Format == 'n') {
|
2011-04-27 20:09:00 +02:00
|
|
|
int *used;
|
|
|
|
|
|
|
|
if(!n_format_enabled) {
|
2012-07-26 11:05:32 +02:00
|
|
|
MSVCRT_INVALID_PMT("\'n\' format specifier disabled", MSVCRT_EINVAL);
|
2011-04-27 20:09:00 +02:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-05-11 15:39:07 +02:00
|
|
|
used = pf_args(args_ctx, pos, VT_PTR, valist).get_ptr;
|
2011-04-20 14:42:16 +02:00
|
|
|
*used = written;
|
|
|
|
i = 0;
|
2011-04-27 12:09:17 +02:00
|
|
|
} else if(flags.Format && strchr("diouxX", flags.Format)) {
|
2011-04-27 12:09:51 +02:00
|
|
|
APICHAR *tmp = buf;
|
2012-03-12 19:49:11 +01:00
|
|
|
int max_len;
|
2011-04-20 14:42:16 +02:00
|
|
|
|
2012-03-18 19:19:01 +01:00
|
|
|
/* 0 padding is added after '0x' if Alternate flag is in use */
|
2012-03-12 19:49:11 +01:00
|
|
|
if((flags.Format=='x' || flags.Format=='X') && flags.PadZero && flags.Alternate
|
|
|
|
&& !flags.LeftAlign && flags.Precision<flags.FieldLength-2)
|
|
|
|
flags.Precision = flags.FieldLength - 2;
|
|
|
|
|
|
|
|
max_len = (flags.FieldLength>flags.Precision ? flags.FieldLength : flags.Precision) + 10;
|
2011-04-27 12:09:51 +02:00
|
|
|
if(max_len > sizeof(buf)/sizeof(APICHAR))
|
2011-04-20 14:42:16 +02:00
|
|
|
tmp = HeapAlloc(GetProcessHeap(), 0, max_len);
|
|
|
|
if(!tmp)
|
|
|
|
return -1;
|
|
|
|
|
2015-11-03 19:40:39 +01:00
|
|
|
if(flags.IntegerDouble || (flags.IntegerNative && sizeof(void*) == 8))
|
2011-04-27 12:09:17 +02:00
|
|
|
FUNC_NAME(pf_integer_conv)(tmp, max_len, &flags, pf_args(args_ctx, pos,
|
2011-05-11 15:39:07 +02:00
|
|
|
VT_I8, valist).get_longlong);
|
2011-04-27 12:09:17 +02:00
|
|
|
else if(flags.Format=='d' || flags.Format=='i')
|
2012-04-16 15:21:47 +02:00
|
|
|
FUNC_NAME(pf_integer_conv)(tmp, max_len, &flags, flags.IntegerLength!='h' ?
|
|
|
|
pf_args(args_ctx, pos, VT_INT, valist).get_int :
|
|
|
|
(short)pf_args(args_ctx, pos, VT_INT, valist).get_int);
|
2011-04-27 12:09:17 +02:00
|
|
|
else
|
2012-04-16 15:21:47 +02:00
|
|
|
FUNC_NAME(pf_integer_conv)(tmp, max_len, &flags, flags.IntegerLength!='h' ?
|
|
|
|
(unsigned)pf_args(args_ctx, pos, VT_INT, valist).get_int :
|
|
|
|
(unsigned short)pf_args(args_ctx, pos, VT_INT, valist).get_int);
|
2011-04-20 14:42:16 +02:00
|
|
|
|
2011-04-27 12:09:51 +02:00
|
|
|
#ifdef PRINTF_WIDE
|
2011-05-24 17:22:32 +02:00
|
|
|
i = FUNC_NAME(pf_output_format_wstr)(pf_puts, puts_ctx, tmp, -1, &flags, locinfo);
|
2011-04-27 12:09:51 +02:00
|
|
|
#else
|
2011-05-24 17:22:32 +02:00
|
|
|
i = FUNC_NAME(pf_output_format_str)(pf_puts, puts_ctx, tmp, -1, &flags, locinfo);
|
2011-04-27 12:09:51 +02:00
|
|
|
#endif
|
2011-04-20 14:42:16 +02:00
|
|
|
if(tmp != buf)
|
|
|
|
HeapFree(GetProcessHeap(), 0, tmp);
|
2015-11-03 19:40:38 +01:00
|
|
|
} else if(flags.Format && strchr("aeEfFgG", flags.Format)) {
|
2011-06-14 13:06:08 +02:00
|
|
|
char float_fmt[20], buf_a[32], *tmp = buf_a, *decimal_point;
|
2012-12-12 11:15:12 +01:00
|
|
|
int len = flags.Precision + 10;
|
2011-07-26 12:41:46 +02:00
|
|
|
double val = pf_args(args_ctx, pos, VT_R8, valist).get_double;
|
2012-12-12 11:15:12 +01:00
|
|
|
int r;
|
2015-03-25 14:22:02 +01:00
|
|
|
BOOL inf = FALSE, nan = FALSE, ind = FALSE;
|
2013-09-16 15:05:16 +02:00
|
|
|
|
|
|
|
if(isinf(val))
|
|
|
|
inf = TRUE;
|
2015-03-25 14:22:02 +01:00
|
|
|
else if(isnan(val)) {
|
|
|
|
if(!signbit(val))
|
|
|
|
nan = TRUE;
|
|
|
|
else
|
|
|
|
ind = TRUE;
|
|
|
|
}
|
2013-09-16 15:05:16 +02:00
|
|
|
|
2015-03-25 14:22:02 +01:00
|
|
|
if(inf || nan || ind) {
|
|
|
|
if(ind || val<0)
|
2013-09-16 15:05:16 +02:00
|
|
|
flags.Sign = '-';
|
|
|
|
|
|
|
|
if(flags.Format=='g' || flags.Format=='G')
|
2015-03-25 14:22:02 +01:00
|
|
|
val = (nan ? 1.12345 : 1.1234); /* fraction will be overwritten with #INF, #IND or #QNAN string */
|
2013-09-16 15:05:16 +02:00
|
|
|
else
|
|
|
|
val = 1;
|
|
|
|
|
|
|
|
if(flags.Format=='a') {
|
|
|
|
if(flags.Precision==-1)
|
|
|
|
flags.Precision = 6; /* strlen("#INF00") */
|
|
|
|
}
|
|
|
|
}
|
2011-07-26 12:41:46 +02:00
|
|
|
|
2015-11-03 19:40:38 +01:00
|
|
|
if(flags.Format=='f' || flags.Format=='F') {
|
2011-07-26 12:41:46 +02:00
|
|
|
if(val>-10.0 && val<10.0)
|
|
|
|
i = 1;
|
|
|
|
else
|
|
|
|
i = 1 + log10(val<0 ? -val : val);
|
|
|
|
/* Default precision is 6, additional space for sign, separator and nullbyte is required */
|
|
|
|
i += (flags.Precision==-1 ? 6 : flags.Precision) + 3;
|
|
|
|
|
2012-12-12 11:15:12 +01:00
|
|
|
if(i > len)
|
|
|
|
len = i;
|
2011-07-26 12:41:46 +02:00
|
|
|
}
|
2011-04-20 14:42:16 +02:00
|
|
|
|
2012-12-12 11:15:12 +01:00
|
|
|
if(len > sizeof(buf_a))
|
|
|
|
tmp = HeapAlloc(GetProcessHeap(), 0, len);
|
2011-04-20 14:42:16 +02:00
|
|
|
if(!tmp)
|
|
|
|
return -1;
|
|
|
|
|
2011-06-14 13:06:08 +02:00
|
|
|
FUNC_NAME(pf_rebuild_format_string)(float_fmt, &flags);
|
2012-12-12 11:15:12 +01:00
|
|
|
if(val < 0) {
|
|
|
|
flags.Sign = '-';
|
|
|
|
val = -val;
|
|
|
|
}
|
2011-04-20 14:42:16 +02:00
|
|
|
|
2015-11-03 19:40:38 +01:00
|
|
|
if((inf || nan || ind) && !legacy_msvcrt_compat) {
|
|
|
|
static const char inf_str[] = "inf";
|
|
|
|
static const char ind_str[] = "nan(ind)";
|
|
|
|
static const char nan_str[] = "nan";
|
|
|
|
if(inf)
|
|
|
|
sprintf(tmp, inf_str);
|
|
|
|
else if(ind)
|
|
|
|
sprintf(tmp, ind_str);
|
|
|
|
else
|
|
|
|
sprintf(tmp, nan_str);
|
|
|
|
if (strchr("EFG", flags.Format))
|
|
|
|
for(i=0; tmp[i]; i++)
|
|
|
|
tmp[i] = toupper(tmp[i]);
|
|
|
|
} else {
|
|
|
|
sprintf(tmp, float_fmt, val);
|
|
|
|
if(toupper(flags.Format)=='E' || toupper(flags.Format)=='G')
|
|
|
|
FUNC_NAME(pf_fixup_exponent)(tmp, three_digit_exp);
|
|
|
|
}
|
2011-04-20 14:42:16 +02:00
|
|
|
|
|
|
|
decimal_point = strchr(tmp, '.');
|
2013-09-16 15:05:16 +02:00
|
|
|
if(decimal_point) {
|
2011-05-24 17:22:32 +02:00
|
|
|
*decimal_point = *locinfo->lconv->decimal_point;
|
2011-04-20 14:42:16 +02:00
|
|
|
|
2015-03-25 14:22:02 +01:00
|
|
|
if(inf || nan || ind) {
|
2013-09-16 15:05:16 +02:00
|
|
|
static const char inf_str[] = "#INF";
|
|
|
|
static const char ind_str[] = "#IND";
|
2015-03-25 14:22:02 +01:00
|
|
|
static const char nan_str[] = "#QNAN";
|
|
|
|
|
|
|
|
const char *str;
|
|
|
|
int size;
|
|
|
|
|
|
|
|
if(inf) {
|
|
|
|
str = inf_str;
|
|
|
|
size = sizeof(inf_str);
|
|
|
|
}else if(ind) {
|
|
|
|
str = ind_str;
|
|
|
|
size = sizeof(ind_str);
|
|
|
|
}else {
|
|
|
|
str = nan_str;
|
|
|
|
size = sizeof(nan_str);
|
|
|
|
}
|
2013-09-16 15:05:16 +02:00
|
|
|
|
2015-03-25 14:22:02 +01:00
|
|
|
for(i=1; i<size; i++) {
|
2013-09-16 15:05:16 +02:00
|
|
|
if(decimal_point[i]<'0' || decimal_point[i]>'9')
|
|
|
|
break;
|
|
|
|
|
2015-03-25 14:22:02 +01:00
|
|
|
decimal_point[i] = str[i-1];
|
2013-09-16 15:05:16 +02:00
|
|
|
}
|
|
|
|
|
2015-03-25 14:22:02 +01:00
|
|
|
if(i!=size && i!=1)
|
2013-09-16 15:05:16 +02:00
|
|
|
decimal_point[i-1]++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-12 11:15:12 +01:00
|
|
|
len = strlen(tmp);
|
|
|
|
i = FUNC_NAME(pf_fill)(pf_puts, puts_ctx, len, &flags, TRUE);
|
|
|
|
if(i < 0)
|
|
|
|
return i;
|
|
|
|
r = FUNC_NAME(pf_output_str)(pf_puts, puts_ctx, tmp, len, locinfo);
|
|
|
|
if(r < 0)
|
|
|
|
return r;
|
|
|
|
i += r;
|
2011-04-27 12:09:51 +02:00
|
|
|
if(tmp != buf_a)
|
2011-04-20 14:42:16 +02:00
|
|
|
HeapFree(GetProcessHeap(), 0, tmp);
|
2012-12-12 11:15:12 +01:00
|
|
|
r = FUNC_NAME(pf_fill)(pf_puts, puts_ctx, len, &flags, FALSE);
|
|
|
|
if(r < 0)
|
|
|
|
return r;
|
|
|
|
i += r;
|
2011-04-20 14:42:16 +02:00
|
|
|
} else {
|
|
|
|
if(invoke_invalid_param_handler) {
|
|
|
|
MSVCRT__invalid_parameter(NULL, NULL, NULL, 0, 0);
|
|
|
|
*MSVCRT__errno() = MSVCRT_EINVAL;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(i < 0)
|
|
|
|
return i;
|
|
|
|
written += i;
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return written;
|
|
|
|
}
|
|
|
|
|
2011-04-27 12:09:01 +02:00
|
|
|
#ifndef PRINTF_WIDE
|
|
|
|
enum types_clbk_flags {
|
|
|
|
TYPE_CLBK_VA_LIST = 1,
|
|
|
|
TYPE_CLBK_POSITIONAL = 2,
|
|
|
|
TYPE_CLBK_ERROR_POS = 4,
|
|
|
|
TYPE_CLBK_ERROR_TYPE = 8
|
|
|
|
};
|
|
|
|
|
|
|
|
/* This functions stores types of arguments. It uses args[0] internally */
|
|
|
|
static printf_arg arg_clbk_type(void *ctx, int pos, int type, __ms_va_list *valist)
|
|
|
|
{
|
|
|
|
static const printf_arg ret;
|
|
|
|
printf_arg *args = ctx;
|
|
|
|
|
|
|
|
if(pos == -1) {
|
|
|
|
args[0].get_int |= TYPE_CLBK_VA_LIST;
|
|
|
|
return ret;
|
|
|
|
} else
|
|
|
|
args[0].get_int |= TYPE_CLBK_POSITIONAL;
|
|
|
|
|
|
|
|
if(pos<1 || pos>MSVCRT__ARGMAX)
|
|
|
|
args[0].get_int |= TYPE_CLBK_ERROR_POS;
|
|
|
|
else if(args[pos].get_int && args[pos].get_int!=type)
|
|
|
|
args[0].get_int |= TYPE_CLBK_ERROR_TYPE;
|
|
|
|
else
|
|
|
|
args[pos].get_int = type;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static int FUNC_NAME(create_positional_ctx)(void *args_ctx, const APICHAR *format, __ms_va_list valist)
|
|
|
|
{
|
|
|
|
struct FUNC_NAME(_str_ctx) puts_ctx = {INT_MAX, NULL};
|
|
|
|
printf_arg *args = args_ctx;
|
|
|
|
int i, j;
|
|
|
|
|
2015-11-03 19:40:36 +01:00
|
|
|
i = FUNC_NAME(pf_printf)(FUNC_NAME(puts_clbk_str), &puts_ctx, format, NULL,
|
|
|
|
MSVCRT_PRINTF_POSITIONAL_PARAMS, arg_clbk_type, args_ctx, NULL);
|
2011-04-27 12:09:01 +02:00
|
|
|
if(i < 0)
|
|
|
|
return i;
|
|
|
|
|
|
|
|
if(args[0].get_int==0 || args[0].get_int==TYPE_CLBK_VA_LIST)
|
|
|
|
return 0;
|
|
|
|
if(args[0].get_int != TYPE_CLBK_POSITIONAL)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
for(i=MSVCRT__ARGMAX; i>0; i--)
|
|
|
|
if(args[i].get_int)
|
|
|
|
break;
|
|
|
|
|
|
|
|
for(j=1; j<=i; j++) {
|
|
|
|
switch(args[j].get_int) {
|
|
|
|
case VT_I8:
|
|
|
|
args[j].get_longlong = va_arg(valist, LONGLONG);
|
|
|
|
break;
|
|
|
|
case VT_INT:
|
|
|
|
args[j].get_int = va_arg(valist, int);
|
|
|
|
break;
|
|
|
|
case VT_R8:
|
|
|
|
args[j].get_double = va_arg(valist, double);
|
|
|
|
break;
|
|
|
|
case VT_PTR:
|
|
|
|
args[j].get_ptr = va_arg(valist, void*);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return j;
|
|
|
|
}
|
|
|
|
|
2011-04-20 14:42:16 +02:00
|
|
|
#undef APICHAR
|
|
|
|
#undef CONVCHAR
|
|
|
|
#undef FUNC_NAME
|