summaryrefslogtreecommitdiff
path: root/src/utils.c
blob: 75d59a6f7f328898e9d2eee2e64b65eb3dddb732 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#include <lcq/pit/utils.h>

enum vsnprintf_mode {
    VSNPRINTF_MODE_NORMAL,
    VSNPRINTF_MODE_FLAGS,
    VSNPRINTF_MODE_WIDTH,
    VSNPRINTF_MODE_PRECISION,
    VSNPRINTF_MODE_LENGTH_MOD,
    VSNPRINTF_MODE_CONVERSION_SPEC
};
#define SCRATCH_LEN 256
#define WRITE(c) { buf[idx++] = c; if (idx >= len - 1) goto done; }
#define WRITE_SCRATCH(c) { scratch[sidx++] = c; if (sidx >= SCRATCH_LEN) goto error; }
int pit_libc_string_vsnprintf(char *buf, size_t len, char *format, va_list ap) {
    // vsnprintf
    size_t idx = 0;
    size_t flen = pit_libc_string_strlen(format);
    size_t fidx = 0;
    enum vsnprintf_mode mode = VSNPRINTF_MODE_NORMAL;
    size_t sidx = 0;
    char scratch[SCRATCH_LEN] = {0};
    char length_mod = 0;
    for (; fidx < flen && idx < len - 1; ++fidx) {
        char c = format[fidx];
        sidx = 0;
        switch (mode) {
        case VSNPRINTF_MODE_NORMAL:
            if (c == '%') {
                mode = VSNPRINTF_MODE_FLAGS;
            } else WRITE(c);
            break;
        case VSNPRINTF_MODE_FLAGS:
        case VSNPRINTF_MODE_WIDTH:
        case VSNPRINTF_MODE_PRECISION:
        case VSNPRINTF_MODE_LENGTH_MOD:
            switch (c) {
            case 'l': length_mod = 'l'; mode = VSNPRINTF_MODE_CONVERSION_SPEC; continue;
            }
            // fallthrough
        case VSNPRINTF_MODE_CONVERSION_SPEC:
            switch (c) {
            case '%': WRITE('%'); mode = VSNPRINTF_MODE_NORMAL; break;
            case 'd': {
                long arg = 0;
                if (length_mod == 'l') arg = va_arg(ap, long); else arg = va_arg(ap, int);
                if (arg == 0) { WRITE('0') }
                else {
                    if (arg < 0) { WRITE('-'); arg = -arg; }
                    while (arg != 0) { WRITE_SCRATCH('0' + (char) (arg % 10)); arg /= 10; }
                    while (sidx > 0) { WRITE(scratch[sidx - 1]); sidx -= 1; }
                }
                mode = VSNPRINTF_MODE_NORMAL;
                break;
            }
            case 'f': {
                double arg = 0.0;
                if (length_mod == 'l')
                    arg = va_arg(ap, double);
                else
                    arg = va_arg(ap, double);
                long wholepart = (long) arg;
                double fracpart = arg - (double) wholepart;
                if (wholepart == 0) { WRITE('0') }
                else {
                    while (wholepart != 0) { WRITE_SCRATCH('0' + (char) (wholepart % 10)); wholepart /= 10; }
                    while (sidx > 0) { WRITE(scratch[sidx - 1]); sidx -= 1; }
                }
                WRITE('.');
                for (int i = 0; i < 6; ++i) {
                    fracpart *= 10.0;
                    wholepart = (long) fracpart;
                    fracpart -= (double) wholepart;
                    WRITE('0' + (char) wholepart);
                }
                mode = VSNPRINTF_MODE_NORMAL;
                break;
            }
            case 's': {
                char *arg = va_arg(ap, char *);
                while (*arg != 0) { WRITE(*arg); arg += 1; }
                mode = VSNPRINTF_MODE_NORMAL;
                break;
            }
            default: goto error;
            }
            break;
        }
    }
done:
    buf[idx] = 0;
    return (int) idx;
error:
    return -1;
}
int pit_libc_string_snprintf(char *buf, size_t len, char *format, ...) {
    va_list ap;
    va_start(ap, format);
    int ret = pit_libc_string_vsnprintf(buf, len, format, ap);
    va_end(ap);
    return ret;
}