]> git.sur5r.net Git - cc65/blob - src/common/xsprintf.c
d50ad7a68f1738151c9183730437310d6a35f176
[cc65] / src / common / xsprintf.c
1 /*****************************************************************************/
2 /*                                                                           */
3 /*                                xsprintf.c                                 */
4 /*                                                                           */
5 /*                       Replacement sprintf function                        */
6 /*                                                                           */
7 /*                                                                           */
8 /*                                                                           */
9 /* (C) 2000-2004 Ullrich von Bassewitz                                       */
10 /*               Roemerstrasse 52                                            */
11 /*               D-70794 Filderstadt                                         */
12 /* EMail:        uz@cc65.org                                                 */
13 /*                                                                           */
14 /*                                                                           */
15 /* This software is provided 'as-is', without any expressed or implied       */
16 /* warranty.  In no event will the authors be held liable for any damages    */
17 /* arising from the use of this software.                                    */
18 /*                                                                           */
19 /* Permission is granted to anyone to use this software for any purpose,     */
20 /* including commercial applications, and to alter it and redistribute it    */
21 /* freely, subject to the following restrictions:                            */
22 /*                                                                           */
23 /* 1. The origin of this software must not be misrepresented; you must not   */
24 /*    claim that you wrote the original software. If you use this software   */
25 /*    in a product, an acknowledgment in the product documentation would be  */
26 /*    appreciated but is not required.                                       */
27 /* 2. Altered source versions must be plainly marked as such, and must not   */
28 /*    be misrepresented as being the original software.                      */
29 /* 3. This notice may not be removed or altered from any source              */
30 /*    distribution.                                                          */
31 /*                                                                           */
32 /*****************************************************************************/
33
34
35
36 #include <stdio.h>
37 #include <stddef.h>
38 #include <string.h>
39 #include <limits.h>
40
41 /* common */
42 #include "chartype.h"
43 #include "check.h"
44 #include "inttypes.h"
45 #include "strbuf.h"
46 #include "va_copy.h"
47 #include "xsprintf.h"
48
49
50
51 /*****************************************************************************/
52 /*                                  vsnprintf                                */
53 /*****************************************************************************/
54
55
56
57 /* The following is a very basic vsnprintf like function called xvsnprintf. It
58 ** features only the basic format specifiers (especially the floating point
59 ** stuff is missing), but may be extended if required. Reason for supplying
60 ** my own implementation is that vsnprintf is standard but not implemented by
61 ** older compilers, and some that implement it, don't adhere to the standard
62 ** (for example Microsoft with its _vsnprintf).
63 */
64
65 typedef struct {
66
67     /* Variable argument list pointer */
68     va_list     ap;
69
70     /* Output buffer */
71     char*       Buf;
72     size_t      BufSize;
73     size_t      BufFill;
74
75     /* Argument string buffer and string buffer pointer. The string buffer
76     ** must be big enough to hold a converted integer of the largest type
77     ** including an optional sign and terminating zero.
78     */
79     char        ArgBuf[256];
80     int         ArgLen;
81
82     /* Flags */
83     enum {
84         fNone     = 0x0000,
85         fMinus    = 0x0001,
86         fPlus     = 0x0002,
87         fSpace    = 0x0004,
88         fHash     = 0x0008,
89         fZero     = 0x0010,
90         fWidth    = 0x0020,
91         fPrec     = 0x0040,
92         fUnsigned = 0x0080,
93         fUpcase   = 0x0100
94     } Flags;
95
96     /* Conversion base and table */
97     unsigned    Base;
98     const char* CharTable;
99
100     /* Field width */
101     int         Width;
102
103     /* Precision */
104     int         Prec;
105
106     /* Length modifier */
107     enum {
108         lmChar,
109         lmShort,
110         lmInt,
111         lmLong,
112         lmIntMax,
113         lmSizeT,
114         lmPtrDiffT,
115         lmLongDouble,
116
117         /* Unsupported modifiers */
118         lmLongLong = lmLong,
119
120         /* Default length is integer */
121         lmDefault = lmInt
122     } LengthMod;
123
124 } PrintfCtrl;
125
126
127
128 static void AddChar (PrintfCtrl* P, char C)
129 /* Store one character in the output buffer if there's enough room. */
130 {
131     if (++P->BufFill <= P->BufSize) {
132         *P->Buf++ = C;
133     }
134 }
135
136
137
138 static void AddPadding (PrintfCtrl* P, char C, unsigned Count)
139 /* Add some amount of padding */
140 {
141     while (Count--) {
142         AddChar (P, C);
143     }
144 }
145
146
147
148 static intmax_t NextIVal (PrintfCtrl*P)
149 /* Read the next integer value from the variable argument list */
150 {
151     switch (P->LengthMod) {
152         case lmChar:        return (char) va_arg (P->ap, int);
153         case lmShort:       return (short) va_arg (P->ap, int);
154         case lmInt:         return (int) va_arg (P->ap, int);
155         case lmLong:        return (long) va_arg (P->ap, long);
156         case lmIntMax:      return va_arg (P->ap, intmax_t);
157         case lmSizeT:       return (uintmax_t) va_arg (P->ap, size_t);
158         case lmPtrDiffT:    return (long) va_arg (P->ap, ptrdiff_t);
159         default:
160             FAIL ("Invalid type size in NextIVal");
161             return 0;
162     }
163 }
164
165
166
167 static uintmax_t NextUVal (PrintfCtrl*P)
168 /* Read the next unsigned integer value from the variable argument list */
169 {
170     switch (P->LengthMod) {
171         case lmChar:        return (unsigned char) va_arg (P->ap, unsigned);
172         case lmShort:       return (unsigned short) va_arg (P->ap, unsigned);
173         case lmInt:         return (unsigned int) va_arg (P->ap, unsigned int);
174         case lmLong:        return (unsigned long) va_arg (P->ap, unsigned long);
175         case lmIntMax:      return va_arg (P->ap, uintmax_t);
176         case lmSizeT:       return va_arg (P->ap, size_t);
177         case lmPtrDiffT:    return (intmax_t) va_arg (P->ap, ptrdiff_t);
178         default:
179             FAIL ("Invalid type size in NextUVal");
180             return 0;
181     }
182 }
183
184
185
186 static void ToStr (PrintfCtrl* P, uintmax_t Val)
187 /* Convert the given value to a (reversed) string */
188 {
189     char* S = P->ArgBuf;
190     while (Val) {
191         *S++ = P->CharTable[Val % P->Base];
192         Val /= P->Base;
193     }
194     P->ArgLen = S - P->ArgBuf;
195 }
196
197
198
199 static void FormatInt (PrintfCtrl* P, uintmax_t Val)
200 /* Convert the integer value */
201 {
202     char Lead[5];
203     unsigned LeadCount = 0;
204     unsigned PrecPadding;
205     unsigned WidthPadding;
206     unsigned I;
207
208
209     /* Determine the translation table */
210     P->CharTable = (P->Flags & fUpcase)? "0123456789ABCDEF" : "0123456789abcdef";
211
212     /* Check if the value is negative */
213     if ((P->Flags & fUnsigned) == 0 && ((intmax_t) Val) < 0) {
214         Val = -((intmax_t) Val);
215         Lead[LeadCount++] = '-';
216     } else if ((P->Flags & fPlus) != 0) {
217         Lead[LeadCount++] = '+';
218     } else if ((P->Flags & fSpace) != 0) {
219         Lead[LeadCount++] = ' ';
220     }
221
222     /* Convert the value into a (reversed string). */
223     ToStr (P, Val);
224
225     /* The default precision for all integer conversions is one. This means
226     ** that the fPrec flag is always set and does not need to be checked
227     ** later on.
228     */
229     if ((P->Flags & fPrec) == 0) {
230         P->Flags |= fPrec;
231         P->Prec = 1;
232     }
233
234     /* Determine the leaders for alternative forms */
235     if ((P->Flags & fHash) != 0) {
236         if (P->Base == 16) {
237             /* Start with 0x */
238             Lead[LeadCount++] = '0';
239             Lead[LeadCount++] = (P->Flags & fUpcase)? 'X' : 'x';
240         } else if (P->Base == 8) {
241             /* Alternative form for 'o': always add a leading zero. */
242             if (P->Prec <= P->ArgLen) {
243                 Lead[LeadCount++] = '0';
244             }
245         }
246     }
247
248     /* Determine the amount of precision padding needed */
249     if (P->ArgLen < P->Prec) {
250         PrecPadding = P->Prec - P->ArgLen;
251     } else {
252         PrecPadding = 0;
253     }
254
255     /* Determine the width padding needed */
256     if ((P->Flags & fWidth) != 0) {
257         int CurWidth = LeadCount + PrecPadding + P->ArgLen;
258         if (CurWidth < P->Width) {
259             WidthPadding = P->Width - CurWidth;
260         } else {
261             WidthPadding = 0;
262         }
263     } else {
264         WidthPadding = 0;
265     }
266
267     /* Output left space padding if any */
268     if ((P->Flags & (fMinus | fZero)) == 0 && WidthPadding > 0) {
269         AddPadding (P, ' ', WidthPadding);
270         WidthPadding = 0;
271     }
272
273     /* Leader */
274     for (I = 0; I < LeadCount; ++I) {
275         AddChar (P, Lead[I]);
276     }
277
278     /* Left zero padding if any */
279     if ((P->Flags & fZero) != 0 && WidthPadding > 0) {
280         AddPadding (P, '0', WidthPadding);
281         WidthPadding = 0;
282     }
283
284     /* Precision padding */
285     if (PrecPadding > 0) {
286         AddPadding (P, '0', PrecPadding);
287     }
288
289     /* The number itself. Beware: It's reversed! */
290     while (P->ArgLen > 0) {
291         AddChar (P, P->ArgBuf[--P->ArgLen]);
292     }
293
294     /* Right width padding if any */
295     if (WidthPadding > 0) {
296         AddPadding (P, ' ', WidthPadding);
297     }
298 }
299
300
301
302 static void FormatStr (PrintfCtrl* P, const char* Val)
303 /* Convert the string */
304 {
305     unsigned WidthPadding;
306
307     /* Get the string length limited to the precision. Beware: We cannot use
308     ** strlen here, because if a precision is given, the string may not be
309     ** zero terminated.
310     */
311     int Len;
312     if ((P->Flags & fPrec) != 0) {
313         const char* S = memchr (Val, '\0', P->Prec);
314         if (S == 0) {
315             /* Not zero terminated */
316             Len = P->Prec;
317         } else {
318             /* Terminating zero found */
319             Len = S - Val;
320         }
321     } else {
322         Len = strlen (Val);
323     }
324
325     /* Determine the width padding needed */
326     if ((P->Flags & fWidth) != 0 && P->Width > Len) {
327         WidthPadding = P->Width - Len;
328     } else {
329         WidthPadding = 0;
330     }
331
332     /* Output left padding */
333     if ((P->Flags & fMinus) != 0 && WidthPadding > 0) {
334         AddPadding (P, ' ', WidthPadding);
335         WidthPadding = 0;
336     }
337
338     /* Output the string */
339     while (Len--) {
340         AddChar (P, *Val++);
341     }
342
343     /* Output right padding if any */
344     if (WidthPadding > 0) {
345         AddPadding (P, ' ', WidthPadding);
346     }
347 }
348
349
350
351 static void StoreOffset (PrintfCtrl* P)
352 /* Store the current output offset (%n format spec) */
353 {
354     switch (P->LengthMod) {
355         case lmChar:     *va_arg (P->ap, int*)       = P->BufFill;
356         case lmShort:    *va_arg (P->ap, int*)       = P->BufFill;
357         case lmInt:      *va_arg (P->ap, int*)       = P->BufFill;
358         case lmLong:     *va_arg (P->ap, long*)      = P->BufFill;
359         case lmIntMax:   *va_arg (P->ap, intmax_t*)  = P->BufFill;
360         case lmSizeT:    *va_arg (P->ap, size_t*)    = P->BufFill;
361         case lmPtrDiffT: *va_arg (P->ap, ptrdiff_t*) = P->BufFill;
362         default: FAIL ("Invalid size modifier for %n format spec in xvsnprintf");
363     }
364 }
365
366
367
368 int xvsnprintf (char* Buf, size_t Size, const char* Format, va_list ap)
369 /* A basic vsnprintf implementation. Does currently only support integer
370 ** formats.
371 */
372 {
373     PrintfCtrl P;
374     int Done;
375     char F;
376     char SBuf[2];
377     const char* SPtr;
378     int UseStrBuf = 0;
379
380
381     /* Initialize the control structure */
382     va_copy (P.ap, ap);
383     P.Buf       = Buf;
384     P.BufSize   = Size;
385     P.BufFill   = 0;
386
387     /* Parse the format string */
388     while ((F = *Format++) != '\0') {
389
390         if (F != '%') {
391             /* Not a format specifier, just copy */
392             AddChar (&P, F);
393             continue;
394         }
395
396         /* Check for %% */
397         if (*Format == '%') {
398             ++Format;
399             AddChar (&P, '%');
400             continue;
401         }
402
403         /* It's a format specifier. Check for flags. */
404         F = *Format++;
405         P.Flags = fNone;
406         Done = 0;
407         while (F != '\0' && !Done) {
408             switch (F) {
409                 case '-': P.Flags |= fMinus; F = *Format++; break;
410                 case '+': P.Flags |= fPlus;  F = *Format++; break;
411                 case ' ': P.Flags |= fSpace; F = *Format++; break;
412                 case '#': P.Flags |= fHash;  F = *Format++; break;
413                 case '0': P.Flags |= fZero;  F = *Format++; break;
414                 default:  Done     = 1;                     break;
415             }
416         }
417         /* Optional field width */
418         if (F == '*') {
419             P.Width = va_arg (P.ap, int);
420             /* A negative field width argument is taken as a - flag followed
421             ** by a positive field width.
422             */
423             if (P.Width < 0) {
424                 P.Flags |= fMinus;
425                 P.Width = -P.Width;
426             }
427             F = *Format++;
428             P.Flags |= fWidth;
429         } else if (IsDigit (F)) {
430             P.Width = F - '0';
431             while (1) {
432                 F = *Format++;
433                 if (!IsDigit (F)) {
434                     break;
435                 }
436                 P.Width = P.Width * 10 + (F - '0');
437             }
438             P.Flags |= fWidth;
439         }
440
441         /* Optional precision */
442         if (F == '.') {
443             F = *Format++;
444             P.Flags |= fPrec;
445             if (F == '*') {
446                 P.Prec = va_arg (P.ap, int);
447                 /* A negative precision argument is taken as if the precision
448                 ** were omitted.
449                 */
450                 if (P.Prec < 0) {
451                     P.Flags &= ~fPrec;
452                 }
453                 F = *Format++;          /* Skip the '*' */
454             } else if (IsDigit (F)) {
455                 P.Prec = F - '0';
456                 while (1) {
457                     F = *Format++;
458                     if (!IsDigit (F)) {
459                         break;
460                     }
461                     P.Prec = P.Prec * 10 + (F - '0');
462                 }
463             } else if (F == '-') {
464                 /* A negative precision argument is taken as if the precision
465                 ** were omitted.
466                 */
467                 F = *Format++;          /* Skip the minus */
468                 while (IsDigit (F = *Format++)) ;
469                 P.Flags &= ~fPrec;
470             } else {
471                 P.Prec = 0;
472             }
473         }
474
475         /* Optional length modifier */
476         P.LengthMod = lmDefault;
477         switch (F) {
478
479             case 'h':
480                 F = *Format++;
481                 if (F == 'h') {
482                     F = *Format++;
483                     P.LengthMod = lmChar;
484                 } else {
485                     P.LengthMod = lmShort;
486                 }
487                 break;
488
489             case 'l':
490                 F = *Format++;
491                 if (F == 'l') {
492                     F = *Format++;
493                     P.LengthMod = lmLongLong;
494                 } else {
495                     P.LengthMod = lmLong;
496                 }
497                 break;
498
499             case 'j':
500                 P.LengthMod = lmIntMax;
501                 F = *Format++;
502                 break;
503
504             case 'z':
505                 P.LengthMod = lmSizeT;
506                 F = *Format++;
507                 break;
508
509             case 't':
510                 P.LengthMod = lmPtrDiffT;
511                 F = *Format++;
512                 break;
513
514             case 'L':
515                 P.LengthMod = lmLongDouble;
516                 F = *Format++;
517                 break;
518
519         }
520
521         /* If the space and + flags both appear, the space flag is ignored */
522         if ((P.Flags & (fSpace | fPlus)) == (fSpace | fPlus)) {
523             P.Flags &= ~fSpace;
524         }
525         /* If the 0 and - flags both appear, the 0 flag is ignored */
526         if ((P.Flags & (fZero | fMinus)) == (fZero | fMinus)) {
527             P.Flags &= ~fZero;
528         }
529         /* If a precision is specified, the 0 flag is ignored */
530         if (P.Flags & fPrec) {
531             P.Flags &= ~fZero;
532         }
533
534         /* Conversion specifier */
535         switch (F) {
536
537             case 'd':
538             case 'i':
539                 P.Base = 10;
540                 FormatInt (&P, NextIVal (&P));
541                 break;
542
543             case 'o':
544                 P.Flags |= fUnsigned;
545                 P.Base = 8;
546                 FormatInt (&P, NextUVal (&P));
547                 break;
548
549             case 'u':
550                 P.Flags |= fUnsigned;
551                 P.Base = 10;
552                 FormatInt (&P, NextUVal (&P));
553                 break;
554
555             case 'X':
556                 P.Flags |= (fUnsigned | fUpcase);
557                 /* FALLTHROUGH */
558             case 'x':
559                 P.Base = 16;
560                 FormatInt (&P, NextUVal (&P));
561                 break;
562
563             case 'c':
564                 SBuf[0] = (char) va_arg (P.ap, int);
565                 SBuf[1] = '\0';
566                 FormatStr (&P, SBuf);
567                 break;
568
569             case 's':
570                 SPtr = va_arg (P.ap, const char*);
571                 CHECK (SPtr != 0);
572                 FormatStr (&P, SPtr);
573                 break;
574
575             case 'p':
576                 /* See comment at top of header file */
577                 if (UseStrBuf) {
578                     /* Argument is StrBuf */
579                     const StrBuf* S = va_arg (P.ap, const StrBuf*);
580                     CHECK (S != 0);
581                     /* Handle the length by using a precision */
582                     if ((P.Flags & fPrec) != 0) {
583                         /* Precision already specified, use length of string 
584                         ** if less.
585                         */
586                         if ((unsigned) P.Prec > SB_GetLen (S)) {
587                             P.Prec = SB_GetLen (S);
588                         }
589                     } else {
590                         /* No precision, add it */
591                         P.Flags |= fPrec;
592                         P.Prec  = SB_GetLen (S);
593                     }
594                     FormatStr (&P, SB_GetConstBuf (S));
595                     UseStrBuf = 0;              /* Reset flag */
596                 } else {
597                     /* Use hex format for pointers */
598                     P.Flags |= (fUnsigned | fPrec);
599                     P.Prec = ((sizeof (void*) * CHAR_BIT) + 3) / 4;
600                     P.Base = 16;
601                     FormatInt (&P, (uintptr_t) va_arg (P.ap, void*));
602                 }
603                 break;
604
605             case 'm':
606                 /* See comment at top of header file */
607                 UseStrBuf = 1;
608                 break;
609
610             case 'n':
611                 StoreOffset (&P);
612                 break;
613
614             default:
615                 /* Invalid format spec */
616                 FAIL ("Invalid format specifier in xvsnprintf");
617
618         }
619     }
620
621     /* We don't need P.ap any longer */
622     va_end (P.ap);
623
624     /* Terminate the output string and return the number of chars that had
625     ** been written if the buffer was large enough.
626     ** Beware: The terminating zero is not counted for the function result!
627     */
628     AddChar (&P, '\0');
629     return P.BufFill - 1;
630 }
631
632
633
634 int xsnprintf (char* Buf, size_t Size, const char* Format, ...)
635 /* A basic snprintf implementation. Does currently only support integer
636 ** formats.
637 */
638 {
639     int Res;
640     va_list ap;
641
642     va_start (ap, Format);
643     Res = xvsnprintf (Buf, Size, Format, ap);
644     va_end (ap);
645
646     return Res;
647 }
648
649
650
651 /*****************************************************************************/
652 /*                                   Code                                    */
653 /*****************************************************************************/
654
655
656
657 int xsprintf (char* Buf, size_t BufSize, const char* Format, ...)
658 /* Replacement function for sprintf */
659 {
660     int Res;
661     va_list ap;
662
663     va_start (ap, Format);
664     Res = xvsprintf (Buf, BufSize, Format, ap);
665     va_end (ap);
666
667     return Res;
668 }
669
670
671
672 int xvsprintf (char* Buf, size_t BufSize, const char* Format, va_list ap)
673 /* Replacement function for sprintf */
674 {
675     int Res = xvsnprintf (Buf, BufSize, Format, ap);
676     CHECK (Res >= 0 && (unsigned) (Res+1) < BufSize);
677     return Res;
678 }