mirror of
https://github.com/pret/pokeplatinum.git
synced 2026-08-23 09:55:29 -05:00
tools: Extract enum parsing to more generic libenum
This commit is contained in:
@@ -34,7 +34,7 @@ dataproc_templates_dir = meson.current_source_dir() / 'data'
|
||||
commonproc_dep = declare_dependency(
|
||||
link_with: static_library(
|
||||
'commonproc',
|
||||
sources: files('src/common.c', 'src/enum.c'),
|
||||
sources: files('src/common.c'),
|
||||
|
||||
c_args: [
|
||||
dataproc_cflags,
|
||||
@@ -43,7 +43,12 @@ commonproc_dep = declare_dependency(
|
||||
f'-DTEMPLATES_DIR="@dataproc_templates_dir@"',
|
||||
],
|
||||
|
||||
dependencies: [ dataproc_dep, nitroarc_dep ],
|
||||
dependencies: [
|
||||
dataproc_dep,
|
||||
nitroarc_dep,
|
||||
libenum_dep,
|
||||
libexpr_dep,
|
||||
],
|
||||
native: true,
|
||||
),
|
||||
dependencies: [ dataproc_dep, nitroarc_dep ],
|
||||
|
||||
@@ -10,11 +10,19 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "dataproc.h"
|
||||
#include "enum.h"
|
||||
#include "libenum.h"
|
||||
#include "libexpr.h"
|
||||
#include "nitroarc.h"
|
||||
|
||||
#define MAX_LOADED_ENUMS 128
|
||||
|
||||
typedef struct enum_t enum_t;
|
||||
struct enum_t {
|
||||
lookup_t *members;
|
||||
size_t size;
|
||||
char *pool;
|
||||
};
|
||||
|
||||
static enum_t loaded_enums[MAX_LOADED_ENUMS] = { 0 };
|
||||
static size_t num_loaded_enums = 0;
|
||||
|
||||
@@ -194,7 +202,7 @@ static void load_header_template(header_template_t *h, FILE *depfile) {
|
||||
memcpy(template_fname + len_out_fname, HEADER_TEMPLATE_SUFFIX, sizeof(HEADER_TEMPLATE_SUFFIX));
|
||||
|
||||
char *full_path = pathjoin(TEMPLATES_DIR, NULL, template_fname);
|
||||
char *template = fload(full_path);
|
||||
char *template = fload(full_path, NULL);
|
||||
char *marker = strstr(template, HEADER_TEMPLATE_MAGIC);
|
||||
char *footer = marker + sizeof(HEADER_TEMPLATE_MAGIC);
|
||||
*marker = '\0';
|
||||
@@ -210,7 +218,10 @@ static void load_header_template(header_template_t *h, FILE *depfile) {
|
||||
}
|
||||
|
||||
static void unload_enums(void) {
|
||||
for (size_t i = 0; i < num_loaded_enums; i++) enum_free(&loaded_enums[i]);
|
||||
for (size_t i = 0; i < num_loaded_enums; i++) {
|
||||
free(loaded_enums[i].pool);
|
||||
free(loaded_enums[i].members);
|
||||
}
|
||||
}
|
||||
|
||||
static void finish_outputs(void) {
|
||||
@@ -364,11 +375,12 @@ void splitenv(const char *name, char ***target, size_t *target_len, const char *
|
||||
}
|
||||
}
|
||||
|
||||
char* fload(const char *filename) {
|
||||
char* fload(const char *filename, size_t *out_size) {
|
||||
char *buf = NULL;
|
||||
FILE *f = fopen(filename, "rb");
|
||||
if (f == NULL) {
|
||||
fprintf(stderr, "could not open file '%s': %s\n", filename, strerror(errno));
|
||||
if (*out_size) *out_size = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -390,6 +402,7 @@ char* fload(const char *filename) {
|
||||
buf[fsize] = 0;
|
||||
|
||||
cleanup:
|
||||
if (out_size) *out_size = fsize;
|
||||
fclose(f);
|
||||
return buf;
|
||||
}
|
||||
@@ -463,6 +476,21 @@ static const char *include_paths[MAX_INCLUDES] = {
|
||||
REPO_BUILD,
|
||||
};
|
||||
|
||||
static int membercmp(const void *lhs, const void *rhs) {
|
||||
const lookup_t *l = lhs;
|
||||
const lookup_t *r = rhs;
|
||||
|
||||
if (l == NULL && r == NULL) return 0;
|
||||
else if (l == NULL) return 1;
|
||||
else if (r == NULL) return -1;
|
||||
|
||||
if (l->def == NULL && r->def == NULL) return 0;
|
||||
else if (l->def == NULL) return 1;
|
||||
else if (r->def == NULL) return -1;
|
||||
|
||||
return strcmp(l->def, r->def);
|
||||
}
|
||||
|
||||
static enum_t dp_include(
|
||||
const char *from_file,
|
||||
const char *with_prefix,
|
||||
@@ -471,6 +499,9 @@ static enum_t dp_include(
|
||||
FILE *depfile
|
||||
) {
|
||||
assert(from_file && "included filename must not be NULL");
|
||||
if (!from_defs) {
|
||||
assert(strncmp("enum", for_type, sizeof("enum") - 1) == 0 && "'for_type' value must be prefixed with 'enum '");
|
||||
}
|
||||
|
||||
char *found_file = NULL;
|
||||
for (int i = 0; i < MAX_INCLUDES && include_paths[i]; i++) {
|
||||
@@ -484,16 +515,55 @@ static enum_t dp_include(
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
char *buf = fload(found_file);
|
||||
enum_t result = from_defs
|
||||
? enum_parse_def(buf, with_prefix, ENUM_F_SORT | ENUM_F_CONVERT)
|
||||
: enum_parse_one(buf, ENUM_F_SORT | ENUM_F_CONVERT, NULL);
|
||||
size_t bufsize = 0;
|
||||
char *endptr = NULL;
|
||||
char *buf = fload(found_file, &bufsize);
|
||||
enum_seq_t parsed = from_defs
|
||||
? libenum_loadcpp(buf, bufsize, with_prefix, &endptr)
|
||||
: libenum_find(buf, bufsize, &for_type[5], &endptr); // strip 'enum ' prefix
|
||||
|
||||
dp_register((lookup_t *)result.syms, result.len, for_type);
|
||||
if (parsed.errc != LIBENUM_E_OK) {
|
||||
// TODO: line number + column number of the error
|
||||
fprintf(stderr, "syntax error while parsing included file '%s': %s\n",
|
||||
found_file, libenum_errs(parsed.errc));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
else if (parsed.members == NULL) {
|
||||
fprintf(stderr, "enum named '%s' could not be found in file '%s'\n",
|
||||
for_type, found_file);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
enum_t result = {
|
||||
.members = calloc(parsed.size, sizeof(*result.members)),
|
||||
.size = parsed.size,
|
||||
.pool = parsed.pool,
|
||||
};
|
||||
|
||||
long curr = 0;
|
||||
for (size_t i = 0; i < parsed.size; i++) {
|
||||
result.members[i].def = parsed.members[i].name;
|
||||
if (parsed.members[i].expr == NULL) {
|
||||
result.members[i].val = curr++;
|
||||
}
|
||||
else {
|
||||
// WARN: this cast is filthy, but it works!
|
||||
result.members[i].val = libexpr_eval(parsed.members[i].expr, &endptr, (scope_t *)&result);
|
||||
curr = result.members[i].val + 1;
|
||||
if (*endptr) {
|
||||
fprintf(stderr, "syntax error while parsing expression '%s' from included file '%s'\n",
|
||||
parsed.members[i].expr, found_file);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qsort(result.members, result.size, sizeof(*result.members), membercmp);
|
||||
|
||||
dp_register(result.members, result.size, for_type);
|
||||
fputs(found_file, depfile);
|
||||
fputc(' ', depfile);
|
||||
|
||||
free(buf);
|
||||
free(found_file);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ char* strupper(const char *s);
|
||||
char* strjoin(const char *s, const char *with, const char *sep);
|
||||
|
||||
void splitenv(const char *name, char ***target, size_t *target_len, const char **extra, size_t extra_len);
|
||||
char* fload(const char *filename);
|
||||
char* fload(const char *filename, size_t *out_size);
|
||||
char* pathjoin(const char *basedir, const char *subdir, const char *file);
|
||||
char* guardify(const char *path);
|
||||
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
#include "enum.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// Duplicate a string onto the heap.
|
||||
static char* strdup_(const char *s) {
|
||||
size_t l = strlen(s);
|
||||
char *d = calloc(l + 1, sizeof(*d));
|
||||
memcpy(d, s, l);
|
||||
return d;
|
||||
}
|
||||
|
||||
// Find the first occurrence of `c` (or `\0`) within `s`.
|
||||
static char* strchrnul_(const char *s, int c) {
|
||||
while (s && *s && *s != c) s++;
|
||||
return (char *)s;
|
||||
}
|
||||
|
||||
// Find the first occurrence of a character OTHER than `c` (or `\0`) within `s`.
|
||||
static char* strnchr(const char *s, int c) {
|
||||
while (s && *s && *s == c) s++;
|
||||
return (char *)s;
|
||||
}
|
||||
|
||||
// Locates the first occurrence of `c` (or `\0`) within `*s` and replaces it
|
||||
// with `\0`. `*s` is then advanced to the succeeding character, and the
|
||||
// original value of `*s` is returned.
|
||||
//
|
||||
// If `*s` is NULL, then NULL is returned immediately.
|
||||
static char* strcsep(char **s, char c) {
|
||||
char *p = *s;
|
||||
|
||||
char *e = strchrnul_(p, c);
|
||||
if (*e) *e++ = 0; else e = 0;
|
||||
|
||||
*s = e;
|
||||
return p;
|
||||
}
|
||||
|
||||
// Locates the first occurrence of any character from `charset` within `*s` and
|
||||
// replaces it with `\0`. `*s` is then advanced to the succeeding character, and
|
||||
// the original value of `*s` is returned.
|
||||
//
|
||||
// If `*s` is NULL, then NULL is returned immediately.
|
||||
//
|
||||
// Generalization of `strcsep` for multiple-characters.
|
||||
static char* strssep(char **s, const char *charset) {
|
||||
char *p = *s;
|
||||
if (!p) return NULL;
|
||||
|
||||
char *e = p + strcspn(p, charset);
|
||||
if (*e) *e++ = 0; else e = 0;
|
||||
|
||||
*s = e;
|
||||
return p;
|
||||
}
|
||||
|
||||
#define INIT_CAP 256
|
||||
|
||||
static int push_symval(enum_t *table, const char *tok, const char *val, unsigned flags) {
|
||||
if (table->len + 1 >= table->cap) {
|
||||
size_t new_cap = table->cap * 3 / 2;
|
||||
symb_t *new_sym = realloc(table->syms, new_cap * sizeof(*table->syms));
|
||||
if (new_sym == NULL) return 1;
|
||||
|
||||
table->syms = new_sym;
|
||||
table->cap = new_cap;
|
||||
}
|
||||
|
||||
if (flags & ENUM_F_CONVERT) { // if val == NULL, get previous (syms[len-1]) and increment
|
||||
if (val == NULL) {
|
||||
const symb_t *prev_sym = &table->syms[table->len - 1];
|
||||
table->syms[table->len++] = (symb_t){
|
||||
.tok = (char *)tok,
|
||||
.val_int = prev_sym->val_int + 1,
|
||||
};
|
||||
}
|
||||
else {
|
||||
table->syms[table->len++] = (symb_t){
|
||||
.tok = (char *)tok,
|
||||
.val_int = strtol(val, NULL, 0),
|
||||
};
|
||||
}
|
||||
}
|
||||
else {
|
||||
table->syms[table->len++] = (symb_t){
|
||||
.tok = (char *)tok,
|
||||
.val_lit = (char *)val
|
||||
};
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int compare_tok(const void *lhs, const void *rhs) {
|
||||
const symb_t *sym_lhs = lhs;
|
||||
const symb_t *sym_rhs = rhs;
|
||||
return strcmp(sym_lhs->tok, sym_rhs->tok);
|
||||
}
|
||||
|
||||
static int push_define(enum_t *table, char **s, unsigned flags) {
|
||||
char *sym = strcsep(s, ' ');
|
||||
char *val = strnchr(*s, ' ');
|
||||
|
||||
return (val && *val) ? push_symval(table, sym, val, flags) : 0;
|
||||
}
|
||||
|
||||
static void push_enum_member(enum_t *table, char **s, unsigned flags) {
|
||||
char *line = strnchr(strcsep(s, '\n'), ' ');
|
||||
char *sym = strssep(&line, " ,");
|
||||
char *equ = strnchr(line, ' ');
|
||||
char *val = equ && *equ == '=' ? strnchr(equ + 1, ' ') : NULL;
|
||||
char *end = strchrnul_(val, ',');
|
||||
|
||||
if (end) *end = 0;
|
||||
if (*sym) push_symval(table, sym, val, flags);
|
||||
}
|
||||
|
||||
enum_t enum_parse_def(const char *buf, const char *prefix, unsigned flags) {
|
||||
enum_t ret = {
|
||||
.name = prefix ? (char *)prefix : NULL,
|
||||
.pool = strdup_(buf),
|
||||
.syms = calloc(INIT_CAP, sizeof(*ret.syms)),
|
||||
.cnv = !!(flags & ENUM_F_CONVERT),
|
||||
.len = 0,
|
||||
.cap = INIT_CAP,
|
||||
};
|
||||
|
||||
char *s = ret.pool;
|
||||
size_t len = prefix ? strlen(prefix) : 0;
|
||||
|
||||
while (*s) {
|
||||
char *line = strcsep(&s, '\n');
|
||||
|
||||
if (strncmp(line, "#define ", 8) != 0) continue; else line += 8;
|
||||
if (prefix && strncmp(line, prefix, len) != 0) continue;
|
||||
if (push_define(&ret, &line, flags) != 0) break;
|
||||
}
|
||||
|
||||
if (flags & ENUM_F_SORT) qsort(ret.syms, ret.len, sizeof(*ret.syms), compare_tok);
|
||||
return ret;
|
||||
}
|
||||
|
||||
enum_t enum_parse_one(const char *buf, unsigned flags, char **endptr) {
|
||||
enum_t ret = {
|
||||
.name = NULL,
|
||||
.pool = strdup_(buf),
|
||||
.syms = calloc(INIT_CAP, sizeof(*ret.syms)),
|
||||
.cnv = !!(flags & ENUM_F_CONVERT),
|
||||
.len = 0,
|
||||
.cap = INIT_CAP,
|
||||
};
|
||||
|
||||
char *s = ret.pool;
|
||||
while (*s) {
|
||||
char *line = strcsep(&s, '\n');
|
||||
|
||||
if (strncmp(line, "enum ", 5) != 0) continue; else line += 5;
|
||||
if (*line != '{') {
|
||||
ret.name = strssep(&line, " {");
|
||||
}
|
||||
|
||||
// NOTE: assumption: opening brace is on the same line, and members
|
||||
// are defined one-per-line
|
||||
|
||||
while (*s && strncmp(s, "};", 2) != 0) {
|
||||
push_enum_member(&ret, &s, flags);
|
||||
}
|
||||
|
||||
s += 2;
|
||||
break; // stop after the one `enum` is found
|
||||
}
|
||||
|
||||
if (flags & ENUM_F_SORT) qsort(ret.syms, ret.len, sizeof(*ret.syms), compare_tok);
|
||||
if (endptr) *endptr = (char *)buf + (s - ret.pool);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum_t* push_subtable(enums_t *tables, const char *name, unsigned flags) {
|
||||
if (tables->len + 1 >= tables->cap) {
|
||||
size_t new_cap = tables->cap * 3 / 2;
|
||||
enum_t *new_arr = realloc(tables->enums, new_cap * sizeof(*tables->enums));
|
||||
if (new_arr == NULL) return NULL;
|
||||
|
||||
tables->enums = new_arr;
|
||||
tables->cap = new_cap;
|
||||
}
|
||||
|
||||
enum_t *next = &tables->enums[tables->len++];
|
||||
next->name = (char *)name;
|
||||
next->pool = NULL;
|
||||
next->syms = calloc(INIT_CAP, sizeof(*next->syms));
|
||||
next->cnv = !!(flags & ENUM_F_CONVERT);
|
||||
next->len = 0;
|
||||
next->cap = INIT_CAP;
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
static int compare_table(const void *lhs, const void *rhs) {
|
||||
const enum_t *table_lhs = lhs;
|
||||
const enum_t *table_rhs = rhs;
|
||||
return strcmp(table_lhs->name, table_rhs->name);
|
||||
}
|
||||
|
||||
enums_t enum_parse_all(const char *buf, unsigned flags) {
|
||||
enums_t ret = {
|
||||
.enums = calloc(INIT_CAP, sizeof(*ret.enums)),
|
||||
.pool = strdup_(buf),
|
||||
.len = 0,
|
||||
.cap = INIT_CAP,
|
||||
};
|
||||
|
||||
push_subtable(&ret, NULL, flags);
|
||||
|
||||
char *s = ret.pool;
|
||||
while (*s) {
|
||||
char *line = strcsep(&s, '\n');
|
||||
enum_t *sub = &ret.enums[0];
|
||||
|
||||
if (strncmp(line, "enum ", 5) == 0) {
|
||||
line += 5;
|
||||
if (*line != '{') {
|
||||
sub = push_subtable(&ret, strssep(&line, " {"), flags);
|
||||
if (sub == NULL) break;
|
||||
}
|
||||
|
||||
while (*s && strncmp(s, "};", 2) != 0) {
|
||||
push_enum_member(sub, &s, flags);
|
||||
}
|
||||
}
|
||||
else if (strncmp(line, "#define ", 8) == 0) {
|
||||
line += 8;
|
||||
push_define(sub, &line, flags);
|
||||
}
|
||||
else continue;
|
||||
}
|
||||
|
||||
if (flags & ENUM_F_SORT) {
|
||||
for (size_t i = 0; i < ret.len; i++) {
|
||||
qsort(ret.enums[i].syms,
|
||||
ret.enums[i].len,
|
||||
sizeof(*ret.enums[i].syms),
|
||||
compare_tok);
|
||||
}
|
||||
|
||||
qsort(ret.enums + 1, ret.len - 1, sizeof(*ret.enums), compare_table);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void enum_free(enum_t *table) {
|
||||
free(table->pool);
|
||||
free(table->syms);
|
||||
|
||||
table->name = NULL;
|
||||
table->pool = NULL;
|
||||
table->syms = NULL;
|
||||
}
|
||||
|
||||
void enum_free_all(enums_t *tables) {
|
||||
for (size_t i = 0; i < tables->len; i++) enum_free(&tables->enums[i]);
|
||||
|
||||
free(tables->enums);
|
||||
free(tables->pool);
|
||||
|
||||
tables->enums = NULL;
|
||||
tables->pool = NULL;
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
#ifndef ENUM_H
|
||||
#define ENUM_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct symb_t symb_t;
|
||||
struct symb_t {
|
||||
union {
|
||||
char *val_lit;
|
||||
long val_int;
|
||||
};
|
||||
char *tok;
|
||||
};
|
||||
|
||||
typedef struct enum_t enum_t;
|
||||
struct enum_t {
|
||||
char *name;
|
||||
char *pool;
|
||||
symb_t *syms;
|
||||
bool cnv;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
};
|
||||
|
||||
typedef struct enums_t enums_t;
|
||||
struct enums_t {
|
||||
enum_t *enums;
|
||||
char *pool;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
};
|
||||
|
||||
#define ENUM_F_SORT (1 << 0)
|
||||
#define ENUM_F_CONVERT (1 << 1)
|
||||
|
||||
// Parse a C file `buf` for defined preprocesor tokens with replacement values
|
||||
// and return them as a symbol-table. If `prefix` is given as non-`NULL`, it
|
||||
// will be used to filter preprocessor tokens from `buf` that are included in
|
||||
// the output.
|
||||
//
|
||||
// If `flags` contains `ENUM_F_SORT`, then symbols in the output table will be
|
||||
// sorted lexicographically by their tokens.
|
||||
//
|
||||
// If `flags` contains `ENUM_F_CONVERT`, then numeric strings will be parsed
|
||||
// into integer values.
|
||||
enum_t enum_parse_def(const char *buf, const char *prefix, unsigned flags);
|
||||
|
||||
// Parse a C file `buf` for a named `enum` and return its member-names as a
|
||||
// symbol-table. If `e_name` is given as `NULL`, then this routine will return
|
||||
// an empty symbol-table; otherwise, its value is used to find a matching `enum`
|
||||
// from `buf` whose member-names shall be loaded into the output.
|
||||
//
|
||||
// If `flags` contains `ENUM_F_SORT`, then symbols in the output table will be
|
||||
// sorted lexicographically by their tokens.
|
||||
//
|
||||
// If `flags` contains `ENUM_F_CONVERT`, then numeric strings will be parsed
|
||||
// into integer values.
|
||||
//
|
||||
// If `endptr` is not `NULL`, then `*endptr` will be set to the next unprocessed
|
||||
// character in `buf` upon exit.
|
||||
enum_t enum_parse_one(const char *buf, unsigned flags, char **endptr);
|
||||
|
||||
// Parse a C file `buf` for both `enum`s and defined preprocessor tokens and
|
||||
// return them as a table of symbol-tables. All preprocessor tokens and members
|
||||
// of unnamed `enum`s will be present in the symbol-table at `enums[0]`; members
|
||||
// of named `enum`s will be present in their own individual symbol-tables.
|
||||
//
|
||||
// If `flags` contains `ENUM_F_SORT`, then symbols in the output tables will be
|
||||
// sorted lexicographically by their tokens, and all named output tables wiil be
|
||||
// sorted lexicographically by their names.
|
||||
enums_t enum_parse_all(const char *buf, unsigned flags);
|
||||
|
||||
// Free allocations in a symbol-table.
|
||||
void enum_free(enum_t *table);
|
||||
void enum_free_all(enums_t *tables);
|
||||
|
||||
#endif // ENUM_H
|
||||
@@ -311,6 +311,27 @@ datagen_cpp_commands = [
|
||||
for file in (homedir / "tools" / "datagen").rglob("*.cpp")
|
||||
]
|
||||
|
||||
enumproc_c_commands = [
|
||||
{
|
||||
"directory": builddir,
|
||||
"arguments": [
|
||||
"gcc",
|
||||
f"-I{homedir}/tools/enumproc",
|
||||
"-std=gnu17",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Wpedantic",
|
||||
"-Wconversion",
|
||||
"-Wno-sign-conversion",
|
||||
"-o",
|
||||
file.with_suffix(".o"),
|
||||
file.resolve(),
|
||||
],
|
||||
"file": file.resolve(),
|
||||
}
|
||||
for file in (homedir / "tools" / "enumproc").rglob("*.c")
|
||||
]
|
||||
|
||||
dataproc_c_commands = [
|
||||
{
|
||||
"directory": builddir,
|
||||
@@ -318,6 +339,7 @@ dataproc_c_commands = [
|
||||
"gcc",
|
||||
f"-I{homedir}/subprojects/yyjson-0.12.0/src",
|
||||
f"-I{homedir}/tools/nitroarc/lib/include",
|
||||
f"-I{homedir}/tools/enumproc",
|
||||
f"-I{homedir}/tools/dataproc/lib/include",
|
||||
f"-I{homedir}/include",
|
||||
f"-I{builddir}",
|
||||
|
||||
509
tools/enumproc/libenum.c
Normal file
509
tools/enumproc/libenum.c
Normal file
@@ -0,0 +1,509 @@
|
||||
/*
|
||||
* #include <libenum.h> - A library for loading C enums into member-mappings
|
||||
* Copyright (C) 2026 <rachel@lhea.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "libenum.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct enum_lex enum_lex_t;
|
||||
struct enum_lex {
|
||||
const char *p; // cursor location
|
||||
const char *e; // end-marker
|
||||
const char *s; // pointer to the start of the token
|
||||
size_t n; // size of the token
|
||||
};
|
||||
|
||||
static unsigned init(enum_seq_t *seq, size_t size);
|
||||
static int lex(enum_lex_t *lexer);
|
||||
static char* skip(const char *str, int (*is_space)(char c));
|
||||
|
||||
static inline int is_hspace(char c) {
|
||||
return c == ' ' || c == '\t';
|
||||
}
|
||||
|
||||
static inline int is_vspace(char c) {
|
||||
return c == '\r' || c == '\n';
|
||||
}
|
||||
|
||||
static inline int is_wspace(char c) {
|
||||
return is_hspace(c) || is_vspace(c);
|
||||
}
|
||||
|
||||
static inline int is_alpha(char c) {
|
||||
return ((c >= 'a') && (c <= 'z'))
|
||||
|| ((c >= 'A') && (c <= 'Z'));
|
||||
}
|
||||
|
||||
static inline int is_digit(char c) {
|
||||
return (c >= '0') && (c <= '9');
|
||||
}
|
||||
|
||||
static inline int is_word(char c) {
|
||||
return is_alpha(c) || is_digit(c) || c == '_';
|
||||
}
|
||||
|
||||
enum {
|
||||
T_EOS,
|
||||
T_RBRACE,
|
||||
T_KWENUM,
|
||||
T_LBRACE,
|
||||
T_COMMA,
|
||||
T_EQUALS,
|
||||
T_IDENTIFIER,
|
||||
T_EXPRESSION,
|
||||
|
||||
T_INVALID = -1,
|
||||
T_INVALIDKW = -2,
|
||||
};
|
||||
|
||||
#define return_error(code) do { result.errc = code; goto error; } while (0)
|
||||
|
||||
#define EXPECT_LBRACE (1 << 0)
|
||||
#define EXPECT_COMMA (1 << 1)
|
||||
#define EXPECT_EQUALS (1 << 2)
|
||||
#define EXPECT_NAME (1 << 3)
|
||||
#define EXPECT_MEMBER (1 << 4)
|
||||
|
||||
static inline char* poolpush(const char **target, char *dst, const char *src, size_t n) {
|
||||
assert(target);
|
||||
assert(dst);
|
||||
assert(src);
|
||||
|
||||
*target = memcpy(dst, src, n);
|
||||
dst[n] = 0;
|
||||
return dst + n + 1;
|
||||
}
|
||||
|
||||
enum_seq_t libenum_load(const char *str, size_t size, char **endptr) {
|
||||
enum_lex_t lexer = {
|
||||
.p = str,
|
||||
.e = str + size,
|
||||
.s = NULL,
|
||||
.n = 0,
|
||||
};
|
||||
|
||||
enum_seq_t result = { 0 };
|
||||
if (init(&result, size) != LIBENUM_E_OK) return_error(result.errc);
|
||||
|
||||
char *p_pool = result.pool;
|
||||
int token = 0;
|
||||
unsigned expect = 0;
|
||||
size_t cap = 256;
|
||||
|
||||
while ((token = lex(&lexer)) > T_RBRACE) {
|
||||
switch (token) {
|
||||
case T_KWENUM:
|
||||
// `enum` is only valid at the start of the stream.
|
||||
if (result.members) return_error(LIBENUM_E_KEYWORD);
|
||||
|
||||
result.members = calloc(cap, sizeof(*result.members));
|
||||
if (!result.members) return_error(LIBENUM_E_ALLOC);
|
||||
|
||||
expect = EXPECT_LBRACE | EXPECT_NAME;
|
||||
break;
|
||||
|
||||
case T_LBRACE:
|
||||
if ((expect & EXPECT_LBRACE) == 0) return_error(LIBENUM_E_TOKEN);
|
||||
expect = EXPECT_MEMBER;
|
||||
break;
|
||||
|
||||
case T_COMMA:
|
||||
if ((expect & EXPECT_COMMA) == 0) return_error(LIBENUM_E_TOKEN);
|
||||
expect = EXPECT_MEMBER;
|
||||
result.size++;
|
||||
break;
|
||||
|
||||
case T_EQUALS:
|
||||
if ((expect & EXPECT_EQUALS) == 0) return_error(LIBENUM_E_TOKEN);
|
||||
|
||||
lexer.p = skip(lexer.p, is_wspace);
|
||||
lexer.s = lexer.p;
|
||||
|
||||
for (
|
||||
lexer.p = skip(lexer.p, is_wspace), lexer.s = lexer.p;
|
||||
lexer.p < lexer.e && *lexer.p && *lexer.p != ',' && *lexer.p != '}';
|
||||
lexer.p++
|
||||
);
|
||||
|
||||
p_pool = poolpush(&result.members[result.size].expr, p_pool,
|
||||
lexer.s, lexer.p - lexer.s);
|
||||
expect = EXPECT_COMMA;
|
||||
break;
|
||||
|
||||
case T_IDENTIFIER:
|
||||
if ((expect & EXPECT_NAME)) {
|
||||
p_pool = poolpush(&result.name, p_pool, lexer.s, lexer.n);
|
||||
expect = EXPECT_LBRACE;
|
||||
break;
|
||||
}
|
||||
|
||||
if ((expect & EXPECT_MEMBER) == 0) return_error(LIBENUM_E_STRAYMEMB);
|
||||
if (result.size + 1 >= cap) {
|
||||
const size_t new = cap * 2;
|
||||
const size_t size = new * sizeof(*result.members);
|
||||
enum_member_t *tmp = realloc(result.members, size);
|
||||
if (!tmp) return_error(LIBENUM_E_ALLOC);
|
||||
|
||||
memset(&tmp[result.size], 0, cap * sizeof(*tmp));
|
||||
result.members = tmp;
|
||||
cap = new;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < result.size; i++) {
|
||||
if (strncmp(result.members[i].name, lexer.s, lexer.n) == 0
|
||||
&& strlen(result.members[i].name) == lexer.n) {
|
||||
return_error(LIBENUM_E_DUPLICATE);
|
||||
}
|
||||
}
|
||||
|
||||
p_pool = poolpush(&result.members[result.size].name, p_pool,
|
||||
lexer.s, lexer.n);
|
||||
expect = EXPECT_EQUALS | EXPECT_COMMA;
|
||||
break;
|
||||
|
||||
default: assert(0 && "unexpected token type");
|
||||
}
|
||||
}
|
||||
|
||||
switch (token) {
|
||||
case T_EOS: return_error(LIBENUM_E_EOS);
|
||||
case T_INVALID: return_error(LIBENUM_E_TOKEN);
|
||||
case T_INVALIDKW: return_error(LIBENUM_E_KEYWORD);
|
||||
|
||||
case T_RBRACE:
|
||||
// Account for any member followed by a closing-brace
|
||||
if (expect & EXPECT_COMMA) result.size++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.size == 0) return_error(LIBENUM_E_EMPTY);
|
||||
|
||||
lexer.p = skip(lexer.p, is_wspace);
|
||||
if (lexer.p >= lexer.e || *lexer.p != ';') return_error(LIBENUM_E_SEMICOLON);
|
||||
|
||||
if (endptr) *endptr = (char *)lexer.p;
|
||||
return result;
|
||||
|
||||
error:
|
||||
free(result.members);
|
||||
free(result.pool);
|
||||
if (endptr) *endptr = (char *)(lexer.s ? lexer.s : lexer.p);
|
||||
|
||||
result.members = NULL;
|
||||
result.pool = NULL;
|
||||
result.name = NULL;
|
||||
result.size = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
#define countof(a) (sizeof(a) / sizeof(*(a)))
|
||||
#define lengthof(s) (countof(s) - 1)
|
||||
|
||||
enum_seq_t libenum_find(const char *str, size_t size, const char *name,
|
||||
char **endptr) {
|
||||
const char *p = str;
|
||||
const char *e = str + size;
|
||||
|
||||
// skip over tokens / lines until we find an enum keyword which is followed
|
||||
// by an identifier matching 'name'
|
||||
for (; p < e; p++) {
|
||||
if (*p == '#') { // scan to end-of-line, then continue
|
||||
do { p++; } while (*p != '\n');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_alpha(*p)) continue;
|
||||
|
||||
const char *s = p;
|
||||
p = skip(p, is_wspace);
|
||||
if ((size_t)(e - p) < lengthof("enum ")) continue; // too short
|
||||
if (strncmp(p, "enum", lengthof("enum")) != 0) continue; // not "enum"
|
||||
if (!is_wspace(p[lengthof("enum")])) continue; // unnamed
|
||||
|
||||
p += lengthof("enum");
|
||||
const char *sym_beg = skip(p, is_wspace);
|
||||
if (sym_beg < e && !is_alpha(*sym_beg) && *sym_beg != '_') {
|
||||
p = sym_beg;
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *sym_end = sym_beg;
|
||||
do { sym_end++; } while (sym_end < e && is_word(*sym_end));
|
||||
|
||||
if (strncmp(name, sym_beg, sym_end - sym_beg) != 0) {
|
||||
p = sym_end;
|
||||
continue;
|
||||
}
|
||||
|
||||
return libenum_load(s, e - s, endptr);
|
||||
}
|
||||
|
||||
if (endptr) *endptr = (char *)p;
|
||||
return (enum_seq_t){ 0 };
|
||||
}
|
||||
|
||||
// reference: https://cppreference.com/c/keyword
|
||||
static const char *keywords[] = {
|
||||
"auto",
|
||||
"break",
|
||||
"case",
|
||||
"char",
|
||||
"const",
|
||||
"continue",
|
||||
"default",
|
||||
"do",
|
||||
"double",
|
||||
"else",
|
||||
// "enum" is allowed for obvious reasons
|
||||
"extern",
|
||||
"float",
|
||||
"for",
|
||||
"goto",
|
||||
"if",
|
||||
"int",
|
||||
"long",
|
||||
"register",
|
||||
"return",
|
||||
"short",
|
||||
"signed",
|
||||
"sizeof",
|
||||
"static",
|
||||
"struct",
|
||||
"switch",
|
||||
"typedef",
|
||||
"union",
|
||||
"unsigned",
|
||||
"void",
|
||||
"volatile",
|
||||
"while",
|
||||
|
||||
#if __STDC_VERSION__ >= 199901L
|
||||
"inline",
|
||||
"restrict",
|
||||
"_Bool",
|
||||
"_Complex",
|
||||
"_Imaginary",
|
||||
#endif
|
||||
|
||||
#if __STDC_VERSION__ >= 201112L
|
||||
"_Alignas",
|
||||
"_Alignof",
|
||||
"_Atomic",
|
||||
"_Generic",
|
||||
"_Noreturn",
|
||||
"_Static_assert",
|
||||
"_Thread_local",
|
||||
#endif
|
||||
|
||||
#if __STDC_VERSION__ >= 202311L
|
||||
"alignas",
|
||||
"alignof",
|
||||
"bool",
|
||||
"constexpr",
|
||||
"false",
|
||||
"nullptr",
|
||||
"static_assert",
|
||||
"thread_local",
|
||||
"true",
|
||||
"typeof",
|
||||
"typeof_unqual",
|
||||
"_BitInt",
|
||||
"_Decimal128",
|
||||
"_Decimal32",
|
||||
"_Decimal64",
|
||||
#endif
|
||||
};
|
||||
|
||||
static int is_keyword(const char *s, size_t n) {
|
||||
for (size_t i = 0; i < countof(keywords); i++) {
|
||||
if (strncmp(keywords[i], s, n) == 0) return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lex(enum_lex_t *lexer) {
|
||||
lexer->p = skip(lexer->p, is_wspace);
|
||||
lexer->s = NULL;
|
||||
|
||||
if (*lexer->p == '\0' || lexer->p >= lexer->e) return T_EOS;
|
||||
|
||||
if (is_alpha(*lexer->p) || *lexer->p == '_') {
|
||||
lexer->s = lexer->p;
|
||||
do { lexer->p++; } while (lexer->p < lexer->e && is_word(*lexer->p));
|
||||
|
||||
lexer->n = lexer->p - lexer->s;
|
||||
if (strncmp(lexer->s, "enum", lexer->n) == 0) return T_KWENUM;
|
||||
if (is_keyword(lexer->s, lexer->n)) return T_INVALIDKW;
|
||||
return T_IDENTIFIER;
|
||||
}
|
||||
|
||||
switch (*lexer->p) {
|
||||
case '{': lexer->p++; return T_LBRACE;
|
||||
case '}': lexer->p++; return T_RBRACE;
|
||||
case ',': lexer->p++; return T_COMMA;
|
||||
case '=': lexer->p++; return T_EQUALS;
|
||||
|
||||
default: return T_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned init(enum_seq_t *seq, size_t size) {
|
||||
seq->pool = calloc(size + 1, sizeof(*seq->pool));
|
||||
if (!seq->pool) return (seq->errc = LIBENUM_E_ALLOC);
|
||||
|
||||
seq->name = "";
|
||||
return LIBENUM_E_OK;
|
||||
}
|
||||
|
||||
static char* skip(const char *str, int (*is_space)(char c)) {
|
||||
assert(str);
|
||||
|
||||
for (;;) {
|
||||
while (is_space(*str)) str++;
|
||||
|
||||
if (str[0] == '/' && str[1] == '/') {
|
||||
str += 2;
|
||||
while (str[0] && str[0] != '\r' && str[0] != '\n') str++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str[0] == '/' && str[1] == '*') {
|
||||
const char *start = str;
|
||||
|
||||
str += 2;
|
||||
while (str[0] && str[1] && !(str[0] == '*' && str[1] == '/')) str++;
|
||||
if (!str[0] || !str[1]) { str = start; break; } // unterminated
|
||||
str += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return (char *)str;
|
||||
}
|
||||
|
||||
enum_seq_t libenum_loadcpp(const char *str, size_t size, const char *prefix,
|
||||
char **endptr) {
|
||||
const char *p = str;
|
||||
const char *e = str + size;
|
||||
enum_seq_t result = { 0 };
|
||||
if (init(&result, size) != LIBENUM_E_OK) return_error(result.errc);
|
||||
|
||||
char *p_pool = result.pool;
|
||||
size_t cap = 256;
|
||||
|
||||
result.members = calloc(cap, sizeof(*result.members));
|
||||
if (!result.members) return_error(LIBENUM_E_ALLOC);
|
||||
|
||||
size_t prefix_len = 0;
|
||||
if (!prefix) prefix = "";
|
||||
else prefix_len = strlen(prefix);
|
||||
|
||||
const char *s, *lf;
|
||||
for (p = skip(p, is_hspace); p < e; p++) {
|
||||
s = p;
|
||||
lf = p;
|
||||
while (lf < e && *lf != '\n') lf++; // TODO: escaped new-lines?
|
||||
p = lf;
|
||||
|
||||
if (lf[-1] == '\r') lf--;
|
||||
if (*s != '#') continue;
|
||||
if (strncmp(s + 1, "define", lengthof("define")) != 0) continue;
|
||||
|
||||
// Parse out the symbol name
|
||||
const char *sym_beg = skip(s + lengthof("#define"), is_hspace);
|
||||
const char *sym_end = sym_beg;
|
||||
if (sym_beg >= lf || (!is_alpha(*sym_beg) && *sym_beg != '_')) {
|
||||
p = sym_beg;
|
||||
return_error(LIBENUM_E_MISSING);
|
||||
}
|
||||
|
||||
do { sym_end++; } while (sym_end < e && is_word(*sym_end));
|
||||
if (is_vspace(*sym_end) || *sym_end == '(') continue;
|
||||
if (!is_hspace(*sym_end)) {
|
||||
p = sym_end;
|
||||
return_error(LIBENUM_E_TOKEN);
|
||||
}
|
||||
|
||||
if (strncmp(prefix, sym_beg, prefix_len) != 0) continue;
|
||||
|
||||
const char *expr_beg = skip(sym_end + 1, is_hspace);
|
||||
const char *expr_end = lf;
|
||||
|
||||
if (result.size + 1 >= cap) {
|
||||
const size_t new = cap * 2;
|
||||
const size_t size = new * sizeof(*result.members);
|
||||
enum_member_t *tmp = realloc(result.members, size);
|
||||
if (!tmp) return_error(LIBENUM_E_ALLOC);
|
||||
|
||||
memset(&tmp[result.size], 0, cap * sizeof(*tmp));
|
||||
result.members = tmp;
|
||||
cap = new;
|
||||
}
|
||||
|
||||
const size_t sym_len = sym_end - sym_beg;
|
||||
const size_t expr_len = expr_end - expr_beg;
|
||||
for (size_t i = 0; i < result.size; i++) {
|
||||
if (strncmp(result.members[i].name, sym_beg, sym_len) == 0) {
|
||||
p = sym_beg;
|
||||
return_error(LIBENUM_E_DUPLICATE);
|
||||
}
|
||||
}
|
||||
|
||||
enum_member_t *memb = &result.members[result.size++];
|
||||
p_pool = poolpush(&memb->name, p_pool, sym_beg, sym_len);
|
||||
p_pool = poolpush(&memb->expr, p_pool, expr_beg, expr_len);
|
||||
}
|
||||
|
||||
if (result.size == 0) free(result.members); // unlike enums, this is valid
|
||||
if (endptr) *endptr = (char *)p;
|
||||
return result;
|
||||
|
||||
error:
|
||||
free(result.members);
|
||||
free(result.pool);
|
||||
if (endptr) *endptr = (char *)p;
|
||||
|
||||
result.members = NULL;
|
||||
result.pool = NULL;
|
||||
result.name = NULL;
|
||||
result.size = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* libenum_errs(unsigned errc) {
|
||||
switch (errc) {
|
||||
case LIBENUM_E_OK: return "(ok)";
|
||||
case LIBENUM_E_ALLOC: return "allocation failure";
|
||||
case LIBENUM_E_EOS: return "unexpected end-of-stream";
|
||||
case LIBENUM_E_TOKEN: return "unexpected token in enum definition";
|
||||
case LIBENUM_E_KEYWORD: return "stray keyword in enum definition";
|
||||
case LIBENUM_E_STRAYMEMB: return "unexpected member definition";
|
||||
case LIBENUM_E_DUPLICATE: return "duplicate member definition";
|
||||
case LIBENUM_E_EMPTY: return "empty enum definition";
|
||||
case LIBENUM_E_SEMICOLON: return "missing semicolon after enum definition";
|
||||
case LIBENUM_E_MISSING: return "missing preprocessor substitution";
|
||||
|
||||
default: return "(unknown error code)";
|
||||
}
|
||||
}
|
||||
120
tools/enumproc/libenum.h
Normal file
120
tools/enumproc/libenum.h
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* #include <libenum.h> - A library for loading C enums into member-mappings
|
||||
* Copyright (C) 2026 <rachel@lhea.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef LIBENUM_H
|
||||
#define LIBENUM_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define LIBENUM_E_OK 0
|
||||
#define LIBENUM_E_ALLOC 1
|
||||
#define LIBENUM_E_EOS 2
|
||||
#define LIBENUM_E_TOKEN 3
|
||||
#define LIBENUM_E_KEYWORD 4
|
||||
#define LIBENUM_E_STRAYMEMB 5
|
||||
#define LIBENUM_E_DUPLICATE 6
|
||||
#define LIBENUM_E_EMPTY 7
|
||||
#define LIBENUM_E_SEMICOLON 8
|
||||
#define LIBENUM_E_MISSING 9
|
||||
|
||||
typedef struct enum_member enum_member_t;
|
||||
typedef struct enum_seq enum_seq_t;
|
||||
|
||||
struct enum_member {
|
||||
const char *name;
|
||||
const char *expr;
|
||||
};
|
||||
|
||||
struct enum_seq {
|
||||
const char *name;
|
||||
enum_member_t *members;
|
||||
size_t size;
|
||||
char *pool;
|
||||
unsigned errc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Load a C-style enum definition from `str` into a member-mapping.
|
||||
*
|
||||
* The resulting structure contains two allocations to be `free`d by the caller:
|
||||
*
|
||||
* 1. `members`, which contains the list of members.
|
||||
* 2. `pool`, which is contains all zero-terminated strings present in the
|
||||
* member-mapping.
|
||||
*
|
||||
* Each member's `name` field points to a zero-terminated string. If the value
|
||||
* of a member is assigned by an integer-constant expression rather than as a
|
||||
* natural successor, then the member's `expr` field points to a zero-terminated
|
||||
* string for that expression. When natural succession is used, `expr` is set to
|
||||
* `NULL`.
|
||||
*
|
||||
* If `endptr` is not `NULL`, then this routine will store the address of the
|
||||
* semicolon which terminates the enum definition, if one is present. If the
|
||||
* terminating semicolon is missing, then it is treated as an error.
|
||||
*
|
||||
* Members within the returned mapping are guaranteed to be uniquely named
|
||||
* within the enum's scope. If parsing fails for any reason, then the returned
|
||||
* mapping's `errc` value will be set to an associated error code. Additionally,
|
||||
* if `endptr` is not `NULL`, the address of the first invalid character for the
|
||||
* last-processed token will be stored in `*endptr`.
|
||||
*/
|
||||
enum_seq_t libenum_load(const char *str, size_t size, char **endptr);
|
||||
|
||||
/**
|
||||
* Load all C preprocessor `#define` directives from `str`, as if they were
|
||||
* defined with a C-style enum using assignment expressions.
|
||||
*
|
||||
* Functional macros and macros without substitution-text are skipped. Macros
|
||||
* which escape new-lines in their substitution-text are not supported and are
|
||||
* treated as if those escapes do not exist.
|
||||
*
|
||||
* If `prefix` is not `NULL`, then only substitutions which begin with `prefix`
|
||||
* will be loaded into the resulting member-mapping.
|
||||
*
|
||||
* If `endptr` is not `NULL`, then this routine will store the address of the
|
||||
* last-processed character on return.
|
||||
*
|
||||
* Members within the returned mapping are guaranteed to be uniquely named
|
||||
* across all other mapped `#define` directives.
|
||||
*/
|
||||
enum_seq_t libenum_loadcpp(const char *str, size_t size, const char *prefix,
|
||||
char **endptr);
|
||||
|
||||
/**
|
||||
* Find a C-style enum definition from `str` with a given `name` and load it
|
||||
* into a member-mapping, as in `libenum_load`. If no such instance is found,
|
||||
* then the returned mapping will be all zeroes.
|
||||
*/
|
||||
enum_seq_t libenum_find(const char *str, size_t size, const char *name,
|
||||
char **endptr);
|
||||
|
||||
/**
|
||||
* Get a descriptive string for the provided error code. The returned string
|
||||
* is statically-allocated and does not need to be `free`d.
|
||||
*/
|
||||
const char* libenum_errs(unsigned errc);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // LIBENUM_H
|
||||
361
tools/enumproc/libexpr.c
Normal file
361
tools/enumproc/libexpr.c
Normal file
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* #include <libexpr.h> - A library for evaluating integer-constant expressions
|
||||
* Copyright (C) 2026 <rachel@lhea.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "libexpr.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static inline bool is_space(char c) {
|
||||
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
|
||||
}
|
||||
|
||||
static inline bool is_alpha(char c) {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
|
||||
}
|
||||
|
||||
static inline bool is_digit(char c) {
|
||||
return (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
static inline bool is_word(char c) {
|
||||
return is_alpha(c) || is_digit(c) || c == '_';
|
||||
}
|
||||
|
||||
static char* skip(const char *str) {
|
||||
assert(str);
|
||||
|
||||
for (;;) {
|
||||
while (is_space(*str)) str++;
|
||||
|
||||
if (str[0] == '/' && str[1] == '/') {
|
||||
str += 2;
|
||||
while (str[0] && str[0] != '\r' && str[0] != '\n') str++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str[0] == '/' && str[1] == '*') {
|
||||
const char *start = str;
|
||||
|
||||
str += 2;
|
||||
while (str[0] && str[1] && !(str[0] == '*' && str[1] == '/')) str++;
|
||||
if (!str[0] || !str[1]) { str = start; break; } // unterminated
|
||||
str += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return (char *)str;
|
||||
}
|
||||
|
||||
typedef struct evalstack evalstack_t;
|
||||
struct evalstack {
|
||||
long nums[16];
|
||||
size_t n_nums;
|
||||
|
||||
int opts[16];
|
||||
size_t n_opts;
|
||||
|
||||
int unas[16];
|
||||
size_t n_unas;
|
||||
};
|
||||
|
||||
#define precedes(o1, o2) opt_data[o2].prec < opt_data[o1].prec
|
||||
#define congruent(o1, o2) opt_data[o2].prec == opt_data[o1].prec
|
||||
#define is_left(opt) opt_data[opt].left
|
||||
|
||||
#define top(stk, var) stk.var[stk.n_##var - 1]
|
||||
#define pop(stk, var) stk.var[--stk.n_##var]
|
||||
#define push(stk, var, val) stk.var[stk.n_##var++] = val
|
||||
|
||||
#define evalbinary(o) \
|
||||
do { \
|
||||
assert(stk.n_nums >= 2); \
|
||||
stk.n_nums -= 2; \
|
||||
\
|
||||
long *p_args = &stk.nums[stk.n_nums]; \
|
||||
long result = 0; \
|
||||
switch (o) { \
|
||||
case OP_MUL: result = p_args[0] * p_args[1]; break; \
|
||||
case OP_DIV: result = p_args[0] / p_args[1]; break; \
|
||||
case OP_MOD: result = p_args[0] % p_args[1]; break; \
|
||||
case OP_ADD: result = p_args[0] + p_args[1]; break; \
|
||||
case OP_SUB: result = p_args[0] - p_args[1]; break; \
|
||||
case OP_LSH: result = p_args[0] << p_args[1]; break; \
|
||||
case OP_RSH: result = p_args[0] >> p_args[1]; break; \
|
||||
case OP_LST: result = p_args[0] < p_args[1]; break; \
|
||||
case OP_LTE: result = p_args[0] <= p_args[1]; break; \
|
||||
case OP_GRT: result = p_args[0] > p_args[1]; break; \
|
||||
case OP_GTE: result = p_args[0] >= p_args[1]; break; \
|
||||
case OP_DEQ: result = p_args[0] == p_args[1]; break; \
|
||||
case OP_NEQ: result = p_args[0] != p_args[1]; break; \
|
||||
case OP_BAN: result = p_args[0] & p_args[1]; break; \
|
||||
case OP_XOR: result = p_args[0] ^ p_args[1]; break; \
|
||||
case OP_BOR: result = p_args[0] | p_args[1]; break; \
|
||||
case OP_AND: result = p_args[0] && p_args[1]; break; \
|
||||
case OP_ORR: result = p_args[0] || p_args[1]; break; \
|
||||
\
|
||||
default: assert(0 && "illegal op-type"); \
|
||||
} \
|
||||
\
|
||||
push(stk, nums, result); \
|
||||
} while (0)
|
||||
|
||||
#define evalunary(o) \
|
||||
do { \
|
||||
assert(stk.n_nums >= 1); \
|
||||
\
|
||||
long res = pop(stk, nums); \
|
||||
switch (o) { \
|
||||
case OP_POS: res = res < 0 ? -res : res; break; \
|
||||
case OP_NEG: res = res > 0 ? -res : res; break; \
|
||||
case OP_NOT: res = !res; break; \
|
||||
case OP_BNT: res = ~res; break; \
|
||||
\
|
||||
default: assert(0 && "illegal op-type"); \
|
||||
} \
|
||||
\
|
||||
push(stk, nums, res); \
|
||||
} while (0)
|
||||
|
||||
// ref: https://en.wikipedia.org/wiki/Shunting_yard_algorithm
|
||||
long libexpr_eval(const char *expr, char **endptr, scope_t *scope) {
|
||||
enum op {
|
||||
OP_LPA,
|
||||
OP_RPA,
|
||||
OP_POS,
|
||||
OP_NEG,
|
||||
OP_NOT,
|
||||
OP_BNT,
|
||||
OP_MUL,
|
||||
OP_DIV,
|
||||
OP_MOD,
|
||||
OP_ADD,
|
||||
OP_SUB,
|
||||
OP_LSH,
|
||||
OP_RSH,
|
||||
OP_LST,
|
||||
OP_LTE,
|
||||
OP_GRT,
|
||||
OP_GTE,
|
||||
OP_DEQ,
|
||||
OP_NEQ,
|
||||
OP_BAN,
|
||||
OP_XOR,
|
||||
OP_BOR,
|
||||
OP_AND,
|
||||
OP_ORR,
|
||||
};
|
||||
|
||||
// ref: https://en.cppreference.com/w/c/language/operator_precedence.html
|
||||
static const struct { int prec; bool left; } opt_data[] = {
|
||||
[OP_LPA] = { .prec = 1, .left = true },
|
||||
[OP_RPA] = { .prec = 1, .left = true },
|
||||
[OP_POS] = { .prec = 2, .left = false },
|
||||
[OP_NEG] = { .prec = 2, .left = false },
|
||||
[OP_NOT] = { .prec = 2, .left = false },
|
||||
[OP_BNT] = { .prec = 2, .left = false },
|
||||
[OP_MUL] = { .prec = 3, .left = true },
|
||||
[OP_DIV] = { .prec = 3, .left = true },
|
||||
[OP_MOD] = { .prec = 3, .left = true },
|
||||
[OP_ADD] = { .prec = 4, .left = true },
|
||||
[OP_SUB] = { .prec = 4, .left = true },
|
||||
[OP_LSH] = { .prec = 5, .left = true },
|
||||
[OP_RSH] = { .prec = 5, .left = true },
|
||||
[OP_LST] = { .prec = 6, .left = true },
|
||||
[OP_LTE] = { .prec = 6, .left = true },
|
||||
[OP_GRT] = { .prec = 6, .left = true },
|
||||
[OP_GTE] = { .prec = 6, .left = true },
|
||||
[OP_DEQ] = { .prec = 7, .left = true },
|
||||
[OP_NEQ] = { .prec = 7, .left = true },
|
||||
[OP_BAN] = { .prec = 8, .left = true },
|
||||
[OP_XOR] = { .prec = 9, .left = true },
|
||||
[OP_BOR] = { .prec = 10, .left = true },
|
||||
[OP_AND] = { .prec = 11, .left = true },
|
||||
[OP_ORR] = { .prec = 12, .left = true },
|
||||
};
|
||||
|
||||
int o1, o2;
|
||||
|
||||
const char *p = skip(expr);
|
||||
char *e = NULL;
|
||||
bool u = true, n = true;
|
||||
long m = 1;
|
||||
evalstack_t stk = { 0 };
|
||||
|
||||
while (*p) {
|
||||
if (is_alpha(*p) || *p == '_') {
|
||||
if (!n) goto early_exit;
|
||||
const char *var_beg = p;
|
||||
do { p++; } while (is_word(*p));
|
||||
|
||||
const char *var_end = p;
|
||||
const size_t var_len = var_end - var_beg;
|
||||
|
||||
var_t *match = NULL;
|
||||
for (size_t i = 0; scope && i < scope->len && !match; i++) {
|
||||
if (strncmp(scope->vars[i].name, var_beg, var_len) == 0) {
|
||||
match = &scope->vars[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
p = var_beg;
|
||||
goto early_exit; // undefined symbol
|
||||
}
|
||||
|
||||
push(stk, nums, match->value);
|
||||
while (stk.n_unas > 0) {
|
||||
o1 = pop(stk, unas);
|
||||
evalunary(o1);
|
||||
}
|
||||
|
||||
p = skip(p);
|
||||
n = false;
|
||||
u = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (*p) {
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
if (!n) goto early_exit;
|
||||
push(stk, nums, strtol(p, &e, 0));
|
||||
while (stk.n_unas > 0) {
|
||||
o1 = pop(stk, unas);
|
||||
evalunary(o1);
|
||||
}
|
||||
|
||||
p = e;
|
||||
n = false;
|
||||
u = false;
|
||||
break;
|
||||
|
||||
#define prepunary(opt) do { push(stk, unas, opt); p++; n = true; } while (0)
|
||||
#define procbinary(opt, len) do { o1 = opt; p += len; goto handle_binary; } while (0)
|
||||
|
||||
case '+':
|
||||
if (u) prepunary(OP_POS);
|
||||
else procbinary(OP_ADD, 1);
|
||||
break;
|
||||
|
||||
case '-':
|
||||
if (u) prepunary(OP_NEG);
|
||||
else procbinary(OP_SUB, 1);
|
||||
break;
|
||||
|
||||
case '!':
|
||||
if (u) prepunary(OP_NOT);
|
||||
else {
|
||||
assert(p[1] == '=' && "invalid operator (expected !=)");
|
||||
procbinary(OP_NEQ, 2);
|
||||
}
|
||||
break;
|
||||
|
||||
case '=':
|
||||
assert(p[1] == '=' && "invalid operator (expected ==)");
|
||||
procbinary(OP_DEQ, 2);
|
||||
break;
|
||||
|
||||
case '*': procbinary(OP_MUL, 1);
|
||||
case '/': procbinary(OP_DIV, 1);
|
||||
case '%': procbinary(OP_MOD, 1);
|
||||
case '^': procbinary(OP_XOR, 1);
|
||||
case '~': prepunary(OP_BNT); break;
|
||||
|
||||
case '<':
|
||||
if (p[1] == '<') procbinary(OP_LSH, 2);
|
||||
if (p[1] == '=') procbinary(OP_LTE, 2);
|
||||
procbinary(OP_LST, 1);
|
||||
|
||||
case '>':
|
||||
if (p[1] == '>') procbinary(OP_RSH, 2);
|
||||
if (p[1] == '=') procbinary(OP_GTE, 2);
|
||||
procbinary(OP_GRT, 1);
|
||||
|
||||
case '&':
|
||||
if (p[1] == '&') procbinary(OP_AND, 2);
|
||||
procbinary(OP_BAN, 1);
|
||||
|
||||
case '|':
|
||||
if (p[1] == '|') procbinary(OP_ORR, 2);
|
||||
procbinary(OP_BOR, 1);
|
||||
|
||||
handle_binary:
|
||||
while (stk.n_opts > 0
|
||||
&& (o2 = top(stk, opts)) != OP_LPA
|
||||
&& (precedes(o1, o2) || (congruent(o1, o2) && is_left(o1)))) {
|
||||
o2 = pop(stk, opts);
|
||||
evalbinary(o2);
|
||||
}
|
||||
|
||||
u = true;
|
||||
n = true;
|
||||
push(stk, opts, o1);
|
||||
break;
|
||||
|
||||
#undef procbinary
|
||||
#undef prepunary
|
||||
|
||||
case '(': push(stk, opts, OP_LPA); p++; u = true; n = true; break;
|
||||
case ')':
|
||||
o2 = -1;
|
||||
while (stk.n_opts > 0 && (o2 = pop(stk, opts)) != OP_LPA) {
|
||||
evalbinary(o2);
|
||||
}
|
||||
|
||||
if (o2 != OP_LPA) goto early_exit;
|
||||
long v = pop(stk, nums) * m;
|
||||
push(stk, nums, v);
|
||||
|
||||
while (stk.n_unas > 0) {
|
||||
o1 = pop(stk, unas);
|
||||
evalunary(o1);
|
||||
}
|
||||
|
||||
n = false;
|
||||
u = false;
|
||||
p++;
|
||||
break;
|
||||
|
||||
default: goto early_exit;
|
||||
}
|
||||
|
||||
p = skip(p);
|
||||
}
|
||||
|
||||
early_exit:
|
||||
while (stk.n_opts > 0) {
|
||||
o2 = pop(stk, opts);
|
||||
assert(o2 != OP_LPA && "unclosed parentheses");
|
||||
evalbinary(o2);
|
||||
}
|
||||
|
||||
while (stk.n_unas > 0) {
|
||||
o1 = pop(stk, unas);
|
||||
evalunary(o1);
|
||||
}
|
||||
|
||||
if (endptr) *endptr = (char *)p;
|
||||
return stk.nums[0];
|
||||
}
|
||||
56
tools/enumproc/libexpr.h
Normal file
56
tools/enumproc/libexpr.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* #include <libexpr.h> - A library for evaluating integer-constant expressions
|
||||
* Copyright (C) 2026 <rachel@lhea.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef LIBEXPR_H
|
||||
#define LIBEXPR_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct var var_t;
|
||||
struct var {
|
||||
long value;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
typedef struct scope scope_t;
|
||||
struct scope {
|
||||
var_t *vars;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
/**
|
||||
* Evaluate a integer-constant expression from a zero-terminated string `expr`.
|
||||
*
|
||||
* Variable-identifiers will be replaced with their corresponding value in
|
||||
* `scope`, if found. Otherwise, the result is an error.
|
||||
*
|
||||
* On return, if `endptr` is not `NULL`, the address of the last-processed
|
||||
* character will be stored in `*endptr`. That is, the expression is valid if
|
||||
* `*endptr` points to the zero-terminator for `expr`.
|
||||
*/
|
||||
long libexpr_eval(const char *expr, char **endptr, scope_t *scope);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // LIBEXPR_H
|
||||
39
tools/enumproc/meson.build
Normal file
39
tools/enumproc/meson.build
Normal file
@@ -0,0 +1,39 @@
|
||||
libenum_dep = declare_dependency(
|
||||
include_directories: include_directories('.'),
|
||||
link_with: static_library(
|
||||
'libenum',
|
||||
sources: files('libenum.c'),
|
||||
c_args: [
|
||||
'-std=gnu17',
|
||||
'-O3',
|
||||
'-Wall',
|
||||
'-Wextra',
|
||||
'-Wpedantic',
|
||||
'-Wconversion',
|
||||
'-Wno-sign-conversion',
|
||||
'-Werror',
|
||||
],
|
||||
include_directories: include_directories('.'),
|
||||
native: true,
|
||||
),
|
||||
)
|
||||
|
||||
libexpr_dep = declare_dependency(
|
||||
include_directories: include_directories('.'),
|
||||
link_with: static_library(
|
||||
'libexpr',
|
||||
sources: files('libexpr.c'),
|
||||
c_args: [
|
||||
'-std=gnu17',
|
||||
'-O3',
|
||||
'-Wall',
|
||||
'-Wextra',
|
||||
'-Wpedantic',
|
||||
'-Wconversion',
|
||||
'-Wno-sign-conversion',
|
||||
'-Werror',
|
||||
],
|
||||
include_directories: include_directories('.'),
|
||||
native: true,
|
||||
),
|
||||
)
|
||||
@@ -1,8 +1,11 @@
|
||||
rapidjson_dep = dependency('rapidjson')
|
||||
|
||||
# Native tools
|
||||
subdir('nitroarc') # Contains a library component that other tools depend on
|
||||
subdir('dataproc')
|
||||
# Native tools with helper libraries
|
||||
subdir('nitroarc') # libnitroarc
|
||||
subdir('enumproc') # libenum
|
||||
subdir('dataproc') # Requires libnitroarc and libenum
|
||||
|
||||
# Other native tools
|
||||
subdir('csv2bin')
|
||||
subdir('datagen')
|
||||
subdir('fixrom')
|
||||
|
||||
Reference in New Issue
Block a user