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