chiark / gitweb /
volume_id: provide libvolume_id.a file
[elogind.git] / klibc / klibc / vsnprintf.c
1 /*
2  * vsnprintf.c
3  *
4  * vsnprintf(), from which the rest of the printf()
5  * family is built
6  */
7
8 #include <stdarg.h>
9 #include <stddef.h>
10 #include <inttypes.h>
11 #include <string.h>
12 #include <limits.h>
13 #include <stdio.h>
14
15 enum flags {
16   FL_ZERO   = 0x01,             /* Zero modifier */
17   FL_MINUS  = 0x02,             /* Minus modifier */
18   FL_PLUS   = 0x04,             /* Plus modifier */
19   FL_TICK   = 0x08,             /* ' modifier */
20   FL_SPACE  = 0x10,             /* Space modifier */
21   FL_HASH   = 0x20,             /* # modifier */
22   FL_SIGNED = 0x40,             /* Number is signed */
23   FL_UPPER  = 0x80              /* Upper case digits */
24 };
25
26 /* These may have to be adjusted on certain implementations */
27 enum ranks {
28   rank_char     = -2,
29   rank_short    = -1,
30   rank_int      = 0,
31   rank_long     = 1,
32   rank_longlong = 2
33 };
34
35 #define MIN_RANK        rank_char
36 #define MAX_RANK        rank_longlong
37
38 #define INTMAX_RANK     rank_longlong
39 #define SIZE_T_RANK     rank_long
40 #define PTRDIFF_T_RANK  rank_long
41
42 #define EMIT(x) ({ if (o<n){*q++ = (x);} o++; })
43
44 static size_t
45 format_int(char *q, size_t n, uintmax_t val, enum flags flags,
46            int base, int width, int prec)
47 {
48   char *qq;
49   size_t o = 0, oo;
50   static const char lcdigits[] = "0123456789abcdef";
51   static const char ucdigits[] = "0123456789ABCDEF";
52   const char *digits;
53   uintmax_t tmpval;
54   int minus = 0;
55   int ndigits = 0, nchars;
56   int tickskip, b4tick;
57
58   /* Select type of digits */
59   digits = (flags & FL_UPPER) ? ucdigits : lcdigits;
60
61   /* If signed, separate out the minus */
62   if ( flags & FL_SIGNED && (intmax_t)val < 0 ) {
63     minus = 1;
64     val = (uintmax_t)(-(intmax_t)val);
65   }
66
67   /* Count the number of digits needed.  This returns zero for 0. */
68   tmpval = val;
69   while ( tmpval ) {
70     tmpval /= base;
71     ndigits++;
72   }
73
74   /* Adjust ndigits for size of output */
75
76   if ( flags & FL_HASH && base == 8 ) {
77     if ( prec < ndigits+1 )
78       prec = ndigits+1;
79   }
80
81   if ( ndigits < prec ) {
82     ndigits = prec;             /* Mandatory number padding */
83   } else if ( val == 0 ) {
84     ndigits = 1;                /* Zero still requires space */
85   }
86
87   /* For ', figure out what the skip should be */
88   if ( flags & FL_TICK ) {
89     tickskip = (base == 16) ? 4 : 3;
90   } else {
91     tickskip = ndigits;         /* No tick marks */
92   }
93
94   /* Tick marks aren't digits, but generated by the number converter */
95   ndigits += (ndigits-1)/tickskip;
96
97   /* Now compute the number of nondigits */
98   nchars = ndigits;
99
100   if ( minus || (flags & (FL_PLUS|FL_SPACE)) )
101     nchars++;                   /* Need space for sign */
102   if ( (flags & FL_HASH) && base == 16 ) {
103     nchars += 2;                /* Add 0x for hex */
104   }
105
106   /* Emit early space padding */
107   if ( !(flags & (FL_MINUS|FL_ZERO)) && width > nchars ) {
108     while ( width > nchars ) {
109       EMIT(' ');
110       width--;
111     }
112   }
113
114   /* Emit nondigits */
115   if ( minus )
116     EMIT('-');
117   else if ( flags & FL_PLUS )
118     EMIT('+');
119   else if ( flags & FL_SPACE )
120     EMIT(' ');
121
122   if ( (flags & FL_HASH) && base == 16 ) {
123     EMIT('0');
124     EMIT((flags & FL_UPPER) ? 'X' : 'x');
125   }
126
127   /* Emit zero padding */
128   if ( (flags & (FL_MINUS|FL_ZERO)) == FL_ZERO && width > ndigits ) {
129     while ( width > nchars ) {
130       EMIT('0');
131       width--;
132     }
133   }
134
135   /* Generate the number.  This is done from right to left. */
136   q += ndigits;                 /* Advance the pointer to end of number */
137   o += ndigits;
138   qq = q; oo = o;               /* Temporary values */
139
140   b4tick = tickskip;
141   while ( ndigits > 0 ) {
142     if ( !b4tick-- ) {
143       qq--; oo--; ndigits--;
144       if ( oo < n ) *qq = '_';
145       b4tick = tickskip-1;
146     }
147     qq--; oo--; ndigits--;
148     if ( oo < n ) *qq = digits[val%base];
149     val /= base;
150   }
151
152   /* Emit late space padding */
153   while ( (flags & FL_MINUS) && width > nchars ) {
154     EMIT(' ');
155     width--;
156   }
157
158   return o;
159 }
160
161
162 int vsnprintf(char *buffer, size_t n, const char *format, va_list ap)
163 {
164   const char *p = format;
165   char ch;
166   char *q = buffer;
167   size_t o = 0;                 /* Number of characters output */
168   uintmax_t val = 0;
169   int rank = rank_int;          /* Default rank */
170   int width = 0;
171   int prec  = -1;
172   int base;
173   size_t sz;
174   enum flags flags = 0;
175   enum {
176     st_normal,                  /* Ground state */
177     st_flags,                   /* Special flags */
178     st_width,                   /* Field width */
179     st_prec,                    /* Field precision */
180     st_modifiers                /* Length or conversion modifiers */
181   } state = st_normal;
182   const char *sarg;             /* %s string argument */
183   char carg;                    /* %c char argument */
184   int slen;                     /* String length */
185
186   while ( (ch = *p++) ) {
187     switch ( state ) {
188     case st_normal:
189       if ( ch == '%' ) {
190         state = st_flags;
191         flags = 0; rank = rank_int; width = 0; prec = -1;
192       } else {
193         EMIT(ch);
194       }
195       break;
196
197     case st_flags:
198       switch ( ch ) {
199       case '-':
200         flags |= FL_MINUS;
201         break;
202       case '+':
203         flags |= FL_PLUS;
204         break;
205       case '\'':
206         flags |= FL_TICK;
207         break;
208       case ' ':
209         flags |= FL_SPACE;
210         break;
211       case '#':
212         flags |= FL_HASH;
213         break;
214       case '0':
215         flags |= FL_ZERO;
216         break;
217       default:
218         state = st_width;
219         p--;                    /* Process this character again */
220         break;
221       }
222       break;
223
224     case st_width:
225       if ( ch >= '0' && ch <= '9' ) {
226         width = width*10+(ch-'0');
227       } else if ( ch == '*' ) {
228         width = va_arg(ap, int);
229         if ( width < 0 ) {
230           width = -width;
231           flags |= FL_MINUS;
232         }
233       } else if ( ch == '.' ) {
234         prec = 0;               /* Precision given */
235         state = st_prec;
236       } else {
237         state = st_modifiers;
238         p--;                    /* Process this character again */
239       }
240       break;
241
242     case st_prec:
243       if ( ch >= '0' && ch <= '9' ) {
244         prec = prec*10+(ch-'0');
245       } else if ( ch == '*' ) {
246         prec = va_arg(ap, int);
247         if ( prec < 0 )
248           prec = -1;
249       } else {
250         state = st_modifiers;
251         p--;                    /* Process this character again */
252       }
253       break;
254
255     case st_modifiers:
256       switch ( ch ) {
257         /* Length modifiers - nonterminal sequences */
258       case 'h':
259         rank--;                 /* Shorter rank */
260         break;
261       case 'l':
262         rank++;                 /* Longer rank */
263         break;
264       case 'j':
265         rank = INTMAX_RANK;
266         break;
267       case 'z':
268         rank = SIZE_T_RANK;
269         break;
270       case 't':
271         rank = PTRDIFF_T_RANK;
272         break;
273       case 'L':
274       case 'q':
275         rank += 2;
276         break;
277       default:
278         /* Output modifiers - terminal sequences */
279         state = st_normal;      /* Next state will be normal */
280         if ( rank < MIN_RANK )  /* Canonicalize rank */
281           rank = MIN_RANK;
282         else if ( rank > MAX_RANK )
283           rank = MAX_RANK;
284
285         switch ( ch ) {
286         case 'P':               /* Upper case pointer */
287           flags |= FL_UPPER;
288           /* fall through */
289         case 'p':               /* Pointer */
290           base = 16;
291           prec = (CHAR_BIT*sizeof(void *)+3)/4;
292           flags |= FL_HASH;
293           val = (uintmax_t)(uintptr_t)va_arg(ap, void *);
294           goto is_integer;
295
296         case 'd':               /* Signed decimal output */
297         case 'i':
298           base = 10;
299           flags |= FL_SIGNED;
300           switch (rank) {
301           case rank_char:
302             /* Yes, all these casts are needed... */
303             val = (uintmax_t)(intmax_t)(signed char)va_arg(ap, signed int);
304             break;
305           case rank_short:
306             val = (uintmax_t)(intmax_t)(signed short)va_arg(ap, signed int);
307             break;
308           case rank_int:
309             val = (uintmax_t)(intmax_t)va_arg(ap, signed int);
310             break;
311           case rank_long:
312             val = (uintmax_t)(intmax_t)va_arg(ap, signed long);
313             break;
314           case rank_longlong:
315             val = (uintmax_t)(intmax_t)va_arg(ap, signed long long);
316             break;
317           }
318           goto is_integer;
319         case 'o':               /* Octal */
320           base = 8;
321           goto is_unsigned;
322         case 'u':               /* Unsigned decimal */
323           base = 10;
324           goto is_unsigned;
325         case 'X':               /* Upper case hexadecimal */
326           flags |= FL_UPPER;
327           /* fall through */
328         case 'x':               /* Hexadecimal */
329           base = 16;
330           goto is_unsigned;
331
332         is_unsigned:
333           switch (rank) {
334           case rank_char:
335             val = (uintmax_t)(unsigned char)va_arg(ap, unsigned int);
336             break;
337           case rank_short:
338             val = (uintmax_t)(unsigned short)va_arg(ap, unsigned int);
339             break;
340           case rank_int:
341             val = (uintmax_t)va_arg(ap, unsigned int);
342             break;
343           case rank_long:
344             val = (uintmax_t)va_arg(ap, unsigned long);
345             break;
346           case rank_longlong:
347             val = (uintmax_t)va_arg(ap, unsigned long long);
348             break;
349           }
350           /* fall through */
351
352         is_integer:
353           sz = format_int(q, (o<n) ? n-o : 0, val, flags, base, width, prec);
354           q += sz; o += sz;
355           break;
356
357         case 'c':               /* Character */
358           carg = (char)va_arg(ap, int);
359           sarg = &carg;
360           slen = 1;
361           goto is_string;
362         case 's':               /* String */
363           sarg = va_arg(ap, const char *);
364           sarg = sarg ? sarg : "(null)";
365           slen = strlen(sarg);
366           goto is_string;
367
368         is_string:
369           {
370             char sch;
371             int i;
372             
373             if ( prec != -1 && slen > prec )
374               slen = prec;
375             
376             if ( width > slen && !(flags & FL_MINUS) ) {
377               char pad = (flags & FL_ZERO) ? '0' : ' ';
378               while ( width > slen ) {
379                 EMIT(pad);
380                 width--;
381               }
382             }
383             for ( i = slen ; i ; i-- ) {
384               sch = *sarg++;
385               EMIT(sch);
386             }
387             if ( width > slen && (flags & FL_MINUS) ) {
388               while ( width > slen ) {
389                 EMIT(' ');
390                 width--;
391               }
392             }
393           }
394           break;
395
396         case 'n':               /* Output the number of characters written */
397           {
398             switch (rank) {
399             case rank_char:
400               *va_arg(ap, signed char *) = o;
401               break;
402             case rank_short:
403               *va_arg(ap, signed short *) = o;
404               break;
405             case rank_int:
406               *va_arg(ap, signed int *) = o;
407               break;
408             case rank_long:
409               *va_arg(ap, signed long *) = o;
410               break;
411             case rank_longlong:
412               *va_arg(ap, signed long long *) = o;
413               break;
414             }
415           }
416           break;
417           
418         default:                /* Anything else, including % */
419           EMIT(ch);
420           break;
421         }
422       }
423     }
424   }
425
426   /* Null-terminate the string */
427   if ( o<n )
428     *q = '\0';                  /* No overflow */
429   else if ( n>0 )
430     buffer[n-1] = '\0';         /* Overflow - terminate at end of buffer */
431
432   return o;
433 }