progress on all fronts
[com32] / src / libc / stdio.c
1 /*
2 pcboot - bootable PC demo/game kernel
3 Copyright (C) 2018-2019  John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY, without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18 #include <stdio.h>
19 #include <string.h>
20 #include <ctype.h>
21 #include <errno.h>
22 #include "contty.h"
23 #include "serial.h"
24 #include "panic.h"
25
26 enum {
27         OUT_DEF,
28         OUT_BUF,
29         OUT_SCR,
30         OUT_SER
31 };
32
33 extern void pcboot_putchar(int c);
34
35 static int intern_printf(int out, char *buf, size_t sz, const char *fmt, va_list ap);
36 static int intern_scanf(const char *instr, FILE *infile, const char *fmt, va_list ap);
37 static void bwrite(int out, char *buf, size_t buf_sz, char *str, int sz);
38 /*static int readchar(const char *str, FILE *fp);*/
39
40 int putchar(int c)
41 {
42         con_putchar(c);
43         return c;
44 }
45
46 int puts(const char *s)
47 {
48         while(*s) {
49                 putchar(*s++);
50         }
51         putchar('\n');
52         return 0;
53 }
54
55 /* -- printf and friends -- */
56
57 int printf(const char *fmt, ...)
58 {
59         int res;
60         va_list ap;
61
62         va_start(ap, fmt);
63         res = intern_printf(OUT_DEF, 0, 0, fmt, ap);
64         va_end(ap);
65         return res;
66 }
67
68 int vprintf(const char *fmt, va_list ap)
69 {
70         return intern_printf(OUT_DEF, 0, 0, fmt, ap);
71 }
72
73 int sprintf(char *buf, const char *fmt, ...)
74 {
75         int res;
76         va_list ap;
77
78         va_start(ap, fmt);
79         res = intern_printf(OUT_BUF, buf, 0, fmt, ap);
80         va_end(ap);
81         return res;
82 }
83
84 int vsprintf(char *buf, const char *fmt, va_list ap)
85 {
86         return intern_printf(OUT_BUF, buf, 0, fmt, ap);
87 }
88
89 int snprintf(char *buf, size_t sz, const char *fmt, ...)
90 {
91         int res;
92         va_list ap;
93
94         va_start(ap, fmt);
95         res = intern_printf(OUT_BUF, buf, sz, fmt, ap);
96         va_end(ap);
97         return res;
98 }
99
100 int vsnprintf(char *buf, size_t sz, const char *fmt, va_list ap)
101 {
102         return intern_printf(OUT_BUF, buf, sz, fmt, ap);
103 }
104
105 int fprintf(FILE *fp, const char *fmt, ...)
106 {
107         int res;
108         va_list ap;
109
110         va_start(ap, fmt);
111         res = vfprintf(fp, fmt, ap);
112         va_end(ap);
113         return res;
114 }
115
116 int vfprintf(FILE *fp, const char *fmt, va_list ap)
117 {
118         if(fp == stdout || fp == stderr) {
119                 return vprintf(fmt, ap);
120         }
121
122         panic("*fprintf for anything other than stdout/stderr, not implemented yet\n");
123         return 0;
124 }
125
126 int ser_printf(const char *fmt, ...)
127 {
128         int res;
129         va_list ap;
130
131         va_start(ap, fmt);
132         res = intern_printf(OUT_SER, 0, 0, fmt, ap);
133         va_end(ap);
134         return res;
135 }
136
137 int ser_vprintf(const char *fmt, va_list ap)
138 {
139         return intern_printf(OUT_SER, 0, 0, fmt, ap);
140 }
141
142 /*
143 void perror(const char *s)
144 {
145         printf("%s: %s\n", s, strerror(errno));
146 }
147 */
148
149 /* intern_printf provides all the functionality needed by all the printf
150  * variants.
151  * - buf: optional buffer onto which the formatted results are written. If null
152  *   then the output goes to the terminal through putchar calls. This is used
153  *   by the (v)sprintf variants which write to an array of char.
154  * - sz: optional maximum size of the output, 0 means unlimited. This is used
155  *   by the (v)snprintf variants to avoid buffer overflows.
156  * The rest are obvious, format string and variable argument list.
157  */
158 static char *convc = "dioxXucsfeEgGpn%";
159
160 #define IS_CONV(c)      strchr(convc, c)
161
162 #define BUF(x)  ((x) ? (x) + cnum : (x))
163 #define SZ(x)   ((x) ? (x) - cnum : (x))
164
165 static int intern_printf(int out, char *buf, size_t sz, const char *fmt, va_list ap)
166 {
167         char conv_buf[32];
168         char *str;
169         int i, slen;
170         const char *fstart = 0;
171
172         /* state */
173         int cnum = 0;
174         int base = 10;
175         int alt = 0;
176         int fwidth = 0;
177         int padc = ' ';
178         int sign = 0;
179         int left_align = 0;
180         int hex_caps = 0;
181         int unsig = 0;
182         int num, unum;
183
184         while(*fmt) {
185                 if(*fmt == '%') {
186                         fstart = fmt++;
187                         continue;
188                 }
189
190                 if(fstart) {
191                         if(IS_CONV(*fmt)) {
192                                 switch(*fmt) {
193                                 case 'X':
194                                         hex_caps = 1;
195
196                                 case 'x':
197                                 case 'p':
198                                         base = 16;
199
200                                         if(alt) {
201                                                 bwrite(out, BUF(buf), SZ(sz), "0x", 2);
202                                                 cnum += 2;
203                                         }
204
205                                 case 'u':
206                                         unsig = 1;
207
208                                         if(0) {
209                                 case 'o':
210                                                 base = 8;
211
212                                                 if(alt) {
213                                                         bwrite(out, BUF(buf), SZ(sz), "0", 1);
214                                                         cnum++;
215                                                 }
216                                         }
217
218                                 case 'd':
219                                 case 'i':
220                                         if(unsig) {
221                                                 unum = va_arg(ap, unsigned int);
222                                                 utoa(unum, conv_buf, base);
223                                         } else {
224                                                 num = va_arg(ap, int);
225                                                 itoa(num, conv_buf, base);
226                                         }
227                                         if(hex_caps) {
228                                                 for(i=0; conv_buf[i]; i++) {
229                                                         conv_buf[i] = toupper(conv_buf[i]);
230                                                 }
231                                         }
232
233                                         slen = strlen(conv_buf);
234
235                                         if(left_align) {
236                                                 if(!unsig && sign && num >= 0) {
237                                                         bwrite(out, BUF(buf), SZ(sz), "+", 1);
238                                                         cnum++;
239                                                 }
240                                                 bwrite(out, BUF(buf), SZ(sz), conv_buf, slen);
241                                                 cnum += slen;
242                                                 padc = ' ';
243                                         }
244                                         for(i=slen; i<fwidth; i++) {
245                                                 bwrite(out, BUF(buf), SZ(sz), (char*)&padc, 1);
246                                                 cnum++;
247                                         }
248                                         if(!left_align) {
249                                                 if(!unsig && sign && num >= 0) {
250                                                         bwrite(out, BUF(buf), SZ(sz), "+", 1);
251                                                         cnum++;
252                                                 }
253                                                 bwrite(out, BUF(buf), SZ(sz), conv_buf, slen);
254                                                 cnum += slen;
255                                         }
256                                         break;
257
258                                 case 'c':
259                                         {
260                                                 char c = va_arg(ap, int);
261                                                 bwrite(out, BUF(buf), SZ(sz), &c, 1);
262                                                 cnum++;
263                                         }
264                                         break;
265
266                                 case 's':
267                                         str = va_arg(ap, char*);
268                                         slen = strlen(str);
269
270                                         if(left_align) {
271                                                 bwrite(out, BUF(buf), SZ(sz), str, slen);
272                                                 cnum += slen;
273                                                 padc = ' ';
274                                         }
275                                         for(i=slen; i<fwidth; i++) {
276                                                 bwrite(out, BUF(buf), SZ(sz), (char*)&padc, 1);
277                                                 cnum++;
278                                         }
279                                         if(!left_align) {
280                                                 bwrite(out, BUF(buf), SZ(sz), str, slen);
281                                                 cnum += slen;
282                                         }
283                                         break;
284
285                                 case 'n':
286                                         *va_arg(ap, int*) = cnum;
287                                         break;
288
289                                 default:
290                                         break;
291                                 }
292
293                                 /* restore default conversion state */
294                                 base = 10;
295                                 alt = 0;
296                                 fwidth = 0;
297                                 padc = ' ';
298                                 hex_caps = 0;
299
300                                 fstart = 0;
301                                 fmt++;
302                         } else {
303                                 switch(*fmt) {
304                                 case '#':
305                                         alt = 1;
306                                         break;
307
308                                 case '+':
309                                         sign = 1;
310                                         break;
311
312                                 case '-':
313                                         left_align = 1;
314                                         break;
315
316                                 case 'l':
317                                 case 'L':
318                                         break;
319
320                                 case '0':
321                                         padc = '0';
322                                         break;
323
324                                 default:
325                                         if(isdigit(*fmt)) {
326                                                 const char *fw = fmt;
327                                                 while(*fmt && isdigit(*fmt)) fmt++;
328
329                                                 fwidth = atoi(fw);
330                                                 continue;
331                                         }
332                                 }
333                                 fmt++;
334                         }
335                 } else {
336                         bwrite(out, BUF(buf), SZ(sz), (char*)fmt++, 1);
337                         cnum++;
338                 }
339         }
340
341         return cnum;
342 }
343
344
345 #if 0
346 static char *sconvc = "diouxcsefg%";
347
348 #define IS_SCONV(c)     strchr(sconvc, c)
349
350 static int intern_scanf(const char *instr, FILE *infile, const char *fmt, va_list ap)
351 {
352         return -1;      /* TODO */
353 }
354 #endif
355
356
357 /* bwrite is called by intern_printf to transparently handle writing into a
358  * buffer or to the terminal
359  */
360 static void bwrite(int out, char *buf, size_t buf_sz, char *str, int sz)
361 {
362         int i;
363
364         if(out == OUT_BUF) {
365                 if(buf_sz && buf_sz <= sz) sz = buf_sz;
366                 buf[sz] = 0;
367                 memcpy(buf, str, sz);
368         } else {
369                 switch(out) {
370                 case OUT_DEF:
371                         for(i=0; i<sz; i++) {
372                                 putchar(*str++);
373                         }
374                         break;
375
376                 case OUT_SER:
377                         for(i=0; i<sz; i++) {
378                                 ser_putchar(*str++);
379                         }
380                         break;
381
382                 default:
383                         /* TODO: OUT_SCR */
384                         break;
385                 }
386         }
387 }
388
389 /*
390 static int readchar(const char *str, FILE *fp)
391 {
392         static const char *orig_str;
393         static const char *sptr;
394
395         if(str) {
396                 if(str == orig_str) {
397                         if(!*sptr) return -1;
398                         return *sptr++;
399                 } else {
400                         orig_str = sptr = str;
401                         return readchar(str, fp);
402                 }
403         } else {
404                 return fgetc(fp);
405         }
406
407         return -1;
408 }
409 */