summaryrefslogtreecommitdiffstats
path: root/lib/libc-headless/stdio/sprintf.c
blob: dd785728543bcb71d44b9f23048c57ddb2bb78e0 (plain) (blame)
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <ctype.h>

#define VSNPRINTF_PUTC(c) if(size > written) str[written++] = c; else written++

int
vsnprintf(char *restrict str, size_t size, const char *restrict format, va_list ap)
{
    int written = 0;
    for(int fmti = 0; format[fmti] != 0; fmti++) {
        char fmtc = format[fmti];
        if(fmtc != '%') {
            VSNPRINTF_PUTC(fmtc);
            continue;
        }

        /* Flags */
        bool alternate = false;
        bool zeropad = false;
        bool leftadj = false;
        bool signspace = false;
        bool showplus = false;
        fmtc = format[++fmti];
        
        switch(fmtc) {
            case '#': alternate = true; fmti++; break;
            case '0': zeropad = true; fmti++; break;
            case '-': leftadj = true; fmti++; break;
            case ' ': signspace = true; fmti++; break;
            case '+': showplus = true; fmti++; break;
            default: break;
        }

        /* Field width. */
        fmtc = format[fmti];
        //TODO: Implement field width

        /* Precision. */
        fmtc = format[fmti];
        //TODO: Implement precision
    
        /* Length modifier */
        fmtc = format[fmti];
        //TODO: Implement length modifier.

        /* Conversion specifier. */
        bool is_signed = true;
        bool is_caps = false;
        int radix = 10;
        fmtc = format[fmti];
        switch(fmtc)
        {
            case 'X':
                is_caps = true;
            case 'x':
                radix = 16;
            case 'u':
                is_signed = false;
            case 'd':
            case 'i':
                goto conversion_int;
           case 'p':
                alternate = true;
                radix = 16;
                is_signed = false;
                is_caps = true;
                goto conversion_int;
            case '%':
                VSNPRINTF_PUTC('%');
                break;
        }
        continue;

conversion_int:
        {
            uintmax_t value = va_arg(ap, uintmax_t);
            bool negative = is_signed && (intmax_t)value < 0;
            if(negative) value = (uintmax_t)(-(intmax_t)value);
            size_t numc = 0;

            /*Count the number of characters to put in the buffer to represent
             * the value in ASCII.*/
            for(uintmax_t v = value; v != 0; v /= radix, numc++);
            if(numc == 0) numc++;

            /* Reserve space for alternate repersentation.*/
            if(showplus && !negative) { VSNPRINTF_PUTC('+'); }
            if(negative) { VSNPRINTF_PUTC('-'); }
            if(signspace && !negative) { VSNPRINTF_PUTC(' '); }
            if(alternate) {
                VSNPRINTF_PUTC('0');
                if(radix == 16) {
                    VSNPRINTF_PUTC('x');
                }
                if(alternate && radix == 2) {
                    VSNPRINTF_PUTC('b');
                }
            }

            /* Try to put characters into the buffer*/
            if(value == 0) {
                VSNPRINTF_PUTC('0');
            }
            else {
                for(uintmax_t v = value, i = numc - 1; v != 0; v /= radix, i--) {
                    if(written + i >= size) continue;
                    int digit = v % radix;
                    char c = digit >= 10 ? (digit + 'a' - 10) : (digit + '0');
                    if(is_caps) c = toupper(c);
                    str[written + i] = c;
                }
                written += numc;
            }
            continue;
        }
    }
    VSNPRINTF_PUTC(0);
    return written - 1;
}