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