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; 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 | |
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 | } |
679 | |