summaryrefslogtreecommitdiff
path: root/src/stdio/__conv.c
diff options
context:
space:
mode:
authorJakob Kaivo <jkk@ung.org>2024-06-06 17:02:32 -0400
committerJakob Kaivo <jkk@ung.org>2024-06-06 17:02:32 -0400
commitd8353afab7c754968ebbaa8fa1cd3f6f4b83d86e (patch)
tree077bfbff86d29acbd06693bc19def74b7743749d /src/stdio/__conv.c
parent5da931dfa4b3846b1ca459290a6724eb67d1685c (diff)
rework formatted I/O to do most validation in __conv(), use __conv() from __printf(), implement more and more robust __printf() functionality
Diffstat (limited to 'src/stdio/__conv.c')
-rw-r--r--src/stdio/__conv.c161
1 files changed, 121 insertions, 40 deletions
diff --git a/src/stdio/__conv.c b/src/stdio/__conv.c
index 3dbfd687..994bb072 100644
--- a/src/stdio/__conv.c
+++ b/src/stdio/__conv.c
@@ -3,7 +3,18 @@
#include <string.h>
#include "_stdio.h"
-size_t __conv(const char *format, struct io_conversion *conv)
+static char *length_names[] = {
+ [L_hh] = "hh",
+ [L_ll] = "ll",
+ [L_h] = "h",
+ [L_l] = "l",
+ [L_j] = "j",
+ [L_z] = "z",
+ [L_t] = "t",
+ [L_L] = "L",
+};
+
+size_t __conv(const char *format, struct io_conversion *conv, va_list arg)
{
size_t ret = 0;
@@ -15,8 +26,6 @@ size_t __conv(const char *format, struct io_conversion *conv)
conv->flags = 0;
conv->length = L_default;
- conv->has_width = 0;
- conv->has_precision = 0;
while (strchr("*-+ #0", format[ret])) {
switch (format[ret]) {
@@ -27,58 +36,130 @@ size_t __conv(const char *format, struct io_conversion *conv)
case '#': conv->flags |= F_ALT; break;
case '0': conv->flags |= F_ZERO; break;
}
- /* check for invalid input flags (only * is allowed) */
+
+ if (conv->dir == IO_IN) {
+ if ((conv->flags & ~(F_STAR)) != 0) {
+ UNDEFINED("Flag '%c' is not valid for formatted input", format[ret]);
+ }
+ } else {
+ if (conv->flags & F_STAR) {
+ int width = va_arg(arg, int);
+ conv->flags |= F_WIDTH;
+ if (width < 0) {
+ conv->flags |= F_LEFT;
+ conv->width = -width;
+ } else {
+ conv->width = width;
+ }
+ }
+ }
+
ret++;
}
if (isdigit(format[ret])) {
char *end = NULL;
- conv->has_width = 1;
+ conv->flags |= F_WIDTH;
conv->width = strtoumax(format + ret, &end, 10);
ret += (size_t)(end - (format + ret));
}
- /* TODO: precision */
-
- if (strchr("hljztL", format[ret])) {
- switch (format[ret]) {
- case 'h':
- if (format[ret + 1] == 'h') {
- ret++;
- conv->length = L_hh;
- } else {
- conv->length = L_h;
- }
- break;
- case 'l':
- if (format[ret + 1] == 'l') {
- ret++;
- conv->length = L_ll;
+ if (format[ret] == '.') {
+ if (conv->dir == IO_IN) {
+ UNDEFINED("Precision is not supported for formatted input");
+ }
+ conv->flags |= F_PRECISION;
+ ret++;
+ if (format[ret] == '*') {
+ int prec = va_arg(arg, int);
+ if (prec > 0) {
+ conv->precision = prec;
} else {
- conv->length = L_l;
+ conv->flags &= ~(F_PRECISION);
}
- break;
- case 'j':
- conv->length = L_j;
- break;
- case 'z':
- conv->length = L_z;
- break;
- case 't':
- conv->length = L_t;
- break;
- case 'L':
- conv->length = L_L;
- break;
- default:
- break;
+ } else {
+ char *end = NULL;
+ conv->precision = strtoumax(format + ret, &end, 10);
+ ret += (size_t)(end - (format + ret));
}
- ret++;
}
- /* TODO: validate */
+ for (size_t i = 0; i < sizeof(length_names) / sizeof(length_names[0]); i++) {
+ if (length_names[i] && strncmp(length_names[i], format + ret, strlen(length_names[i])) == 0) {
+ conv->length = (enum conversion_length)i;
+ ret += strlen(length_names[i]);
+ break;
+ }
+ }
conv->spec = format[ret];
- ret++;
+ if (isupper(conv->spec)) {
+ conv->flags |= F_UPPER;
+ }
+
+ switch (conv->spec) {
+ case 'd':
+ case 'i':
+ if (conv->length == L_L) {
+ UNDEFINED("In call to %s(): Length '%s' is not supported with conversion specifier '%c'", conv->func, length_names[conv->length], conv->spec);
+ }
+ break;
+
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X':
+ if (conv->length == L_L) {
+ UNDEFINED("In call to %s(): Length '%s' is not supported with conversion specifier '%c'", conv->func, length_names[conv->length], conv->spec);
+ }
+ break;
+
+ case 'f':
+ case 'F':
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ case 'a':
+ case 'A':
+ if (conv->length != L_L && conv->length) {
+ UNDEFINED("In call to %s(): Length '%s' is not supported with conversion specifier '%c'", conv->func, length_names[conv->length], conv->spec);
+ }
+ break;
+
+ case 'c':
+ if (conv->flags & F_PRECISION) {
+ UNDEFINED("In call to %s(): Precision is not supported with conversion specifier '%c'", conv->func, conv->spec);
+ }
+ /* FALLTHRU */
+
+ case 's':
+ case '[':
+ if (conv->length != L_l && conv->length) {
+ UNDEFINED("In call to %s(): Length '%s' is not supported with conversion specifier '%c'", conv->func, length_names[conv->length], conv->spec);
+ }
+ break;
+
+ case 'p':
+ case 'n':
+ case '%':
+ if (conv->flags & F_WIDTH) {
+ UNDEFINED("In call to %s(): Field width is not supported with conversion specififier '%c'", conv->func, conv->spec);
+ }
+ if (conv->flags & F_PRECISION) {
+ UNDEFINED("In call to %s(): Precision is not supported with conversion specififier '%c'", conv->func, conv->spec);
+ }
+ if (conv->flags) {
+ UNDEFINED("In call to %s(): Flags are not supported with conversion specifier '%c'", conv->func, conv->spec);
+ }
+ if (conv->length) {
+ UNDEFINED("In call to %s(): Length '%s' is not supported with conversion specifier '%c'", conv->func, length_names[conv->length], conv->spec);
+ }
+ break;
+
+ default:
+ UNDEFINED("In call to %s(): Unknown conversion specifier '%c'", conv->func, conv->spec);
+ }
+
return ret;
}