summaryrefslogtreecommitdiffhomepage
path: root/misc/include/fmt.h
blob: df1fe990337b8688a03f22cc1c610853a4de58dd (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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#ifndef FMT_INCLUDED
#define FMT_INCLUDED
/*
void        fmt_print(dst, fmt, ...);
void        fmt_destroy(fmt_buffer* buf);

  dst - destination 
    int             1=stdout, 2=stderr
    FILE* file      Write to a file
    char* strbuf    Write to a pre-allocated string buffer
    fmt_buffer* b   Auto realloc the needed memory. Set b->stream=1 for stream-mode.
                    b->data must be freed after usage.

  fmt - format string
    {}              Auto-detected format. If :MOD is not specified,
                    float will use ".8g" format, and double ".16g".
    {:MOD}          Format modifiers: < left align (replaces -), default for char*, char.
                                      > right align, default for numbers.
                    Other than that MOD can be normal printf format modifiers.
    {{, }}          Print chars {, and }. (note: a single % prints %).

* C11 or higher required.
* MAX 255 chars fmt string by default. MAX 12 arguments after fmt string.
* Static linking by default, shared symbols by defining FMT_HEADER / FMT_IMPLEMENT.
* (c) operamint, 2022, MIT License.
-----------------------------------------------------------------------------------
#include "fmt.h"

int main() {
    const double pi = 3.141592653589793;
    const size_t x = 1234567890;
    const char* string = "Hello world";
    const char z = 'z';
    _Bool flag = 1;
    unsigned char r = 123, g = 214, b = 90, w = 110;
    char buffer[64];

    fmt_print(1, "Color: ({} {} {}), {}\n", r, g, b, flag);
    fmt_print(1, "{:10} {:10} {:10.2f}\n", 42ull, 43, pi);
    fmt_print(stdout, "{:>10} {:>10} {:>10}\n", z, z, w);
    fmt_print(stdout, "{:10} {:10} {:10}\n", "Hello", "Mad", "World");
    fmt_print(stderr, "100%: {:<20} {:.*} {}\n", string, 4, pi, x);
    fmt_print(buffer, "Precision: {} {:.10} {}", string, pi, x);
    fmt_print(1, "{}\n", buffer);
    fmt_print(1, "Vector: ({}, {}, {})\n", 3.2, 3.3, pi);

    fmt_buffer out[1] = {{.stream=1}};
    fmt_print(out, "{} {}", "Pi is:", pi);
    fmt_print(1,   "{}, len={}, cap={}\n", out->data, out->len, out->cap);
    fmt_print(out, "{} {}", ", Pi squared is:", pi*pi);
    fmt_print(1,   "{}, len={}, cap={}\n", out->data, out->len, out->cap);
    fmt_destroy(out);
}
*/
#include <stdio.h>
#include <assert.h>

#define fmt_OVERLOAD(name, ...) \
    fmt_JOIN(name, fmt_NUMARGS(__VA_ARGS__))(__VA_ARGS__)
#define fmt_CONCAT(a, b) a ## b
#define fmt_JOIN(a, b) fmt_CONCAT(a, b)
#define fmt_EXPAND(...) __VA_ARGS__
#define fmt_NUMARGS(...) _fmt_APPLY_ARG_N((__VA_ARGS__, _fmt_RSEQ_N))

#define _fmt_APPLY_ARG_N(args) fmt_EXPAND(_fmt_ARG_N args)
#define _fmt_RSEQ_N 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
#define _fmt_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, \
                   _14, _15, _16, N, ...) N

#if defined FMT_HEADER || defined FMT_IMPLEMENT
#  define FMT_API
#else
#  define FMT_API static inline
#endif

typedef struct { 
    char* data; 
    size_t cap, len; 
    _Bool stream;
} fmt_buffer;

FMT_API void fmt_destroy(fmt_buffer* buf);
FMT_API int  _fmt_parse(char* p, int nargs, const char *fmt, ...);
FMT_API void _fmt_iprint(int fd, const char* fmt, ...);
FMT_API void _fmt_bprint(fmt_buffer*, const char* fmt, ...);

#ifndef FMT_MAX
#define FMT_MAX 256
#endif

/* Primary function. */
#define fmt_print(...) fmt_OVERLOAD(fmt_print, __VA_ARGS__)
#define fmt_print2(to, fmt) \
    do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 0, fmt); \
        assert(_n == 0); _fmt_fn(to)(to, fmt); } while (0)
#define fmt_print3(to, fmt, c) \
    do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 1, fmt, _fc(c)); \
        assert(_n == 1); _fmt_fn(to)(to, _fs, c); } while (0)
#define fmt_print4(to, fmt, c, d) \
    do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 2, fmt, _fc(c), _fc(d)); \
        assert(_n == 2); _fmt_fn(to)(to, _fs, c, d); } while (0)
#define fmt_print5(to, fmt, c, d, e) \
    do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 3, fmt, _fc(c), _fc(d), _fc(e)); \
        assert(_n == 3); _fmt_fn(to)(to, _fs, c, d, e); } while (0)
#define fmt_print6(to, fmt, c, d, e, f) \
    do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 4, fmt, _fc(c), _fc(d), _fc(e), _fc(f)); \
        assert(_n == 4); _fmt_fn(to)(to, _fs, c, d, e, f); } while (0)
#define fmt_print7(to, fmt, c, d, e, f, g) \
    do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 5, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g)); \
        assert(_n == 5); _fmt_fn(to)(to, _fs, c, d, e, f, g); } while (0)
#define fmt_print8(to, fmt, c, d, e, f, g, h) \
    do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 6, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h)); \
        assert(_n == 6); _fmt_fn(to)(to, _fs, c, d, e, f, g, h); } while (0)
#define fmt_print9(to, fmt, c, d, e, f, g, h, i) \
    do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 7, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), _fc(i)); \
        assert(_n == 7); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i); } while (0)
#define fmt_print10(to, fmt, c, d, e, f, g, h, i, j) \
    do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 8, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), \
                                                                     _fc(i), _fc(j)); \
        assert(_n == 8); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j); } while (0)
#define fmt_print11(to, fmt, c, d, e, f, g, h, i, j, k) \
    do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 9, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), \
                                                                     _fc(i), _fc(j), _fc(k)); \
        assert(_n == 9); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j, k); } while (0)
#define fmt_print12(to, fmt, c, d, e, f, g, h, i, j, k, m) \
    do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 10, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), \
                                                                      _fc(i), _fc(j), _fc(k), _fc(m)); \
        assert(_n == 10); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j, k, m); } while (0)
#define fmt_print13(to, fmt, c, d, e, f, g, h, i, j, k, m, n) \
    do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 11, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), \
                                                                      _fc(i), _fc(j), _fc(k), _fc(m), _fc(n)); \
        assert(_n == 11); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j, k, m, n); } while (0)
#define fmt_print14(to, fmt, c, d, e, f, g, h, i, j, k, m, n, o) \
    do { char _fs[FMT_MAX]; int _n = _fmt_parse(_fs, 12, fmt, _fc(c), _fc(d), _fc(e), _fc(f), _fc(g), _fc(h), \
                                                                      _fc(i), _fc(j), _fc(k), _fc(m), _fc(n), _fc(o)); \
        assert(_n == 12); _fmt_fn(to)(to, _fs, c, d, e, f, g, h, i, j, k, m, n, o); } while (0)

#define _fmt_fn(x) _Generic ((x), \
    FILE*: fprintf, \
    char*: sprintf, \
    int: _fmt_iprint, \
    fmt_buffer*: _fmt_bprint)

#if defined(_MSC_VER) && !defined(__clang__)
#  define _signed_char_hhd
#else
#  define _signed_char_hhd signed char: "hhd",
#endif

#define _fc(x) _Generic (x, \
    _Bool: "d", \
    unsigned char: "hhu", \
    _signed_char_hhd \
    char: "c", \
    short: "hd", \
    unsigned short: "hu", \
    int: "d", \
    unsigned: "u", \
    long: "ld", \
    unsigned long: "lu", \
    long long: "lld", \
    unsigned long long: "llu", \
    float: "g", \
    double: "@g", \
    long double: "@Lg", \
    char*: "s", \
    void*: "p", \
    const char*: "s", \
    const void*: "p")

#if defined FMT_IMPLEMENT || !(defined FMT_HEADER || defined FMT_IMPLEMENT)

#include <stdlib.h>
#include <stdarg.h>
#include <string.h>

FMT_API void fmt_destroy(fmt_buffer* buf) {
    free(buf->data);
}

FMT_API void _fmt_iprint(int fd, const char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vfprintf(fd == 1 ? stdout : stderr, fmt, args);
    va_end(args);
}

FMT_API void _fmt_bprint(fmt_buffer* buf, const char* fmt, ...) {
    va_list args, args2;
    va_start(args, fmt);
    va_copy(args2, args);
    const int n = vsnprintf(NULL, 0U, fmt, args);
    const size_t pos = buf->stream ? buf->len : 0U;
    buf->len = pos + (size_t)n;
    if (buf->len > buf->cap) {
        buf->cap = buf->len + buf->cap/2;
        buf->data = (char*)realloc(buf->data, buf->cap + 1);
    }
    vsprintf(buf->data + pos, fmt, args2);
    va_end(args2);
    va_end(args);
}

FMT_API int _fmt_parse(char* p, int nargs, const char *fmt, ...) {
    char *arg, *p0, ch;
    int n = 0, empty;
    va_list args;
    va_start(args, fmt);
    do {
        switch ((ch = *fmt)) {
        case '%':
            *p++ = '%';
            break;
        case '}':
            if (*++fmt == '}') break; /* ok */
            n = 99;
            continue;
        case '{':
            if (*++fmt == '{') break; /* ok */
            if (++n > nargs) continue;
            if (*fmt != ':' && *fmt != '}') n = 99;
            fmt += (*fmt == ':');
            empty = *fmt == '}';
            arg = va_arg(args, char *);
            *p++ = '%', p0 = p;
            while (1) switch (*fmt) {
                case '\0': n = 99; /* nobreak */
                case '}': goto done;
                case '<': *p++ = '-', ++fmt; break;
                case '>': p0 = NULL; /* nobreak */
                case '-': ++fmt; break;
                case '*': if (++n <= nargs) arg = va_arg(args, char *); /* nobreak */
                default: *p++ = *fmt++;
            }
            done:
            switch (*arg) {
            case 'g': if (empty) memcpy(p, ".8", 2), p += 2; break;
            case '@': ++arg; if (empty) memcpy(p, ".16", 3), p += 3; break;
            }
            if (!strchr("csdioxXufFeEaAgGnp", fmt[-1]))
                while (*arg) *p++ = *arg++;
            if (p0 && (p[-1] == 's' || p[-1] == 'c')) /* left-align str */
                memmove(p0 + 1, p0, p++ - p0), *p0 = '-';
            fmt += *fmt == '}';
            continue;
        }
        *p++ = *fmt++;
    } while (ch);
    va_end(args);
    return n;
}
#endif
#endif