Deputy turned on. YOU NEED TO UPDATE YOUR IVY
[akaros.git] / lib / 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 <inc/types.h>
10 #include <inc/stdio.h>
11 #include <inc/string.h>
12 #include <inc/stdarg.h>
13 #include <inc/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
41 printnum(void (*putch)(int, void*), void *putdat,
42          unsigned long long num, unsigned base, int width, int padc)
43 {
44         // first recursively print all preceding (more significant) digits
45         if (num >= base) {
46                 printnum(putch, putdat, num / base, base, width - 1, padc);
47         } else {
48                 // print any needed pad characters before first digit
49                 while (--width > 0)
50                         putch(padc, putdat);
51         }
52
53         // then print this (the least significant) digit
54         putch("0123456789abcdef"[num % base], putdat);
55 }
56
57 // Get an unsigned int of various possible sizes from a varargs list,
58 // depending on the lflag parameter.
59 static unsigned long long
60 getuint(va_list *ap, int lflag)
61 {
62         if (lflag >= 2)
63                 return va_arg(*ap, unsigned long long);
64         else if (lflag)
65                 return va_arg(*ap, unsigned long);
66         else
67                 return va_arg(*ap, unsigned int);
68 }
69
70 // Same as getuint but signed - can't use getuint
71 // because of sign extension
72 static long long
73 getint(va_list *ap, int lflag)
74 {
75         if (lflag >= 2)
76                 return va_arg(*ap, long long);
77         else if (lflag)
78                 return va_arg(*ap, long);
79         else
80                 return va_arg(*ap, int);
81 }
82
83
84 // Main function to format and print a string.
85 void printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...);
86
87 void
88 vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap)
89 {
90         register const char *p;
91         register int ch, err;
92         unsigned long long num;
93         int base, lflag, width, precision, altflag;
94         char padc;
95
96         while (1) {
97                 while ((ch = *(unsigned char *) fmt++) != '%') {
98                         if (ch == '\0')
99                                 return;
100                         putch(ch, putdat);
101                 }
102
103                 // Process a %-escape sequence
104                 padc = ' ';
105                 width = -1;
106                 precision = -1;
107                 lflag = 0;
108                 altflag = 0;
109         reswitch:
110                 switch (ch = *(unsigned char *) fmt++) {
111
112                 // flag to pad on the right
113                 case '-':
114                         padc = '-';
115                         goto reswitch;
116                         
117                 // flag to pad with 0's instead of spaces
118                 case '0':
119                         padc = '0';
120                         goto reswitch;
121
122                 // width field
123                 case '1':
124                 case '2':
125                 case '3':
126                 case '4':
127                 case '5':
128                 case '6':
129                 case '7':
130                 case '8':
131                 case '9':
132                         for (precision = 0; ; ++fmt) {
133                                 precision = precision * 10 + ch - '0';
134                                 ch = *fmt;
135                                 if (ch < '0' || ch > '9')
136                                         break;
137                         }
138                         goto process_precision;
139
140                 case '*':
141                         precision = va_arg(ap, int);
142                         goto process_precision;
143
144                 case '.':
145                         if (width < 0)
146                                 width = 0;
147                         goto reswitch;
148
149                 case '#':
150                         altflag = 1;
151                         goto reswitch;
152
153                 process_precision:
154                         if (width < 0)
155                                 width = precision, precision = -1;
156                         goto reswitch;
157
158                 // long flag (doubled for long long)
159                 case 'l':
160                         lflag++;
161                         goto reswitch;
162
163                 // character
164                 case 'c':
165                         putch(va_arg(ap, int), putdat);
166                         break;
167
168                 // error message
169                 case 'e':
170                         err = va_arg(ap, int);
171                         if (err < 0)
172                                 err = -err;
173                         if (err > MAXERROR || (p = error_string[err]) == NULL)
174                                 printfmt(putch, putdat, "error %d", err);
175                         else
176                                 printfmt(putch, putdat, "%s", p);
177                         break;
178
179                 // string
180                 case 's':
181                         if ((p = va_arg(ap, char *)) == NULL)
182                                 p = "(null)";
183                         if (width > 0 && padc != '-')
184                                 for (width -= strnlen(p, precision); width > 0; width--)
185                                         putch(padc, putdat);
186                         for (; (ch = *p++) != '\0' && (precision < 0 || --precision >= 0); width--)
187                                 if (altflag && (ch < ' ' || ch > '~'))
188                                         putch('?', putdat);
189                                 else
190                                         putch(ch, putdat);
191                         for (; width > 0; width--)
192                                 putch(' ', putdat);
193                         break;
194
195                 // (signed) decimal
196                 case 'd':
197                         num = getint(&ap, lflag);
198                         if ((long long) num < 0) {
199                                 putch('-', putdat);
200                                 num = -(long long) num;
201                         }
202                         base = 10;
203                         goto number;
204
205                 // unsigned decimal
206                 case 'u':
207                         num = getuint(&ap, lflag);
208                         base = 10;
209                         goto number;
210
211                 // (unsigned) octal
212                 case 'o':
213                         // should do something with padding so it's always 3 octits
214                         num = getuint(&ap, lflag);
215                         base = 8;
216                         goto number;
217
218                 // pointer
219                 case 'p':
220                         putch('0', putdat);
221                         putch('x', putdat);
222                         num = (unsigned long long)
223                                 (uintptr_t) va_arg(ap, void *);
224                         base = 16;
225                         goto number;
226
227                 // (unsigned) hexadecimal
228                 case 'x':
229                         num = getuint(&ap, lflag);
230                         base = 16;
231                 number:
232                         printnum(putch, putdat, num, base, width, padc);
233                         break;
234
235                 // escaped '%' character
236                 case '%':
237                         putch(ch, putdat);
238                         break;
239                         
240                 // unrecognized escape sequence - just print it literally
241                 default:
242                         putch('%', putdat);
243                         for (fmt--; fmt[-1] != '%'; fmt--)
244                                 /* do nothing */;
245                         break;
246                 }
247         }
248 }
249
250 void
251 printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...)
252 {
253         va_list ap;
254
255         va_start(ap, fmt);
256         vprintfmt(putch, putdat, fmt, ap);
257         va_end(ap);
258 }
259
260 struct sprintbuf {
261         char *buf;
262         char *ebuf;
263         int cnt;
264 };
265
266 static void
267 sprintputch(int ch, struct sprintbuf *b)
268 {
269         b->cnt++;
270         if (b->buf < b->ebuf)
271                 *b->buf++ = ch;
272 }
273
274 int
275 vsnprintf(char *buf, int n, const char *fmt, va_list ap)
276 {
277         struct sprintbuf b = {buf, buf+n-1, 0};
278
279         if (buf == NULL || n < 1)
280                 return -E_INVAL;
281
282         // print the string to the buffer
283         vprintfmt((void*)sprintputch, &b, fmt, ap);
284
285         // null terminate the buffer
286         *b.buf = '\0';
287
288         return b.cnt;
289 }
290
291 int
292 snprintf(char *buf, int n, const char *fmt, ...)
293 {
294         va_list ap;
295         int rc;
296
297         va_start(ap, fmt);
298         rc = vsnprintf(buf, n, fmt, ap);
299         va_end(ap);
300
301         return rc;
302 }
303
304