/* This file is part of Mailfromd. -*- c -*- Copyright (C) 2007, 2008 Sergey Poznyakoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #define FMT_ALTPOS 0x01 #define FMT_ALTERNATE 0x02 #define FMT_PADZERO 0x04 #define FMT_ADJUST_LEFT 0x08 #define FMT_SPACEPFX 0x10 #define FMT_SIGNPFX 0x20 typedef enum { fmts_copy, /* Copy char as is */ fmts_pos, /* Expect argument position -- %_2$ */ fmts_flags, /* Expect flags -- %2$_# */ fmts_width, /* Expect width -- %2$#_8 or %2$#_* */ fmts_width_arg, /* Expect width argument position -- %2$#*_1$ */ fmts_prec, /* Expect precision */ fmts_prec_arg, /* Expect precision argument position */ fmts_conv /* Expect conversion specifier */ } printf_format_state; char * get_num(char *p, unsigned *pn) { unsigned n = 0; for (; *p && isdigit(*p); p++) n = n * 10 + *p - '0'; *pn = n; return p; } #define __MF_MAX(a,b) ((a)>(b) ? (a) : (b)) #define SPRINTF_BUF_SIZE (__MF_MAX(3*sizeof(long), NUMERIC_BUFSIZE_BOUND) + 2) MF_DEFUN_VARARGS(sprintf, STRING, STRING format) { int i = 0; char *p = format; char *start; char buf[SPRINTF_BUF_SIZE]; printf_format_state state = fmts_copy; int flags = 0; unsigned width = 0; unsigned prec = 0; unsigned argnum; MF_OBSTACK_BEGIN(); MF_VA_START(); while (*p) { unsigned n; char *str; long num; int negative; char fmtbuf[] = { '%', 'x', 0 }; switch (state) { case fmts_copy: /* Expect `%', and copy all the rest verbatim */ if (*p == '%') { start = p; state = fmts_pos; flags = 0; width = 0; prec = 0; } else MF_OBSTACK_1GROW(*p); p++; break; case fmts_pos: /* Expect '%' or an argument position -- %_% or %_2$ */ if (*p == '%') { MF_OBSTACK_1GROW('%'); p++; state = fmts_copy; break; } if (isdigit(*p)) { char *q = get_num(p, &n); if (*q == '$') { argnum = n - 1; flags |= FMT_ALTPOS; p = q + 1; } } state = fmts_flags; break; case fmts_flags: /* Expect flags -- %2$_# */ switch (*p) { case '#': flags |= FMT_ALTERNATE; p++; break; case '0': flags |= FMT_PADZERO; p++; break; case '-': flags |= FMT_ADJUST_LEFT; p++; break; case ' ': flags |= FMT_SPACEPFX; p++; break; case '+': flags |= FMT_SIGNPFX; p++; break; default: state = fmts_width; } break; case fmts_width: /* Expect width -- %2$#_8 or %2$#_* */ if (isdigit(*p)) { p = get_num(p, &width); state = fmts_prec; } else if (*p == '*') { p++; state = fmts_width_arg; } else state = fmts_prec; break; case fmts_width_arg: /* Expect width argument position -- %2$#*_1$ */ state = fmts_prec; if (isdigit(*p)) { char *q = get_num(p, &n); if (*q == '$') { num = (unsigned) MF_VA_ARG(n-1, NUMBER); p = q + 1; if (num < 0) { flags |= FMT_SPACEPFX; num = - num; } width = (unsigned) num; break; } } num = MF_VA_ARG(i, NUMBER); i++; if (num < 0) { /* A negative field width is taken as a `-' flag followed by a positive field width. */ flags |= FMT_SPACEPFX; num = - num; } width = (unsigned) num; break; case fmts_prec: /* Expect precision -- %2$#*1$_. */ state = fmts_conv; if (*p == '.') { p++; if (isdigit(*p)) { p = get_num(p, &prec); } else if (*p == '*') { p++; state = fmts_prec_arg; } } break; case fmts_prec_arg: /* Expect precision argument position -- %2$#*1$.*_3$ */ state = fmts_conv; if (isdigit(*p)) { char *q = get_num(p, &n); if (*q == '$') { num = MF_VA_ARG(n-1, NUMBER); if (num > 0) prec = (unsigned) num; p = q + 1; break; } } num = MF_VA_ARG(i, NUMBER); i++; if (num > 0) prec = (unsigned) num; break; case fmts_conv: /* Expect conversion specifier */ if (!(flags & FMT_ALTPOS)) argnum = i++; switch (*p) { case 's': str = MF_VA_ARG(argnum,STRING); n = strlen(str); if (prec && prec < n) n = prec; if (width) { char *q, *s; if (n > width) width = n; q = s = MF_ALLOC_HEAP_TEMP(width + 1); q[width] = 0; memset(q, ' ', width); if (!(flags & FMT_ADJUST_LEFT) && n < width) { s = q + width - n; } memcpy(s, str, n); str = q; n = width; } MF_OBSTACK_GROW(str, n); break; case 'i': case 'd': num = MF_VA_ARG(argnum,NUMBER); if (num < 0) { negative = 1; num = - num; } else negative = 0; /* If a precision is given with a numeric conversion, the 0 flag is ignored. */ /* A - overrides a 0 if both are given. */ if (prec || (flags & FMT_ADJUST_LEFT)) flags &= ~FMT_PADZERO; snprintf(buf+1, sizeof(buf)-1, "%ld", num); str = buf + 1; n = strlen(str); if (prec && prec > n) { memmove(str + prec - n, str, n + 1); memset(str, '0', prec - n); } if (flags & FMT_SIGNPFX) { buf[0] = negative ? '-' : '+'; str = buf; } else if (flags & FMT_SPACEPFX) { buf[0] = negative ? '-' : ' '; str = buf; } else if (negative) { buf[0] = '-'; str = buf; } else str = buf + 1; n = strlen(str); if (width && width > n) { char *q; MF_OBSTACK_GROW(NULL, width, q); memset(q, (flags & FMT_PADZERO) ? '0' : ' ', width); if (flags & FMT_ADJUST_LEFT) memcpy(q, str, n); else { if ((flags & FMT_PADZERO) && str == buf) { q[0] = *str++; n--; } memcpy(q + width - n, str, n); } } else MF_OBSTACK_GROW(str, n); break; case 'u': num = MF_VA_ARG(argnum,NUMBER); /* If a precision is given with a numeric conversion, the 0 flag is ignored. */ /* A - overrides a 0 if both are given.*/ if (prec || (flags & FMT_ADJUST_LEFT)) flags &= ~FMT_PADZERO; snprintf(buf, sizeof(buf), "%lu", num); str = buf; n = strlen(str); if (prec && prec > n) { memmove(str + prec - n, str, n + 1); memset(str, '0', prec - n); n = prec; } if (width && width > n) { char *q; MF_OBSTACK_GROW(NULL, width, q); memset(q, (flags & FMT_PADZERO) ? '0' : ' ', width); if (flags & FMT_ADJUST_LEFT) memcpy(q, str, n); else memcpy(q + width - n, str, n); } else MF_OBSTACK_GROW(str, n); break; case 'x': case 'X': num = MF_VA_ARG(argnum,NUMBER); /* If a precision is given with a numeric conversion, the 0 flag is ignored. */ /* A - overrides a 0 if both are given.*/ if (prec || (flags & FMT_ADJUST_LEFT)) flags &= ~FMT_PADZERO; fmtbuf[1] = *p; snprintf(buf+2, sizeof(buf)-2, fmtbuf, num); str = buf + 2; n = strlen(str); if (prec && prec > n) { memmove(str + prec - n, str, n + 1); memset(str, '0', prec - n); n = prec; } if (flags & FMT_ALTERNATE) { *--str = *p; *--str = '0'; n += 2; } if (width && width > n) { char *q; MF_OBSTACK_GROW(NULL, width, q); memset(q, (flags & FMT_PADZERO) ? '0' : ' ', width); if (flags & FMT_ADJUST_LEFT) memcpy(q, str, n); else { if (flags & FMT_ALTERNATE && flags & FMT_PADZERO) { q[0] = *str++; q[1] = *str++; n -= 2; } memcpy(q + width - n, str, n); } } else MF_OBSTACK_GROW(str, n); break; case 'o': num = MF_VA_ARG(argnum,NUMBER); /* If a precision is given with a numeric conversion, the 0 flag is ignored. */ /* A - overrides a 0 if both are given.*/ if (prec || (flags & FMT_ADJUST_LEFT)) flags &= ~FMT_PADZERO; snprintf(buf+1, sizeof(buf)-1, "%lo", num); str = buf + 2; n = strlen(str); if (prec && prec > n) { memmove(str + prec - n, str, n + 1); memset(str, '0', prec - n); } if ((flags & FMT_ALTERNATE) && *str != '0') { *--str = '0'; n++; } if (width && width > n) { char *q; MF_OBSTACK_GROW(NULL, width, q); memset(q, (flags & FMT_PADZERO) ? '0' : ' ', width); if (flags & FMT_ADJUST_LEFT) memcpy(q, str, n); else memcpy(q + width - n, str, n); } else MF_OBSTACK_GROW(str, n); break; default: MF_OBSTACK_GROW(start, p - start + 1); } p++; state = fmts_copy; } } MF_OBSTACK_1GROW(0); MF_VA_END(); MF_RETURN_OBSTACK(); } END MF_INIT