1/* This file is part of Mailfromd. -*- c -*- 2 Copyright (C) 2007-2021 Sergey Poznyakoff 3 4 This program is free software; you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation; either version 3, or (at your option) 7 any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 GNU General Public License for more details. 13 14 You should have received a copy of the GNU General Public License 15 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 16 17MF_BUILTIN_MODULE 18 19#define FMT_ALTPOS 0x01 20#define FMT_ALTERNATE 0x02 21#define FMT_PADZERO 0x04 22#define FMT_ADJUST_LEFT 0x08 23#define FMT_SPACEPFX 0x10 24#define FMT_SIGNPFX 0x20 25 26typedef enum { 27 fmts_copy, /* Copy char as is */ 28 fmts_pos, /* Expect argument position -- %_2$ */ 29 fmts_flags, /* Expect flags -- %2$_# */ 30 fmts_width, /* Expect width -- %2$#_8 or %2$#_* */ 31 fmts_width_arg, /* Expect width argument position -- %2$#*_1$ */ 32 fmts_prec, /* Expect precision */ 33 fmts_prec_arg, /* Expect precision argument position */ 34 fmts_conv /* Expect conversion specifier */ 35} printf_format_state; 36 37static int 38get_num(const char *p, int i, unsigned *pn) 39{ 40 unsigned n = 0; 41 42 for (; p[i] && mu_isdigit(p[i]); i++) 43 n = n * 10 + p[i] - '0'; 44 *pn = n; 45 return i; 46} 47 48#define __MF_MAX(a,b) ((a)>(b) ? (a) : (b)) 49#define SPRINTF_BUF_SIZE (__MF_MAX(3*sizeof(long), NUMERIC_BUFSIZE_BOUND) + 2) 50 51MF_DEFUN_VARARGS_NO_PROM(sprintf, STRING, STRING format) 52{ 53 int i = 0; 54 int cur = 0; 55 int start; 56 char buf[SPRINTF_BUF_SIZE]; 57 printf_format_state state = fmts_copy; 58 int flags = 0; 59 unsigned width = 0; 60 unsigned prec = 0; 61 unsigned argnum; 62 63 MF_OBSTACK_BEGIN(); 64 MF_VA_START(); 65 while (format[cur]) { 66 unsigned n; 67 char *str; 68 long num; 69 int negative; 70 char fmtbuf[] = { '%', 'x', 0 }; 71 72 switch (state) { 73 case fmts_copy: 74 /* Expect `%', and copy all the rest verbatim */ 75 if (format[cur] == '%') { 76 start = cur; 77 state = fmts_pos; 78 flags = 0; 79 width = 0; 80 prec = 0; 81 } else 82 MF_OBSTACK_1GROW(format[cur]); 83 cur++; 84 break; 85 86 case fmts_pos: 87 /* Expect '%' or an argument position -- %_% or %_2$ */ 88 if (format[cur] == '%') { 89 MF_OBSTACK_1GROW('%'); 90 cur++; 91 state = fmts_copy; 92 break; 93 } 94 if (mu_isdigit(format[cur])) { 95 int pos = get_num(format, cur, &n); 96 if (format[pos] == '$') { 97 argnum = n - 1; 98 flags |= FMT_ALTPOS; 99 cur = pos + 1; 100 } 101 } 102 state = fmts_flags; 103 break; 104 105 case fmts_flags: 106 /* Expect flags -- %2$_# */ 107 switch (format[cur]) { 108 case '#': 109 flags |= FMT_ALTERNATE; 110 cur++; 111 break; 112 113 case '0': 114 flags |= FMT_PADZERO; 115 cur++; 116 break; 117 118 case '-': 119 flags |= FMT_ADJUST_LEFT; 120 cur++; 121 break; 122 123 case ' ': 124 flags |= FMT_SPACEPFX; 125 cur++; 126 break; 127 128 case '+': 129 flags |= FMT_SIGNPFX; 130 cur++; 131 break; 132 133 default: 134 state = fmts_width; 135 } 136 break; 137 138 case fmts_width: 139 /* Expect width -- %2$#_8 or %2$#_* */ 140 if (mu_isdigit(format[cur])) { 141 cur = get_num(format, cur, &width); 142 state = fmts_prec; 143 } else if (format[cur] == '*') { 144 cur++; 145 state = fmts_width_arg; 146 } else 147 state = fmts_prec; 148 break; 149 150 case fmts_width_arg: 151 /* Expect width argument position -- %2$#*_1$ */ 152 state = fmts_prec; 153 if (mu_isdigit(format[cur])) { 154 int pos = get_num(format, cur, &n); 155 if (format[pos] == '$') { 156 MF_VA_ARG(n-1, NUMBER, num); 157 cur = pos + 1; 158 if (num < 0) { 159 flags |= FMT_SPACEPFX; 160 num = - num; 161 } 162 width = (unsigned) num; 163 break; 164 } 165 } 166 MF_VA_ARG(i, NUMBER, num); 167 i++; 168 if (num < 0) { 169 /* A negative field width is taken 170 as a `-' flag followed by a positive field width. */ 171 flags |= FMT_SPACEPFX; 172 num = - num; 173 } 174 width = (unsigned) num; 175 break; 176 177 case fmts_prec: 178 /* Expect precision -- %2$#*1$_. */ 179 state = fmts_conv; 180 if (format[cur] == '.') { 181 cur++; 182 if (mu_isdigit(format[cur])) { 183 cur = get_num(format, cur, &prec); 184 } else if (format[cur] == '*') { 185 cur++; 186 state = fmts_prec_arg; 187 } 188 } 189 break; 190 191 case fmts_prec_arg: 192 /* Expect precision argument position -- 193 %2$#*1$.*_3$ */ 194 state = fmts_conv; 195 if (mu_isdigit(format[cur])) { 196 int pos = get_num(format, cur, &n); 197 if (format[pos] == '$') { 198 MF_VA_ARG(n-1, NUMBER, num); 199 if (num > 0) 200 prec = (unsigned) num; 201 cur = pos + 1; 202 break; 203 } 204 } 205 MF_VA_ARG(i, NUMBER, num); 206 i++; 207 if (num > 0) 208 prec = (unsigned) num; 209 break; 210 211 case fmts_conv: /* Expect conversion specifier */ 212 if (!(flags & FMT_ALTPOS)) 213 argnum = i++; 214 switch (format[cur]) { 215 case 's': 216 MF_VA_ARG(argnum, STRING, str); 217 n = strlen(str); 218 if (prec && prec < n) 219 n = prec; 220 if (width) { 221 char *q, *s; 222 if (n > width) 223 width = n; 224 q = s = MF_ALLOC_HEAP_TEMP(width + 1); 225 q[width] = 0; 226 memset(q, ' ', width); 227 if (!(flags & FMT_ADJUST_LEFT) 228 && n < width) { 229 s = q + width - n; 230 } 231 memcpy(s, str, n); 232 str = q; 233 n = width; 234 } 235 MF_OBSTACK_GROW(str, n); 236 break; 237 238 case 'i': 239 case 'd': 240 MF_VA_ARG(argnum, NUMBER, num); 241 if (num < 0) { 242 negative = 1; 243 num = - num; 244 } else 245 negative = 0; 246 /* If a precision is given with a 247 numeric conversion, the 0 flag is ignored. 248 */ 249 /* A - overrides a 0 if both are given. */ 250 if (prec || (flags & FMT_ADJUST_LEFT)) 251 flags &= ~FMT_PADZERO; 252 snprintf(buf+1, sizeof(buf)-1, "%ld", num); 253 str = buf + 1; 254 n = strlen(str); 255 if (prec && prec > n) { 256 memmove(str + prec - n, str, n + 1); 257 memset(str, '0', prec - n); 258 } 259 260 if (flags & FMT_SIGNPFX) { 261 buf[0] = negative ? '-' : '+'; 262 str = buf; 263 } else if (flags & FMT_SPACEPFX) { 264 buf[0] = negative ? '-' : ' '; 265 str = buf; 266 } else if (negative) { 267 buf[0] = '-'; 268 str = buf; 269 } else 270 str = buf + 1; 271 n = strlen(str); 272 273 if (width && width > n) { 274 char *q; 275 MF_OBSTACK_GROW(NULL, width, q); 276 memset(q, 277 (flags & FMT_PADZERO) ? 278 '0' : ' ', 279 width); 280 if (flags & FMT_ADJUST_LEFT) 281 memcpy(q, str, n); 282 else { 283 if ((flags & FMT_PADZERO) && 284 str == buf) { 285 q[0] = *str++; 286 n--; 287 } 288 memcpy(q + width - n, str, n); 289 } 290 } else 291 MF_OBSTACK_GROW(str, n); 292 break; 293 294 case 'u': 295 MF_VA_ARG(argnum, NUMBER, num); 296 /* If a precision is given with a 297 numeric conversion, the 0 flag is ignored. 298 */ 299 /* A - overrides a 0 if both are given.*/ 300 if (prec || (flags & FMT_ADJUST_LEFT)) 301 flags &= ~FMT_PADZERO; 302 snprintf(buf, sizeof(buf), "%lu", num); 303 str = buf; 304 n = strlen(str); 305 if (prec && prec > n) { 306 memmove(str + prec - n, str, n + 1); 307 memset(str, '0', prec - n); 308 n = prec; 309 } 310 311 if (width && width > n) { 312 char *q; 313 MF_OBSTACK_GROW(NULL, width, q); 314 memset(q, 315 (flags & FMT_PADZERO) ? 316 '0' : ' ', 317 width); 318 if (flags & FMT_ADJUST_LEFT) 319 memcpy(q, str, n); 320 else 321 memcpy(q + width - n, str, n); 322 } else 323 MF_OBSTACK_GROW(str, n); 324 break; 325 326 case 'x': 327 case 'X': 328 MF_VA_ARG(argnum, NUMBER, num); 329 /* If a precision is given with a 330 numeric conversion, the 0 flag is ignored. 331 */ 332 /* A - overrides a 0 if both are given.*/ 333 if (prec || (flags & FMT_ADJUST_LEFT)) 334 flags &= ~FMT_PADZERO; 335 fmtbuf[1] = format[cur]; 336 snprintf(buf+2, sizeof(buf)-2, fmtbuf, num); 337 str = buf + 2; 338 n = strlen(str); 339 if (prec && prec > n) { 340 memmove(str + prec - n, str, n + 1); 341 memset(str, '0', prec - n); 342 n = prec; 343 } 344 345 if (flags & FMT_ALTERNATE) { 346 *--str = format[cur]; 347 *--str = '0'; 348 n += 2; 349 } 350 351 if (width && width > n) { 352 char *q; 353 MF_OBSTACK_GROW(NULL, width, q); 354 memset(q, 355 (flags & FMT_PADZERO) ? 356 '0' : ' ', 357 width); 358 if (flags & FMT_ADJUST_LEFT) 359 memcpy(q, str, n); 360 else { 361 if (flags & FMT_ALTERNATE 362 && flags & FMT_PADZERO) { 363 q[0] = *str++; 364 q[1] = *str++; 365 n -= 2; 366 } 367 memcpy(q + width - n, str, n); 368 } 369 } else 370 MF_OBSTACK_GROW(str, n); 371 break; 372 373 case 'o': 374 MF_VA_ARG(argnum, NUMBER, num); 375 /* If a precision is given with a 376 numeric conversion, the 0 flag is ignored. 377 */ 378 /* A - overrides a 0 if both are given.*/ 379 if (prec || (flags & FMT_ADJUST_LEFT)) 380 flags &= ~FMT_PADZERO; 381 snprintf(buf+1, sizeof(buf)-1, "%lo", num); 382 str = buf + 2; 383 n = strlen(str); 384 if (prec && prec > n) { 385 memmove(str + prec - n, str, n + 1); 386 memset(str, '0', prec - n); 387 } 388 389 if ((flags & FMT_ALTERNATE) && *str != '0') { 390 *--str = '0'; 391 n++; 392 } 393 394 if (width && width > n) { 395 char *q; 396 MF_OBSTACK_GROW(NULL, width, q); 397 memset(q, 398 (flags & FMT_PADZERO) ? 399 '0' : ' ', 400 width); 401 if (flags & FMT_ADJUST_LEFT) 402 memcpy(q, str, n); 403 else 404 memcpy(q + width - n, str, n); 405 } else 406 MF_OBSTACK_GROW(str, n); 407 break; 408 409 default: 410 MF_OBSTACK_GROW(&format[start], 411 cur - start + 1); 412 } 413 414 cur++; 415 state = fmts_copy; 416 } 417 } 418 MF_OBSTACK_1GROW(0); 419 MF_VA_END(); 420 MF_RETURN_OBSTACK(); 421} 422END 423 424