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
65typedef 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
128static 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
138static 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
148static 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
167static 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
186static 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
199static 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
302static 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
351static 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; break;
356 case lmShort: *va_arg (P->ap, int*) = P->BufFill; break;
357 case lmInt: *va_arg (P->ap, int*) = P->BufFill; break;
358 case lmLong: *va_arg (P->ap, long*) = P->BufFill; break;
359 case lmIntMax: *va_arg (P->ap, intmax_t*) = P->BufFill; break;
360 case lmSizeT: *va_arg (P->ap, size_t*) = P->BufFill; break;
361 case lmPtrDiffT: *va_arg (P->ap, ptrdiff_t*) = P->BufFill; break;
362 default: FAIL ("Invalid size modifier for %n format spec. in xvsnprintf()");
363 }
364}
365
366
367
368int 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
634int 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
657int 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
672int 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}
679