↑ 1 /*
↑ 2 Copyright (C) 2006 Ralf Huesing <ralf@stormbind.net>
↑ 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 2 of the License, or
↑ 7 (at your option) 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, write to the Free Software
↑ 16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
↑ 17 */
↑ 18
↑ 19 #include <stdarg.h>
↑ 20 #include <string.h> /* strerror() */
↑ 21 #include <errno.h>
↑ 22 //#include <stdio.h>
↑ 23 #include "rh_buffer.h"
↑ 24 #include "rh_string.h"
↑ 25
↑ 26 #ifndef MIN
↑ 27 # define MIN(_a,_b) ( (_a) > (_b) ? (_b) : (_a) )
↑ 28 #endif
↑ 29
↑ 30 /**
↑ 31 * @file rh_buffer_sprintf.c
↑ 32 * @brief sprintf() like function for rh_buffer_t.
↑ 33 * @version 0.0.1
↑ 34 * @date 2006-02-18, Initial Release
↑ 35 */
↑ 36
↑ 37 /**
↑ 38 *
↑ 39 * rh_buffer_sprintf acts like sprintf() expect it uses the rh_buffer
↑ 40 * structure.
↑ 41 *
↑ 42 * rh_buffer_sprintf knows about the following format types.
↑ 43 *
↑ 44 * @par The flag characters:
↑ 45 * The character % is followed by zero or more of the following flags:
↑ 46 * - 0 The value should be zero padded.
↑ 47 * - '-' The converted value is to be left adjusted on the field boundary.
↑ 48 * - ' '
↑ 49 * - '+'
↑ 50 *
↑ 51 * @par Field and Precision width
↑ 52 * decimal digit string, *
↑ 53 *
↑ 54 * @par The length modifier
↑ 55 * - hh char
↑ 56 * - h
↑ 57 * - l
↑ 58 * - ll
↑ 59 * - q
↑ 60 * - z
↑ 61 *
↑ 62 * @par The conversion specifier
↑ 63 * d,i, o,u,x,X, c, s, m, %
↑ 64 *
↑ 65 * @par Extensions
↑ 66 * @par The conversion specifier
↑ 67 * - b; The struct rh_buffer * argument is interpreted like the s-specifier.
↑ 68 *
↑ 69 * \sa sprintf(3)
↑ 70 *
↑ 71 * @return
↑ 72 * - @b RH_SUCCESS On Success.
↑ 73 * - @b RH_ENOSYS Not supported format specifier.
↑ 74 * - @b RH_ENOSPC Set by rh_buffer functions. The buffer's limit was reached.
↑ 75 * - @b RH_ENOMEM Set by rh_buffer/rh_mempool functions.
↑ 76 *
↑ 77 *
↑ 78 * @bug \%p formatting is incorrect because of the prefixed '0x'.
↑ 79 *
↑ 80 * @bug Missing floating point support.
↑ 81 *
↑ 82 */
↑ 83
↑ 84 int rh_buffer_sprintf (rh_buffer_t *buffer, const char *fmt, ...)
↑ 85 {
↑ 86 /* buffer for digit conversions;
↑ 87 * written from end to begin used by the CONVERT_INT_STR macro */
↑ 88 char digit[sizeof("18446744073709551615")];
↑ 89 /* main buffer to avoid realloc()'s and calls to
↑ 90 * rh_buffer() functions */
↑ 91 char tmp[1024];
↑ 92 size_t ntmp = 0; /* main buffers used counter */
↑ 93 va_list ap;
↑ 94 int save_errno = errno;
↑ 95 int error = 0;
↑ 96
↑ 97 va_start (ap, fmt);
↑ 98
↑ 99 for (;;) {
↑100 char *data; /* points to a string containing data to add */
↑101 size_t ndata; /* data's size */
↑102 size_t width = 0; /* given width argument */
↑103 size_t precision = 0; /* given precision argument */
↑104 enum {
↑105 length_unset,
↑106 length_char,
↑107 length_short,
↑108 length_int,
↑109 length_long,
↑110 length_long_long,
↑111 length_size_t,
↑112 } length = length_unset; /* length modifier */
↑113 union {
↑114 char signed_char;
↑115 short signed_short;
↑116 int signed_int;
↑117 long signed_long;
↑118 long long signed_long_long;
↑119 ssize_t signed_size_t;
↑120
↑121 unsigned char unsigned_char;
↑122 unsigned short unsigned_short;
↑123 unsigned int unsigned_int;
↑124 unsigned long unsigned_long;
↑125 unsigned long long unsigned_long_long;
↑126 size_t unsigned_size_t;
↑127
↑128 struct rh_buffer *buf_ptr;
↑129 void *ptr;
↑130 } arg; /* loaded argument */
↑131 struct {
↑132 unsigned alternate:1;
↑133 unsigned zero:1;
↑134 unsigned left:1;
↑135 unsigned blank:1;
↑136 unsigned sign:1;
↑137
↑138 /* internal */
↑139 unsigned precision:1;
↑140 unsigned width:1;
↑141 unsigned string:1;
↑142 unsigned negative:1;
↑143 /* if no_fmt is set then no formatting is done
↑144 * and the buffer pointing to ,,data'' is written
↑145 * into tmp or buffer depending on ndata */
↑146 unsigned no_fmt:1;
↑147 } flags = {0,};
↑148
↑149 /* copy non-format characters */
↑150 for (;;) {
↑151 char ch;
↑152
↑153 switch ((ch=*fmt++)) {
↑154 case 0:
↑155 goto out_done;
↑156 case '%':
↑157 /* skip over the format stuff
↑158 * to safe parsing time and avoid reallocs.
↑159 */
↑160 switch (*fmt) {
↑161 case '0': case '1': case '2': case '3':
↑162 case '4': case '5': case '6': case '7':
↑163 case '8': case '9':
↑164 case '#':
↑165 case '-':
↑166 case ' ':
↑167 case '+':
↑168 case '.':
↑169 case '*':
↑170 break;
↑171 case 'd':
↑172 case 'u':
↑173 case 'x':
↑174 case 'X':
↑175 case 's':
↑176 case 'b':
↑177 case 'p':
↑178 case 'm':
↑179 flags.no_fmt = 1;
↑180 goto simple_without_length;
↑181 default:
↑182 flags.no_fmt = 1;
↑183 goto simple_with_length;
↑184 }
↑185 break;
↑186 default:
↑187 if (ntmp > sizeof(tmp)-1) {
↑188 if (rh_buffer_append(buffer,tmp,ntmp))
↑189 return -1;
↑190 ntmp=0;
↑191 }
↑192 tmp[ntmp++] = ch;
↑193 continue;
↑194 }
↑195 break;
↑196 }
↑197
↑198 /* flag (zero or more) */
↑199 /* '#' - alternate form
↑200 * '0' - zero padded
↑201 * '-' - left adjusted
↑202 * ' ' - blank padded
↑203 * '+' - always sign
↑204 */
↑205
↑206 for (;;) {
↑207 switch (*fmt) {
↑208 case '#': flags.alternate=1; ++fmt; continue;
↑209 case '0': flags.zero=1; ++fmt; continue;
↑210 case '-': flags.left=1; ++fmt; continue;
↑211 case ' ': flags.blank=1; ++fmt; continue;
↑212 case '+': flags.sign=1; ++fmt; continue;
↑213 }
↑214 break;
↑215 }
↑216
↑217 /* field width */
↑218 /* [1-9][0-9]+ number in fmt
↑219 * * number in arg as int
↑220 * *m$ number in m'th arg as int
↑221 */
↑222 //printf ("widt (%s)\n", fmt);
↑223 switch (*fmt) {
↑224 case '*':
↑225 arg.signed_int = va_arg(ap, int);
↑226 if (arg.signed_int < 0)
↑227 width = -arg.signed_int;
↑228 else
↑229 width = arg.signed_int;
↑230 flags.width = 1;
↑231 ++fmt;
↑232 break;
↑233 case '1': case '2': case '3': case '4': case '5':
↑234 case '6': case '7': case '8': case '9':
↑235 width = (*fmt++) - '0';
↑236 for (;;) {
↑237 char ch = *fmt;
↑238 switch (ch) {
↑239 case '0': case '1': case '2': case '3':
↑240 case '4': case '5': case '6': case '7':
↑241 case '8': case '9':
↑242 width = width * 10 + (ch-'0');
↑243 ++fmt;
↑244 continue;
↑245 case '$':
↑246 errno = ENOSYS;
↑247 goto out_error;
↑248 }
↑249 break;
↑250 }
↑251 flags.width = 1;
↑252 }
↑253
↑254 //printf ("prec (%s)\n", fmt);
↑255
↑256 /* precision */
↑257 /* . followed by digit or * or *m$ */
↑258 if (*fmt == '.') {
↑259 switch (*++fmt) {
↑260 case '*':
↑261 arg.signed_int = va_arg(ap, int);
↑262 if (arg.signed_int < 0)
↑263 precision = -arg.signed_int;
↑264 else
↑265 precision = arg.signed_int;
↑266 flags.precision = 1;
↑267 ++fmt;
↑268 break;
↑269 case '0': case '1': case '2': case '3': case '4':
↑270 case '5': case '6': case '7': case '8': case '9':
↑271 precision = (*fmt++) - '0';
↑272 for (;;) {
↑273 char ch = *fmt;
↑274 switch (ch) {
↑275 case '0': case '1': case '2': case '3':
↑276 case '4': case '5': case '6': case '7':
↑277 case '8': case '9':
↑278 precision = precision
↑279 * 10 + (ch-'0');
↑280 ++fmt;
↑281 continue;
↑282 case '$':
↑283 errno = ENOSYS;
↑284 goto out_error;
↑285 }
↑286 break;
↑287 }
↑288 flags.precision = 1;
↑289 break;
↑290 case '$':
↑291 errno = ENOSYS;
↑292 goto out_error;
↑293 }
↑294 }
↑295
↑296 /* length modifier */
↑297 /* hh - char
↑298 * h - short
↑299 * l - long
↑300 * ll - long long
↑301 * q - quad; long long
↑302 * z - size_t
↑303 *
↑304 * not supported:
↑305 * L - long double
↑306 * j - (u)intmax_t
↑307 * t - ptrdiff_t
↑308 */
↑309
↑310 simple_with_length:
↑311 //printf ("len (%s)\n", fmt);
↑312
↑313 switch (*fmt) {
↑314 case 'h':
↑315 if (fmt[1] == 'h') {
↑316 length = length_char;
↑317 fmt += 2;
↑318 } else {
↑319 length = length_short;
↑320 ++fmt;
↑321 }
↑322 break;
↑323 case 'l':
↑324 if (fmt[1] == 'l') {
↑325 length = length_long_long;
↑326 fmt += 2;
↑327 } else {
↑328 length = length_long;
↑329 ++fmt;
↑330 }
↑331 break;
↑332 case 'q':
↑333 length = length_long_long;
↑334 ++fmt;
↑335 break;
↑336 case 'z':
↑337 length = length_size_t;
↑338 ++fmt;
↑339 break;
↑340 case 'L':
↑341 case 'j':
↑342 case 't':
↑343 errno = ENOSYS;
↑344 goto out_error;
↑345 }
↑346
↑347
↑348 /* conversion specifier */
↑349 /* d,i signed int; base: 10
↑350 * o,u,x,X unsigned int; base: 8, 10, 16, 16
↑351 * c char
↑352 * s c-string
↑353 * p pointer, same as "0x%x"
↑354 * % %-character
↑355 */
↑356
↑357 /* extensions
↑358 * b rh_buffer * pointer
↑359 *
↑360 */
↑361
↑362 #define CONVERT_INT_STR(_var,_base) \
↑363 { \
↑364 data = &digit[sizeof(tmp)-1]; \
↑365 do { \
↑366 *data-- = _base[_var % (sizeof(_base)-1)]; \
↑367 _var /= sizeof(_base)-1; \
↑368 } while (_var); \
↑369 ndata = &digit[sizeof(tmp)-1] - data++; \
↑370 }
↑371
↑372
↑373 #define CONVERT_signed_INT(_type,_base) \
↑374 { \
↑375 if (arg.signed_ ##_type < 0) { \
↑376 flags.negative = 1; \
↑377 arg.signed_ ##_type = -arg.signed_ ##_type; \
↑378 } \
↑379 CONVERT_INT_STR(arg.signed_ ##_type, _base); \
↑380 }
↑381
↑382 #define CONVERT_unsigned_INT(_type,_base) \
↑383 { \
↑384 CONVERT_INT_STR(arg.unsigned_ ##_type, _base); \
↑385 }
↑386
↑387 #define CONVERT_INT(_sign,_base) \
↑388 { \
↑389 switch (length) { \
↑390 case length_char: \
↑391 arg._sign ##_char = (char) va_arg(ap, int); \
↑392 CONVERT_ ##_sign ##_INT(char, _base); \
↑393 break; \
↑394 case length_short: \
↑395 arg._sign ##_short = (short) va_arg(ap, int); \
↑396 CONVERT_ ##_sign ##_INT(short, _base); \
↑397 break; \
↑398 case length_long: \
↑399 arg._sign ##_long = (long) va_arg(ap, long); \
↑400 CONVERT_ ##_sign ##_INT(long, _base); \
↑401 break; \
↑402 case length_long_long: \
↑403 arg._sign ##_long_long = (long long)va_arg(ap, long long);\
↑404 CONVERT_ ##_sign ##_INT(long_long, _base); \
↑405 break; \
↑406 case length_size_t: \
↑407 arg._sign ##_size_t = (size_t) va_arg(ap, size_t); \
↑408 CONVERT_ ##_sign ##_INT(size_t, _base); \
↑409 break; \
↑410 default: \
↑411 arg._sign ##_int = (int)va_arg(ap, int); \
↑412 CONVERT_ ##_sign ##_INT(int, _base); \
↑413 break; \
↑414 } \
↑415 }
↑416
↑417 simple_without_length:
↑418 // printf ("conv (%s)\n", fmt);
↑419 switch (*fmt++) {
↑420 case 'd':
↑421 case 'i':
↑422 CONVERT_INT( signed, "0123456789");
↑423 break;
↑424 case 'o':
↑425 CONVERT_INT(unsigned, "01234567");
↑426 flags.sign = 0;
↑427 break;
↑428 case 'u':
↑429 CONVERT_INT(unsigned, "0123456789");
↑430 flags.sign = 0;
↑431 break;
↑432 case 'x':
↑433 CONVERT_INT(unsigned, "0123456789abcdef");
↑434 flags.sign = 0;
↑435 break;
↑436 case 'X':
↑437 CONVERT_INT(unsigned, "0123456789ABCDEF");
↑438 flags.sign = 0;
↑439 break;
↑440 case 'c':
↑441 if (length == length_long) {
↑442 /* wide-char */
↑443 errno = ENOSYS;
↑444 goto out_error;
↑445 }
↑446 arg.signed_char = (char)va_arg(ap, int);
↑447 data = tmp;
↑448 data[0] = arg.signed_char;
↑449 ndata = 1;
↑450 flags.string = 1;
↑451 break;
↑452 case 'p':
↑453 arg.unsigned_size_t = (size_t)va_arg(ap, void *);
↑454 CONVERT_INT_STR(arg.unsigned_size_t,"0123456789abcdef");
↑455 /* XXX: "%020p" */
↑456 *--data = 'x';
↑457 *--data = '0';
↑458 ndata+=2;
↑459 break;
↑460 case 'b':
↑461 arg.buf_ptr = va_arg(ap, struct rh_buffer *);
↑462 if (NULL == arg.buf_ptr) {
↑463 flags.no_fmt = 1;
↑464 data = "(null)";
↑465 ndata = CONST_LEN("(null)");
↑466 } else {
↑467 if (flags.precision) {
↑468 ndata = MIN(precision,
↑469 arg.buf_ptr->used);
↑470 } else {
↑471 ndata = arg.buf_ptr->used;
↑472 }
↑473
↑474 if (!flags.width)
↑475 flags.no_fmt = 1;
↑476
↑477 data = arg.buf_ptr->data;
↑478 }
↑479 flags.string = 1;
↑480 break;
↑481 case 's':
↑482 if (length == length_long) {
↑483 /* wide-string */
↑484 errno = ENOSYS;
↑485 goto out_error;
↑486 }
↑487 data = va_arg(ap, char *);
↑488 if (NULL==data) {
↑489 flags.no_fmt = 1;
↑490 data = "(null)";
↑491 ndata = CONST_LEN("(null)");
↑492 } else {
↑493 if (flags.precision)
↑494 ndata = precision;
↑495 else
↑496 ndata = strlen(data);
↑497
↑498 if (!flags.width)
↑499 flags.no_fmt = 1;
↑500 }
↑501 flags.string = 1;
↑502 break;
↑503 case 'm':
↑504 data = strerror(save_errno);
↑505 ndata = strlen(data);
↑506 flags.string = 1;
↑507 flags.no_fmt = 1;
↑508 break;
↑509 case '%':
↑510 flags.no_fmt = 1;
↑511 data = "%";
↑512 ndata = 1;
↑513 flags.string = 1;
↑514 break;
↑515 default:
↑516 errno = ENOSYS;
↑517 goto out_error;
↑518 }
↑519
↑520 if (flags.no_fmt) {
↑521 /* append unformatted data */
↑522 if (ndata > sizeof(tmp)) {
↑523 if (ntmp) {
↑524 if (rh_buffer_append (buffer,
↑525 tmp,ntmp))
↑526 return -1;
↑527 ntmp=0;
↑528 }
↑529 if (rh_buffer_append (buffer,data,ndata))
↑530 return -1;
↑531 } else {
↑532 /* use internal buffer for small data to avoid
↑533 * realloc's() in buffer. */
↑534 while (ndata) {
↑535 size_t size;
↑536
↑537 if (ntmp > sizeof(tmp)-2) {
↑538 if (rh_buffer_append (buffer,
↑539 tmp,ntmp))
↑540 return -1;
↑541 ntmp=0;
↑542 }
↑543
↑544 size = MIN(ndata, sizeof(tmp)-1-ntmp);
↑545 memcpy (&tmp[ntmp],data,size);
↑546 data += size;
↑547 ntmp += size;
↑548 ndata -= size;
↑549 }
↑550 }
↑551 } else
↑552 if (flags.string) {
↑553 size_t size;
↑554
↑555 /* append formatted string */
↑556
↑557 if (ntmp) {
↑558 if (rh_buffer_append(buffer, tmp, ntmp))
↑559 return -1;
↑560 ntmp=0;
↑561 }
↑562
↑563 if (flags.width && width > ndata) {
↑564 width -= ndata;
↑565 } else {
↑566 flags.width = 0;
↑567 }
↑568
↑569 size = ndata + width;
↑570
↑571 if (rh_buffer_reserve (buffer, size))
↑572 return -1;
↑573
↑574 if (flags.left) {
↑575 if (rh_buffer_append(buffer, data, ndata))
↑576 return -1;
↑577 }
↑578
↑579 if (flags.width) {
↑580 if (rh_buffer_append_ch(buffer, ' ', width))
↑581 return -1;
↑582 }
↑583
↑584 if (!flags.left) {
↑585 if (rh_buffer_append(buffer, data, ndata))
↑586 return -1;
↑587 }
↑588 } else {
↑589 size_t size;
↑590 char sign = 0;
↑591
↑592 /* append formatted digit */
↑593
↑594 if (ntmp) {
↑595 if (rh_buffer_append(buffer, tmp, ntmp))
↑596 return -1;
↑597 ntmp=0;
↑598 }
↑599
↑600 if (flags.precision) {
↑601 flags.zero = 0;
↑602
↑603 if (precision > ndata) {
↑604 precision -= ndata;
↑605 } else {
↑606 precision = 0;
↑607 }
↑608
↑609 if (width > precision) {
↑610 width -= precision;
↑611 } else {
↑612 width = 0;
↑613 }
↑614 }
↑615
↑616 if (width > ndata) {
↑617 width -= ndata;
↑618 } else {
↑619 width = 0;
↑620 }
↑621
↑622 if (flags.sign || flags.negative) {
↑623 sign = (flags.negative ? '-' : '+');
↑624 if (width)
↑625 --width;
↑626 }
↑627
↑628 size = ndata + precision + width + (sign > 0);
↑629
↑630 error = rh_buffer_reserve (buffer, size);
↑631 if (error) goto out_error;
↑632
↑633 if (flags.left) {
↑634 if (sign) {
↑635 error = rh_buffer_append_ch(buffer, sign, 1);
↑636 if (error) goto out_error;
↑637 }
↑638
↑639 error = rh_buffer_append_ch(buffer, '0', precision);
↑640 if (error) goto out_error;
↑641
↑642 error = rh_buffer_append(buffer, data, ndata);
↑643 if (error) goto out_error;
↑644
↑645 error = rh_buffer_append_ch(buffer, ' ', width);
↑646 if (error) goto out_error;
↑647 } else {
↑648 if (sign && flags.zero) {
↑649 error = rh_buffer_append_ch(buffer, sign, 1);
↑650 if (error) goto out_error;
↑651 sign = 0;
↑652 }
↑653
↑654 if (flags.zero) {
↑655 error = rh_buffer_append_ch(buffer,'0',width);
↑656 if (error) goto out_error;
↑657 } else {
↑658 error = rh_buffer_append_ch(buffer,' ',width);
↑659 if (error) goto out_error;
↑660 }
↑661
↑662 if (sign) {
↑663 error = rh_buffer_append_ch(buffer, sign, 1);
↑664 if (error) goto out_error;
↑665 }
↑666
↑667 error = rh_buffer_append_ch(buffer, '0', precision);
↑668 if (error) goto out_error;
↑669
↑670 error = rh_buffer_append(buffer, data, ndata);
↑671 if (error) goto out_error;
↑672 }
↑673 }
↑674 }
↑675 out_done:
↑676 va_end (ap);
↑677 return (ntmp ? rh_buffer_append(buffer, tmp, ntmp) : 0);
↑678 out_error:
↑679 va_end (ap);
↑680 return error;
↑681 }