/+junk/codegen

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/%2Bjunk/codegen

« back to all changes in this revision

Viewing changes to src/flag.h

  • Committer: Gustav Hartvigsson
  • Date: 2022-10-19 21:06:55 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20221019210655-9iqu2dhjbpvedoma
Beep Boop, nothin yet.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// flag.h -- command-line flag parsing
 
2
//
 
3
//   Inspired by Go's flag module: https://pkg.go.dev/flag
 
4
//
 
5
#ifndef FLAG_H_
 
6
#define FLAG_H_
 
7
 
 
8
#include <assert.h>
 
9
#include <stdio.h>
 
10
#include <stdlib.h>
 
11
#include <stdbool.h>
 
12
#include <stdint.h>
 
13
#include <stddef.h>
 
14
#include <inttypes.h>
 
15
#include <limits.h>
 
16
#include <string.h>
 
17
#include <errno.h>
 
18
 
 
19
// TODO: add support for -flag=x syntax
 
20
// TODO: *_var function variants
 
21
// void flag_bool_var(bool *var, const char *name, bool def, const char *desc);
 
22
// void flag_bool_uint64(uint64_t *var, const char *name, bool def, const char *desc);
 
23
// etc.
 
24
// WARNING! *_var functions may break the flag_name() functionality
 
25
 
 
26
char *flag_name(void *val);
 
27
bool *flag_bool(const char *name, bool def, const char *desc);
 
28
uint64_t *flag_uint64(const char *name, uint64_t def, const char *desc);
 
29
size_t *flag_size(const char *name, uint64_t def, const char *desc);
 
30
char **flag_str(const char *name, const char *def, const char *desc);
 
31
bool flag_parse(int argc, char **argv);
 
32
int flag_rest_argc(void);
 
33
char **flag_rest_argv(void);
 
34
void flag_print_error(FILE *stream);
 
35
void flag_print_options(FILE *stream);
 
36
 
 
37
#endif // FLAG_H_
 
38
 
 
39
//////////////////////////////
 
40
 
 
41
#ifdef FLAG_IMPLEMENTATION
 
42
 
 
43
typedef enum {
 
44
    FLAG_BOOL = 0,
 
45
    FLAG_UINT64,
 
46
    FLAG_SIZE,
 
47
    FLAG_STR,
 
48
    COUNT_FLAG_TYPES,
 
49
} Flag_Type;
 
50
 
 
51
static_assert(COUNT_FLAG_TYPES == 4, "Exhaustive Flag_Value definition");
 
52
typedef union {
 
53
    char *as_str;
 
54
    uint64_t as_uint64;
 
55
    bool as_bool;
 
56
    size_t as_size;
 
57
} Flag_Value;
 
58
 
 
59
typedef enum {
 
60
    FLAG_NO_ERROR = 0,
 
61
    FLAG_ERROR_UNKNOWN,
 
62
    FLAG_ERROR_NO_VALUE,
 
63
    FLAG_ERROR_INVALID_NUMBER,
 
64
    FLAG_ERROR_INTEGER_OVERFLOW,
 
65
    FLAG_ERROR_INVALID_SIZE_SUFFIX,
 
66
    COUNT_FLAG_ERRORS,
 
67
} Flag_Error;
 
68
 
 
69
typedef struct {
 
70
    Flag_Type type;
 
71
    char *name;
 
72
    char *desc;
 
73
    Flag_Value val;
 
74
    Flag_Value def;
 
75
} Flag;
 
76
 
 
77
#ifndef FLAGS_CAP
 
78
#define FLAGS_CAP 256
 
79
#endif
 
80
 
 
81
typedef struct {
 
82
    Flag flags[FLAGS_CAP];
 
83
    size_t flags_count;
 
84
 
 
85
    Flag_Error flag_error;
 
86
    char *flag_error_name;
 
87
 
 
88
    int rest_argc;
 
89
    char **rest_argv;
 
90
} Flag_Context;
 
91
 
 
92
static Flag_Context flag_global_context;
 
93
 
 
94
Flag *flag_new(Flag_Type type, const char *name, const char *desc)
 
95
{
 
96
    Flag_Context *c = &flag_global_context;
 
97
 
 
98
    assert(c->flags_count < FLAGS_CAP);
 
99
    Flag *flag = &c->flags[c->flags_count++];
 
100
    memset(flag, 0, sizeof(*flag));
 
101
    flag->type = type;
 
102
    // NOTE: I won't touch them I promise Kappa
 
103
    flag->name = (char*) name;
 
104
    flag->desc = (char*) desc;
 
105
    return flag;
 
106
}
 
107
 
 
108
char *flag_name(void *val)
 
109
{
 
110
    Flag *flag = (Flag*) ((char*) val - offsetof(Flag, val));
 
111
    return flag->name;
 
112
}
 
113
 
 
114
bool *flag_bool(const char *name, bool def, const char *desc)
 
115
{
 
116
    Flag *flag = flag_new(FLAG_BOOL, name, desc);
 
117
    flag->def.as_bool = def;
 
118
    flag->val.as_bool = def;
 
119
    return &flag->val.as_bool;
 
120
}
 
121
 
 
122
uint64_t *flag_uint64(const char *name, uint64_t def, const char *desc)
 
123
{
 
124
    Flag *flag = flag_new(FLAG_UINT64, name, desc);
 
125
    flag->val.as_uint64 = def;
 
126
    flag->def.as_uint64 = def;
 
127
    return &flag->val.as_uint64;
 
128
}
 
129
 
 
130
size_t *flag_size(const char *name, uint64_t def, const char *desc)
 
131
{
 
132
    Flag *flag = flag_new(FLAG_SIZE, name, desc);
 
133
    flag->val.as_size = def;
 
134
    flag->def.as_size = def;
 
135
    return &flag->val.as_size;
 
136
}
 
137
 
 
138
char **flag_str(const char *name, const char *def, const char *desc)
 
139
{
 
140
    Flag *flag = flag_new(FLAG_STR, name, desc);
 
141
    flag->val.as_str = (char*) def;
 
142
    flag->def.as_str = (char*) def;
 
143
    return &flag->val.as_str;
 
144
}
 
145
 
 
146
static char *flag_shift_args(int *argc, char ***argv)
 
147
{
 
148
    assert(*argc > 0);
 
149
    char *result = **argv;
 
150
    *argv += 1;
 
151
    *argc -= 1;
 
152
    return result;
 
153
}
 
154
 
 
155
int flag_rest_argc(void)
 
156
{
 
157
    return flag_global_context.rest_argc;
 
158
}
 
159
 
 
160
char **flag_rest_argv(void)
 
161
{
 
162
    return flag_global_context.rest_argv;
 
163
}
 
164
 
 
165
bool flag_parse(int argc, char **argv)
 
166
{
 
167
    Flag_Context *c = &flag_global_context;
 
168
 
 
169
    flag_shift_args(&argc, &argv);
 
170
 
 
171
    while (argc > 0) {
 
172
        char *flag = flag_shift_args(&argc, &argv);
 
173
 
 
174
        if (*flag != '-') {
 
175
            // NOTE: pushing flag back into args
 
176
            c->rest_argc = argc + 1;
 
177
            c->rest_argv = argv - 1;
 
178
            return true;
 
179
        }
 
180
 
 
181
        if (strcmp(flag, "--") == 0) {
 
182
            // NOTE: but if it's the terminator we don't need to push it back
 
183
            c->rest_argc = argc;
 
184
            c->rest_argv = argv;
 
185
            return true;
 
186
        }
 
187
 
 
188
        // NOTE: remove the dash
 
189
        flag += 1;
 
190
 
 
191
        bool found = false;
 
192
        for (size_t i = 0; i < c->flags_count; ++i) {
 
193
            if (strcmp(c->flags[i].name, flag) == 0) {
 
194
                static_assert(COUNT_FLAG_TYPES == 4, "Exhaustive flag type parsing");
 
195
                switch (c->flags[i].type) {
 
196
                case FLAG_BOOL: {
 
197
                    c->flags[i].val.as_bool = true;
 
198
                }
 
199
                break;
 
200
 
 
201
                case FLAG_STR: {
 
202
                    if (argc == 0) {
 
203
                        c->flag_error = FLAG_ERROR_NO_VALUE;
 
204
                        c->flag_error_name = flag;
 
205
                        return false;
 
206
                    }
 
207
                    char *arg = flag_shift_args(&argc, &argv);
 
208
                    c->flags[i].val.as_str = arg;
 
209
                }
 
210
                break;
 
211
 
 
212
                case FLAG_UINT64: {
 
213
                    if (argc == 0) {
 
214
                        c->flag_error = FLAG_ERROR_NO_VALUE;
 
215
                        c->flag_error_name = flag;
 
216
                        return false;
 
217
                    }
 
218
                    char *arg = flag_shift_args(&argc, &argv);
 
219
 
 
220
                    static_assert(sizeof(unsigned long long int) == sizeof(uint64_t), "The original author designed this for x86_64 machine with the compiler that expects unsigned long long int and uint64_t to be the same thing, so they could use strtoull() function to parse it. Please adjust this code for your case and maybe even send the patch to upstream to make it work on a wider range of environments.");
 
221
                    char *endptr;
 
222
                    // TODO: replace strtoull with a custom solution
 
223
                    // That way we can get rid of the dependency on errno and static_assert
 
224
                    unsigned long long int result = strtoull(arg, &endptr, 10);
 
225
 
 
226
                    if (*endptr != '\0') {
 
227
                        c->flag_error = FLAG_ERROR_INVALID_NUMBER;
 
228
                        c->flag_error_name = flag;
 
229
                        return false;
 
230
                    }
 
231
                    
 
232
                    if (result == ULLONG_MAX && errno == ERANGE) {
 
233
                        c->flag_error = FLAG_ERROR_INTEGER_OVERFLOW;
 
234
                        c->flag_error_name = flag;
 
235
                        return false;
 
236
                    }
 
237
 
 
238
                    c->flags[i].val.as_uint64 = result;
 
239
                }
 
240
                break;
 
241
 
 
242
                case FLAG_SIZE: {
 
243
                    if (argc == 0) {
 
244
                        c->flag_error = FLAG_ERROR_NO_VALUE;
 
245
                        c->flag_error_name = flag;
 
246
                        return false;
 
247
                    }
 
248
                    char *arg = flag_shift_args(&argc, &argv);
 
249
 
 
250
                    static_assert(sizeof(unsigned long long int) == sizeof(size_t), "The original author designed this for x86_64 machine with the compiler that expects unsigned long long int and size_t to be the same thing, so they could use strtoull() function to parse it. Please adjust this code for your case and maybe even send the patch to upstream to make it work on a wider range of environments.");
 
251
                    char *endptr;
 
252
                    // TODO: replace strtoull with a custom solution
 
253
                    // That way we can get rid of the dependency on errno and static_assert
 
254
                    unsigned long long int result = strtoull(arg, &endptr, 10);
 
255
 
 
256
                    // TODO: handle more multiplicative suffixes like in dd(1). From the dd(1) man page:
 
257
                    // > N and BYTES may be followed by the following
 
258
                    // > multiplicative suffixes: c =1, w =2, b =512, kB =1000, K
 
259
                    // > =1024, MB =1000*1000, M =1024*1024, xM =M, GB
 
260
                    // > =1000*1000*1000, G =1024*1024*1024, and so on for T, P,
 
261
                    // > E, Z, Y.
 
262
                    if (strcmp(endptr, "K") == 0) {
 
263
                        result *= 1024;
 
264
                    } else if (strcmp(endptr, "M") == 0) {
 
265
                        result *= 1024*1024;
 
266
                    } else if (strcmp(endptr, "G") == 0) {
 
267
                        result *= 1024*1024*1024;
 
268
                    } else if (strcmp(endptr, "") != 0) {
 
269
                        c->flag_error = FLAG_ERROR_INVALID_SIZE_SUFFIX;
 
270
                        c->flag_error_name = flag;
 
271
                        // TODO: capability to report what exactly is the wrong suffix
 
272
                        return false;
 
273
                    }
 
274
 
 
275
                    if (result == ULLONG_MAX && errno == ERANGE) {
 
276
                        c->flag_error = FLAG_ERROR_INTEGER_OVERFLOW;
 
277
                        c->flag_error_name = flag;
 
278
                        return false;
 
279
                    }
 
280
 
 
281
                    c->flags[i].val.as_size = result;
 
282
                }
 
283
                break;
 
284
 
 
285
                case COUNT_FLAG_TYPES:
 
286
                default: {
 
287
                    assert(0 && "unreachable");
 
288
                    exit(69);
 
289
                }
 
290
                }
 
291
 
 
292
                found = true;
 
293
            }
 
294
        }
 
295
 
 
296
        if (!found) {
 
297
            c->flag_error = FLAG_ERROR_UNKNOWN;
 
298
            c->flag_error_name = flag;
 
299
            return false;
 
300
        }
 
301
    }
 
302
 
 
303
    c->rest_argc = argc;
 
304
    c->rest_argv = argv;
 
305
    return true;
 
306
}
 
307
 
 
308
void flag_print_options(FILE *stream)
 
309
{
 
310
    Flag_Context *c = &flag_global_context;
 
311
    for (size_t i = 0; i < c->flags_count; ++i) {
 
312
        Flag *flag = &c->flags[i];
 
313
 
 
314
        fprintf(stream, "    -%s\n", flag->name);
 
315
        fprintf(stream, "        %s\n", flag->desc);
 
316
        static_assert(COUNT_FLAG_TYPES == 4, "Exhaustive flag type defaults printing");
 
317
        switch (c->flags[i].type) {
 
318
        case FLAG_BOOL:
 
319
            if (flag->def.as_bool) {
 
320
                fprintf(stream, "        Default: %s\n", flag->def.as_bool ? "true" : "false");
 
321
            }
 
322
            break;
 
323
        case FLAG_UINT64:
 
324
            fprintf(stream, "        Default: %" PRIu64 "\n", flag->def.as_uint64);
 
325
            break;
 
326
        case FLAG_SIZE:
 
327
            fprintf(stream, "        Default: %zu\n", flag->def.as_size);
 
328
            break;
 
329
        case FLAG_STR:
 
330
            if (flag->def.as_str) {
 
331
                fprintf(stream, "        Default: %s\n", flag->def.as_str);
 
332
            }
 
333
            break;
 
334
        default:
 
335
            assert(0 && "unreachable");
 
336
            exit(69);
 
337
        }
 
338
    }
 
339
}
 
340
 
 
341
void flag_print_error(FILE *stream)
 
342
{
 
343
    Flag_Context *c = &flag_global_context;
 
344
    static_assert(COUNT_FLAG_ERRORS == 6, "Exhaustive flag error printing");
 
345
    switch (c->flag_error) {
 
346
    case FLAG_NO_ERROR:
 
347
        // NOTE: don't call flag_print_error() if flag_parse() didn't return false, okay? ._.
 
348
        fprintf(stream, "Operation Failed Successfully! Please tell the developer of this software that they don't know what they are doing! :)");
 
349
        break;
 
350
    case FLAG_ERROR_UNKNOWN:
 
351
        fprintf(stream, "ERROR: -%s: unknown flag\n", c->flag_error_name);
 
352
        break;
 
353
    case FLAG_ERROR_NO_VALUE:
 
354
        fprintf(stream, "ERROR: -%s: no value provided\n", c->flag_error_name);
 
355
        break;
 
356
    case FLAG_ERROR_INVALID_NUMBER:
 
357
        fprintf(stream, "ERROR: -%s: invalid number\n", c->flag_error_name);
 
358
        break;
 
359
    case FLAG_ERROR_INTEGER_OVERFLOW:
 
360
        fprintf(stream, "ERROR: -%s: integer overflow\n", c->flag_error_name);
 
361
        break;
 
362
    case FLAG_ERROR_INVALID_SIZE_SUFFIX:
 
363
        fprintf(stream, "ERROR: -%s: invalid size suffix\n", c->flag_error_name);
 
364
        break;
 
365
    case COUNT_FLAG_ERRORS:
 
366
    default:
 
367
        assert(0 && "unreachable");
 
368
        exit(69);
 
369
    }
 
370
}
 
371
 
 
372
#endif
 
373
// Copyright 2021 Alexey Kutepov <reximkut@gmail.com>
 
374
//
 
375
// Permission is hereby granted, free of charge, to any person obtaining a copy
 
376
// of this software and associated documentation files (the "Software"), to
 
377
// deal in the Software without restriction, including without limitation the
 
378
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 
379
// sell copies of the Software, and to permit persons to whom the Software is
 
380
// furnished to do so, subject to the following conditions:
 
381
//
 
382
// The above copyright notice and this permission notice shall be included in
 
383
// all copies or substantial portions of the Software.
 
384
//
 
385
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 
386
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 
387
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 
388
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 
389
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 
390
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 
391
// IN THE SOFTWARE.