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