Another round at reorganization
[akaros.git] / kern / src / printfmt.c
1 // Stripped-down primitive printf-style formatting routines,
2 // used in common by printf, sprintf, fprintf, etc.
3 // This code is also used by both the kernel and user programs.
4
5 #ifdef __DEPUTY__
6 #pragma nodeputy
7 #endif
8
9 #include <arch/types.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <stdarg.h>
13 #include <error.h>
14
15 /*
16  * Space or zero padding and a field width are supported for the numeric
17  * formats only. 
18  * 
19  * The special format %e takes an integer error code
20  * and prints a string describing the error.
21  * The integer may be positive or negative,
22  * so that -E_NO_MEM and E_NO_MEM are equivalent.
23  */
24
25 static const char * const error_string[MAXERROR + 1] =
26 {
27         NULL,
28         "unspecified error",
29         "bad environment",
30         "invalid parameter",
31         "out of memory",
32         "out of environments",
33         "segmentation fault",
34 };
35
36 /*
37  * Print a number (base <= 16) in reverse order,
38  * using specified putch function and associated pointer putdat.
39  */
40 static void printnum(void (*putch)(int, void**), void **putdat,
41                          unsigned long long num, unsigned base, int width, int padc)
42 {
43         // first recursively print all preceding (more significant) digits
44         if (num >= base) {
45                 printnum(putch, putdat, num / base, base, width - 1, padc);
46         } else {
47                 // print any needed pad characters before first digit
48                 while (--width > 0)
49                         putch(padc, putdat);
50         }
51
52         // then print this (the least significant) digit
53         putch("0123456789abcdef"[num % base], putdat);
54 }
55
56 // Get an unsigned int of various possible sizes from a varargs list,
57 // depending on the lflag parameter.
58 static unsigned long long getuint(va_list *ap, int lflag)
59 {
60         if (lflag >= 2)
61                 return va_arg(*ap, unsigned long long);
62         else if (lflag)
63                 return va_arg(*ap, unsigned long);
64         else
65                 return va_arg(*ap, unsigned int);
66 }
67
68 // Same as getuint but signed - can't use getuint
69 // because of sign extension
70 static long long getint(va_list *ap, int lflag)
71 {
72         if (lflag >= 2)
73                 return va_arg(*ap, long long);
74         else if (lflag)
75                 return va_arg(*ap, long);
76         else
77                 return va_arg(*ap, int);
78 }
79
80
81 // Main function to format and print a string.
82 void printfmt(void (*putch)(int, void**), void **putdat, const char *fmt, ...);
83
84 void vprintfmt(void (*putch)(int, void**), void **putdat, const char *fmt, va_list ap)
85 {
86         register const char *p;
87         register int ch, err;
88         unsigned long long num;
89         int base, lflag, width, precision, altflag;
90         char padc;
91
92         while (1) {
93                 while ((ch = *(unsigned char *) fmt++) != '%') {
94                         if (ch == '\0')
95                                 return;
96                         putch(ch, putdat);
97                 }
98
99                 // Process a %-escape sequence
100                 padc = ' ';
101                 width = -1;
102                 precision = -1;
103                 lflag = 0;
104                 altflag = 0;
105         reswitch:
106                 switch (ch = *(unsigned char *) fmt++) {
107
108                 // flag to pad on the right
109                 case '-':
110                         padc = '-';
111                         goto reswitch;
112                         
113                 // flag to pad with 0's instead of spaces
114                 case '0':
115                         padc = '0';
116                         goto reswitch;
117
118                 // width field
119                 case '1':
120                 case '2':
121                 case '3':
122                 case '4':
123                 case '5':
124                 case '6':
125                 case '7':
126                 case '8':
127                 case '9':
128                         for (precision = 0; ; ++fmt) {
129                                 precision = precision * 10 + ch - '0';
130                                 ch = *fmt;
131                                 if (ch < '0' || ch > '9')
132                                         break;
133                         }
134                         goto process_precision;
135
136                 case '*':
137                         precision = va_arg(ap, int);
138                         goto process_precision;
139
140                 case '.':
141                         if (width < 0)
142                                 width = 0;
143                         goto reswitch;
144
145                 case '#':
146                         altflag = 1;
147                         goto reswitch;
148
149                 process_precision:
150                         if (width < 0)
151                                 width = precision, precision = -1;
152                         goto reswitch;
153
154                 // long flag (doubled for long long)
155                 case 'l':
156                         lflag++;
157                         goto reswitch;
158
159                 // character
160                 case 'c':
161                         putch(va_arg(ap, int), putdat);
162                         break;
163
164                 // error message
165                 case 'e':
166                         err = va_arg(ap, int);
167                         if (err < 0)
168                                 err = -err;
169                         if (err > MAXERROR || (p = error_string[err]) == NULL)
170                                 printfmt(putch, putdat, "error %d", err);
171                         else
172                                 printfmt(putch, putdat, "%s", p);
173                         break;
174
175                 // string
176                 case 's':
177                         if ((p = va_arg(ap, char *)) == NULL)
178                                 p = "(null)";
179                         if (width > 0 && padc != '-')
180                                 for (width -= strnlen(p, precision); width > 0; width--)
181                                         putch(padc, putdat);
182                         for (; (ch = *p++) != '\0' && (precision < 0 || --precision >= 0); width--)
183                                 if (altflag && (ch < ' ' || ch > '~'))
184                                         putch('?', putdat);
185                                 else
186                                         putch(ch, putdat);
187                         for (; width > 0; width--)
188                                 putch(' ', putdat);
189                         break;
190
191                 // (signed) decimal
192                 case 'd':
193                         num = getint(&ap, lflag);
194                         if ((long long) num < 0) {
195                                 putch('-', putdat);
196                                 num = -(long long) num;
197                         }
198                         base = 10;
199                         goto number;
200
201                 // unsigned decimal
202                 case 'u':
203                         num = getuint(&ap, lflag);
204                         base = 10;
205                         goto number;
206
207                 // (unsigned) octal
208                 case 'o':
209                         // should do something with padding so it's always 3 octits
210                         num = getuint(&ap, lflag);
211                         base = 8;
212                         goto number;
213
214                 // pointer
215                 case 'p':
216                         putch('0', putdat);
217                         putch('x', putdat);
218                         num = (unsigned long long)
219                                 (uintptr_t) va_arg(ap, void *);
220                         base = 16;
221                         goto number;
222
223                 // (unsigned) hexadecimal
224                 case 'x':
225                         num = getuint(&ap, lflag);
226                         base = 16;
227                 number:
228                         printnum(putch, putdat, num, base, width, padc);
229                         break;
230
231                 // escaped '%' character
232                 case '%':
233                         putch(ch, putdat);
234                         break;
235                         
236                 // unrecognized escape sequence - just print it literally
237                 default:
238                         putch('%', putdat);
239                         for (fmt--; fmt[-1] != '%'; fmt--)
240                                 /* do nothing */;
241                         break;
242                 }
243         }
244 }
245
246 void printfmt(void (*putch)(int, void**), void **putdat, const char *fmt, ...)
247 {
248         va_list ap;
249
250         va_start(ap, fmt);
251         vprintfmt(putch, putdat, fmt, ap);
252         va_end(ap);
253 }
254
255 typedef struct sprintbuf {
256         char *buf;
257         char *ebuf;
258         int cnt;
259 } sprintbuf_t;
260
261 static void sprintputch(int ch, sprintbuf_t **b)
262 {
263         (*b)->cnt++;
264         if ((*b)->buf < (*b)->ebuf)
265                 *((*b)->buf++) = ch;
266 }
267
268 int vsnprintf(char *buf, int n, const char *fmt, va_list ap)
269 {
270         sprintbuf_t b = {buf, buf+n-1, 0};
271         sprintbuf_t *bp = &b;
272
273         if (buf == NULL || n < 1)
274                 return -E_INVAL;
275
276         // print the string to the buffer
277         vprintfmt((void*)sprintputch, (void**)&bp, fmt, ap);
278
279         // null terminate the buffer
280         *b.buf = '\0';
281
282         return b.cnt;
283 }
284
285 int snprintf(char *buf, int n, const char *fmt, ...)
286 {
287         va_list ap;
288         int rc;
289
290         va_start(ap, fmt);
291         rc = vsnprintf(buf, n, fmt, ap);
292         va_end(ap);
293
294         return rc;
295 }
296
297