57a83d8ddf069e3544f8753774ed833d4f43be96
[monolithium.git] / libraries / mlcrt / src / printf.c
1 /*
2  * printf.c
3  *
4  * Copyright (C) 2018 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as
8  * published by the Free Software Foundation, either version 3 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <stdio.h>
21 #include <string.h>
22 #include <stdlib.h>
23 #include <monolithium.h>
24 #include "io_priv.h"
25
26 #define __CRT_PRINTF_FLAG_ALIGN_LEFT         (1 << 0)
27 #define __CRT_PRINTF_FLAG_PLUS               (1 << 1)
28 #define __CRT_PRINTF_FLAG_SPACE              (1 << 2)
29 #define __CRT_PRINTF_FLAG_EXTRA              (1 << 3)
30 #define __CRT_PRINTF_FLAG_ZERO_PAD           (1 << 4)
31 #define __CRT_PRINTF_FLAG_EXTERNAL_WIDTH     (1 << 5)
32 #define __CRT_PRINTF_FLAG_EXTERNAL_PRECISION (1 << 6)
33 #define __CRT_PRINTF_FLAG_DEFAULT_WIDTH      (1 << 7)
34 #define __CRT_PRINTF_FLAG_DEFAULT_PRECISION  (1 << 8)
35
36 typedef struct
37 {
38     FILE *stream;
39     char *string;
40     size_t size;
41 } __crt_stream_or_string_t;
42
43 static int __crt_strputc(__crt_stream_or_string_t *str, char c)
44 {
45     if (str->stream)
46     {
47         return fputc_unlocked(c, str->stream) != EOF;
48     }
49     else
50     {
51         if (str->size > 1)
52         {
53             *str->string++ = c;
54             str->size--;
55         }
56
57         return 1;
58     }
59 }
60
61 static int __crt_strputs(__crt_stream_or_string_t *str, const char *s)
62 {
63     if (str->stream)
64     {
65         return fputs_unlocked(s, str->stream) != EOF ? strlen(s) : 0;
66     }
67     else
68     {
69         int length = strlen(s);
70
71         if (str->size > 1)
72         {
73             strncpy(str->string, s, str->size - 1);
74
75             if (length < str->size)
76             {
77                 str->string += length;
78                 str->size -= length;
79             }
80             else
81             {
82                 str->string += str->size - 1;
83                 str->size = 0;
84             }
85         }
86
87         return length;
88     }
89 }
90
91 static inline int __crt_vstrprintf(__crt_stream_or_string_t *str, const char *format, va_list ap)
92 {
93     int ret = 0;
94     if (str->stream) syscall_wait_mutex(str->stream->mutex, NO_TIMEOUT);
95
96     const char *ptr;
97     for (ptr = format; *ptr; ptr++)
98     {
99         if (*ptr == '%')
100         {
101             unsigned long flags = 0;
102             unsigned int width = 0, precision = 0;
103
104             ptr++;
105
106             while (*ptr)
107             {
108                 if (*ptr == '-') flags |= __CRT_PRINTF_FLAG_ALIGN_LEFT;
109                 else if (*ptr == '+') flags |= __CRT_PRINTF_FLAG_PLUS;
110                 else if (*ptr == ' ') flags |= __CRT_PRINTF_FLAG_SPACE;
111                 else if (*ptr == '#') flags |= __CRT_PRINTF_FLAG_EXTRA;
112                 else if (*ptr == '0') flags |= __CRT_PRINTF_FLAG_ZERO_PAD;
113                 else break;
114
115                 ptr++;
116             }
117
118             if (*ptr == '*') flags |= __CRT_PRINTF_FLAG_EXTERNAL_WIDTH;
119             else if (isdigit(*ptr)) width = strtoul(ptr, (char**)&ptr, 10);
120             else flags |= __CRT_PRINTF_FLAG_DEFAULT_WIDTH;
121
122             if (*ptr == '.')
123             {
124                 ptr++;
125                 if (*ptr == '*') flags |= __CRT_PRINTF_FLAG_EXTERNAL_PRECISION;
126                 else if (isdigit(*ptr)) precision = strtoul(ptr, (char**)&ptr, 10);
127                 else flags |= __CRT_PRINTF_FLAG_DEFAULT_PRECISION;
128             }
129             else
130             {
131                 flags |= __CRT_PRINTF_FLAG_DEFAULT_PRECISION;
132             }
133
134             int variable_size = 0;
135
136             if (*ptr == 'h')
137             {
138                 variable_size--;
139
140                 if (*++ptr == 'h')
141                 {
142                     variable_size--;
143                     ptr++;
144                 }
145             }
146             else if (*ptr == 'l')
147             {
148                 variable_size++;
149
150                 if (*++ptr == 'l')
151                 {
152                     variable_size++;
153                     ptr++;
154                 }
155             }
156
157             const char *data = NULL;
158             char buffer[512];
159             int radix = 10;
160
161             switch (*ptr)
162             {
163             case 's':
164                 data = va_arg(ap, const char*);
165                 break;
166
167             case 'c':
168                 data = buffer;
169                 buffer[0] = (char)va_arg(ap, int);
170                 buffer[1] = '\0';
171                 break;
172
173             case 'd':
174             case 'i':
175                 if (flags & __CRT_PRINTF_FLAG_DEFAULT_WIDTH) width = 0;
176                 if (flags & __CRT_PRINTF_FLAG_DEFAULT_PRECISION) precision = 1;
177                 data = buffer;
178
179                 switch (variable_size)
180                 {
181                 case -2: itoa((char)va_arg(ap, int), buffer, 10); break;
182                 case -1: itoa((short)va_arg(ap, int), buffer, 10); break;
183                 case  0: itoa(va_arg(ap, int), buffer, 10); break;
184                 case  1: ltoa(va_arg(ap, long), buffer, 10); break;
185                 case  2: lltoa(va_arg(ap, long long), buffer, 10); break;
186                 }
187                 break;
188
189             case 'o':
190                 radix = 8;
191                 goto read_variable;
192             case 'x':
193             case 'X':
194                 radix = 16;
195             case 'u':
196             read_variable:
197                 if (flags & __CRT_PRINTF_FLAG_DEFAULT_WIDTH) width = 0;
198                 if (flags & __CRT_PRINTF_FLAG_DEFAULT_PRECISION) precision = 1;
199                 data = buffer;
200
201                 switch (variable_size)
202                 {
203                 case -2: uitoa((unsigned char)va_arg(ap, unsigned int), buffer, radix); break;
204                 case -1: uitoa((unsigned short)va_arg(ap, unsigned int), buffer, radix); break;
205                 case  0: uitoa(va_arg(ap, unsigned int), buffer, radix); break;
206                 case  1: ultoa(va_arg(ap, unsigned long), buffer, radix); break;
207                 case  2: ulltoa(va_arg(ap, unsigned long long), buffer, radix); break;
208                 }
209                 break;
210
211             case '%':
212                 data = buffer;
213                 strcpy(buffer, "%");
214                 break;
215             }
216
217             if (flags & __CRT_PRINTF_FLAG_EXTERNAL_WIDTH) width = va_arg(ap, unsigned int);
218             if (flags & __CRT_PRINTF_FLAG_EXTERNAL_PRECISION) precision = va_arg(ap, unsigned int);
219
220             int length = strlen(data);
221             char padding = (flags &__CRT_PRINTF_FLAG_ZERO_PAD) ? '0' : ' ';
222
223             switch (*ptr)
224             {
225             case 's':
226                 if (!(flags & __CRT_PRINTF_FLAG_ALIGN_LEFT))
227                 {
228                     while (length < width)
229                     {
230                         ret += __crt_strputc(str, padding);
231                         length++;
232                     }
233                 }
234
235                 if (precision)
236                 {
237                     while (*data && precision > 0)
238                     {
239                         ret += __crt_strputc(str, *data++);
240                         precision--;
241                     }
242                 }
243                 else
244                 {
245                     ret += __crt_strputs(str, data ? data : "(null)");
246                 }
247                 break;
248
249             case 'd':
250             case 'i':
251             case 'o':
252             case 'u':
253             case 'x':
254             case 'X':
255                 if (flags & (__CRT_PRINTF_FLAG_PLUS | __CRT_PRINTF_FLAG_SPACE))
256                 {
257                     if (*data == '-')
258                     {
259                         ret += __crt_strputc(str, '-');
260                         data++;
261                     }
262                     else
263                     {
264                         ret += __crt_strputc(str, (flags & __CRT_PRINTF_FLAG_PLUS) ? '+' : ' ');
265                         length++;
266                     }
267                 }
268
269                 if (!(flags & __CRT_PRINTF_FLAG_ALIGN_LEFT) && width > precision)
270                 {
271                     while (length < width - precision)
272                     {
273                         ret += __crt_strputc(str, padding);
274                         length++;
275                     }
276
277                     while (length < width)
278                     {
279                         ret += __crt_strputc(str, '0');
280                         length++;
281                     }
282                 }
283                 else
284                 {
285                     while (length < precision)
286                     {
287                         ret += __crt_strputc(str, '0');
288                         length++;
289                     }
290                 }
291
292                 ret += __crt_strputs(str, data);
293                 break;
294             }
295
296             while (length < width)
297             {
298                 ret += __crt_strputc(str, padding);
299                 length++;
300             }
301         }
302         else
303         {
304             ret += __crt_strputc(str, *ptr);
305         }
306     }
307
308     if (str->string) *str->string = '\0';
309     if (str->stream) syscall_release_mutex(str->stream->mutex);
310     return ret;
311 }
312
313 int vsnprintf(char *string, size_t size, const char *format, va_list ap)
314 {
315     __crt_stream_or_string_t str = { .stream = NULL, .string = string, .size = size };
316     return __crt_vstrprintf(&str, format, ap);
317 }
318
319 int vsprintf(char *str, const char *format, va_list ap)
320 {
321     return vsnprintf(str, -1, format, ap);
322 }
323
324 int vprintf(const char *format, va_list ap)
325 {
326     return vfprintf(stdout, format, ap);
327 }
328
329 int vfprintf(FILE *stream, const char *format, va_list ap)
330 {
331     __crt_stream_or_string_t str = { .stream = stream, .string = NULL, .size = 0 };
332     return __crt_vstrprintf(&str, format, ap);
333 }
334
335 int fprintf(FILE *stream, const char *format, ...)
336 {
337     va_list ap;
338     va_start(ap, format);
339     int ret = vfprintf(stream, format, ap);
340     va_end(ap);
341     return ret;
342 }
343
344 int snprintf(char *str, size_t size, const char *format, ...)
345 {
346     va_list ap;
347     va_start(ap, format);
348     int ret = vsnprintf(str, size, format, ap);
349     va_end(ap);
350     return ret;
351 }
352
353 int sprintf(char *str, const char *format, ...)
354 {
355     va_list ap;
356     va_start(ap, format);
357     int ret = vsprintf(str, format, ap);
358     va_end(ap);
359     return ret;
360 }
361
362 int printf(const char *format, ...)
363 {
364     va_list ap;
365     va_start(ap, format);
366     int ret = vprintf(format, ap);
367     va_end(ap);
368     return ret;
369 }