]> git.sur5r.net Git - cc65/blob - src/common/xsprintf.c
Fixed another bug
[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     /* The default precision for all integer conversions is one. This means
215      * that the fPrec flag is always set and does not need to be checked 
216      * later on.
217      */
218     if ((P->Flags & fPrec) == 0) {
219         P->Flags |= fPrec;
220         P->Prec = 1;
221     }
222
223     /* Determine the leaders for alternative forms */
224     if ((P->Flags & fHash) != 0) {
225         if (P->Base == 16) {
226             /* Start with 0x */
227             Lead[LeadCount++] = '0';
228             Lead[LeadCount++] = (P->Flags & fUpcase)? 'X' : 'x';
229         } else if (P->Base == 8) {
230             /* Alternative form for 'o': always add a leading zero. */
231             if (P->Prec <= P->ArgLen) {
232                 Lead[LeadCount++] = '0';
233             }
234         }
235     }
236
237     /* Determine the amount of precision padding needed */
238     if (P->ArgLen < P->Prec) {
239         PrecPadding = P->Prec - P->ArgLen;
240     } else {
241         PrecPadding = 0;
242     }
243
244     /* Determine the width padding needed */
245     if ((P->Flags & fWidth) != 0) {
246         int CurWidth = LeadCount + PrecPadding + P->ArgLen;
247         if (CurWidth < P->Width) {
248             WidthPadding = P->Width - CurWidth;
249         } else {
250             WidthPadding = 0;
251         }
252     } else {
253         WidthPadding = 0;
254     }
255
256     /* Output left space padding if any */
257     if ((P->Flags & (fMinus | fZero)) == 0 && WidthPadding > 0) {
258         AddPadding (P, ' ', WidthPadding);
259         WidthPadding = 0;
260     }
261
262     /* Leader */
263     for (I = 0; I < LeadCount; ++I) {
264         AddChar (P, Lead[I]);
265     }
266
267     /* Left zero padding if any */
268     if ((P->Flags & fZero) != 0 && WidthPadding > 0) {
269         AddPadding (P, '0', WidthPadding);
270         WidthPadding = 0;
271     }
272
273     /* Precision padding */
274     if (PrecPadding > 0) {
275         AddPadding (P, '0', PrecPadding);
276     }
277
278     /* The number itself. Beware: It's reversed! */
279     while (P->ArgLen > 0) {
280         AddChar (P, P->ArgBuf[--P->ArgLen]);
281     }
282
283     /* Right width padding if any */
284     if (WidthPadding > 0) {
285         AddPadding (P, ' ', WidthPadding);
286     }
287 }
288
289
290
291 static void FormatStr (PrintfCtrl* P, const char* Val)
292 /* Convert the string */
293 {
294     unsigned WidthPadding;
295
296     /* Get the string length limited to the precision. Beware: We cannot use
297      * strlen here, because if a precision is given, the string may not be
298      * zero terminated.
299      */
300     int Len;
301     if ((P->Flags & fPrec) != 0) {
302         const char* S = memchr (Val, '\0', P->Prec);
303         if (S == 0) {
304             /* Not zero terminated */
305             Len = P->Prec;
306         } else {
307             /* Terminating zero found */
308             Len = S - Val;
309         }
310     } else {
311         Len = strlen (Val);
312     }
313
314     /* Determine the width padding needed */
315     if ((P->Flags & fWidth) != 0 && P->Width > Len) {
316         WidthPadding = P->Width - Len;
317     } else {
318         WidthPadding = 0;
319     }
320
321     /* Output left padding */
322     if ((P->Flags & fMinus) != 0 && WidthPadding > 0) {
323         AddPadding (P, ' ', WidthPadding);
324         WidthPadding = 0;
325     }
326
327     /* Output the string */
328     while (Len--) {
329         AddChar (P, *Val++);
330     }
331
332     /* Output right padding if any */
333     if (WidthPadding > 0) {
334         AddPadding (P, ' ', WidthPadding);
335     }
336 }
337
338
339
340 int xvsnprintf (char* Buf, size_t Size, const char* Format, va_list ap)
341 /* A basic vsnprintf implementation. Does currently only support integer
342  * formats.
343  */
344 {
345     PrintfCtrl P;
346     int Done;
347     char F;
348     char SBuf[2];
349     const char* SPtr;
350
351
352     /* Initialize the control structure */
353     P.ap        = ap;
354     P.Buf       = Buf;
355     P.BufSize   = Size;
356     P.BufFill   = 0;
357
358     /* Parse the format string */
359     while ((F = *Format++) != '\0') {
360
361         if (F != '%') {
362             /* Not a format specifier, just copy */
363             AddChar (&P, F);
364             continue;
365         }
366
367         /* Check for %% */
368         if (*Format == '%') {
369             ++Format;
370             AddChar (&P, '%');
371             continue;
372         }
373
374         /* It's a format specifier. Check for flags. */
375         F = *Format++;
376         P.Flags = fNone;
377         Done = 0;
378         while (F != '\0' && !Done) {
379             switch (F) {
380                 case '-': P.Flags |= fMinus; F = *Format++; break;
381                 case '+': P.Flags |= fPlus;  F = *Format++; break;
382                 case ' ': P.Flags |= fSpace; F = *Format++; break;
383                 case '#': P.Flags |= fHash;  F = *Format++; break;
384                 case '0': P.Flags |= fZero;  F = *Format++; break;
385                 default:  Done     = 1;                     break;
386             }
387         }
388         /* Optional field width */
389         if (F == '*') {
390             P.Width = va_arg (P.ap, int);
391             /* A negative field width argument is taken as a - flag followed
392              * by a positive field width.
393              */
394             if (P.Width < 0) {
395                 P.Flags |= fMinus;
396                 P.Width = -P.Width;
397             }
398             F = *Format++;
399             P.Flags |= fWidth;
400         } else if (IsDigit (F)) {
401             P.Width = F - '0';
402             while (1) {
403                 F = *Format++;
404                 if (!IsDigit (F)) {
405                     break;
406                 }
407                 P.Width = P.Width * 10 + (F - '0');
408             }
409             P.Flags |= fWidth;
410         }
411
412         /* Optional precision */
413         if (F == '.') {
414             F = *Format++;
415             P.Flags |= fPrec;
416             if (F == '*') {
417                 P.Prec = va_arg (P.ap, int);
418                 /* A negative precision argument is taken as if the precision
419                  * were omitted.
420                  */
421                 if (P.Prec < 0) {
422                     P.Flags &= ~fPrec;
423                 }
424                 F = *Format++;          /* Skip the '*' */
425             } else if (IsDigit (F)) {
426                 P.Prec = F - '0';
427                 while (1) {
428                     F = *Format++;
429                     if (!IsDigit (F)) {
430                         break;
431                     }
432                     P.Prec = P.Prec * 10 + (F - '0');
433                 }
434             } else if (F == '-') {
435                 /* A negative precision argument is taken as if the precision
436                  * were omitted.
437                  */
438                 F = *Format++;          /* Skip the minus */
439                 while (IsDigit (F = *Format++)) ;
440                 P.Flags &= ~fPrec;
441             } else {
442                 P.Prec = 0;
443             }
444         }
445
446         /* Optional length modifier */
447         P.LengthMod = lmDefault;
448         switch (F) {
449
450             case 'h':
451                 F = *Format++;
452                 if (F == 'h') {
453                     F = *Format++;
454                     P.LengthMod = lmChar;
455                 } else {
456                     P.LengthMod = lmShort;
457                 }
458                 break;
459
460             case 'l':
461                 F = *Format++;
462                 if (F == 'l') {
463                     F = *Format++;
464                     P.LengthMod = lmLongLong;
465                 } else {
466                     P.LengthMod = lmLong;
467                 }
468                 break;
469
470             case 'j':
471                 P.LengthMod = lmIntMax;
472                 F = *Format++;
473                 break;
474
475             case 'z':
476                 P.LengthMod = lmSizeT;
477                 F = *Format++;
478                 break;
479
480             case 't':
481                 P.LengthMod = lmPtrDiffT;
482                 F = *Format++;
483                 break;
484
485             case 'L':
486                 P.LengthMod = lmLongDouble;
487                 F = *Format++;
488                 break;
489
490         }
491
492         /* If the space and + flags both appear, the space flag is ignored */
493         if ((P.Flags & (fSpace | fPlus)) == (fSpace | fPlus)) {
494             P.Flags &= ~fSpace;
495         }
496         /* If the 0 and - flags both appear, the 0 flag is ignored */
497         if ((P.Flags & (fZero | fMinus)) == (fZero | fMinus)) {
498             P.Flags &= ~fZero;
499         }
500         /* If a precision is specified, the 0 flag is ignored */
501         if (P.Flags & fPrec) {
502             P.Flags &= ~fZero;
503         }
504
505         /* Conversion specifier */
506         switch (F) {
507
508             case 'd':
509             case 'i':
510                 P.Base = 10;
511                 FormatInt (&P, NextIVal (&P));
512                 break;
513
514             case 'o':
515                 P.Flags |= fUnsigned;
516                 P.Base = 8;
517                 FormatInt (&P, NextUVal (&P));
518                 break;
519
520             case 'u':
521                 P.Flags |= fUnsigned;
522                 P.Base = 10;
523                 FormatInt (&P, NextUVal (&P));
524                 break;
525
526             case 'X':
527                 P.Flags |= (fUnsigned | fUpcase);
528                 /* FALLTHROUGH */
529             case 'x':
530                 P.Base = 16;
531                 FormatInt (&P, NextUVal (&P));
532                 break;
533
534             case 'c':
535                 SBuf[0] = (char) va_arg (P.ap, int);
536                 SBuf[1] = '\0';
537                 FormatStr (&P, SBuf);
538                 break;
539
540             case 's':
541                 SPtr = va_arg (P.ap, const char*);
542                 CHECK (SPtr != 0);
543                 FormatStr (&P, SPtr);
544                 break;
545
546             default:
547                 /* Invalid format spec */
548                 FAIL ("Invalid format specifier in xvsnprintf");
549
550         }
551     }
552
553     /* Terminate the output string and return the number of chars that had
554      * been written if the buffer was large enough.
555      * Beware: The terminating zero is not counted for the function result!
556      */
557     AddChar (&P, '\0');
558     return P.BufFill - 1;
559 }
560
561
562
563 int xsnprintf (char* Buf, size_t Size, const char* Format, ...)
564 /* A basic snprintf implementation. Does currently only support integer
565  * formats.
566  */
567 {
568     int Res;
569     va_list ap;
570
571     va_start (ap, Format);
572     Res = xvsnprintf (Buf, Size, Format, ap);
573     va_end (ap);
574
575     return Res;
576 }
577
578
579
580 /*****************************************************************************/
581 /*                                   Code                                    */
582 /*****************************************************************************/
583
584
585
586 int xsprintf (char* Buf, size_t BufSize, const char* Format, ...)
587 /* Replacement function for sprintf */
588 {
589     int Res;
590     va_list ap;
591
592     va_start (ap, Format);
593     Res = xvsprintf (Buf, BufSize, Format, ap);
594     va_end (ap);
595
596     return Res;
597 }
598
599
600
601 int xvsprintf (char* Buf, size_t BufSize, const char* Format, va_list ap)
602 /* Replacement function for sprintf */
603 {
604     int Res = xvsnprintf (Buf, BufSize, Format, ap);
605     CHECK (Res >= 0 && (unsigned) (Res+1) < BufSize);
606     return Res;
607 }
608
609
610