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 }


syntax highlighted by Code2HTML, v. 0.9.1