diff options
author | 2025-03-10 02:37:19 +0000 | |
---|---|---|
committer | 2025-04-06 16:41:13 +0100 | |
commit | 244fea664121acf12871ab5858a5fe95a2606b52 (patch) | |
tree | e42b1990ef97adc0f0ab48b9be7e11de7fee0558 /src/build | |
parent | d86b7b41453c69b3854baa7cdc05a79a3cdfe092 (diff) | |
download | sst-244fea664121acf12871ab5858a5fe95a2606b52.tar.gz sst-244fea664121acf12871ab5858a5fe95a2606b52.zip |
Rewrite and redesign codegen and feature system
Also switch to somewhat proper C23 flags while we're at it.
This is a huge change. It took me forever, in between being really busy.
Sorry about that. But the good news is I'm now free to start integrating
the various patches that have accumulated since last release. Well, at
least in between still being really busy. Gotta manage expectations.
The main benefit of introducing GAMESPECIFIC() is that features
that don't apply to a particular game no longer show up *at all*, and
less time is wasted on init. It also enables a cool optimisation wherein
unnecessary REQUIRE_GAMEDATA() checks can elided at compile time
whenever the gamedata is known up-front to always exist in supported
games.
The DEF_FEAT_CVAR macro family meanwhile makes it easier to manage the
lifecycle of cvars/ccmds, with less manual registering, unhiding and
such.
Originally I was going to try and just hack these features into the
existing codegen abomination, but it just got too terrible. This rewrite
should make it easier to continue tweaking codegen behaviour in future.
It also has slightly better error messages.
Diffstat (limited to 'src/build')
-rw-r--r-- | src/build/cmeta.c | 454 | ||||
-rw-r--r-- | src/build/cmeta.h | 115 | ||||
-rw-r--r-- | src/build/codegen.c | 537 | ||||
-rw-r--r-- | src/build/gluegen.c | 963 | ||||
-rw-r--r-- | src/build/mkentprops.c | 12 | ||||
-rw-r--r-- | src/build/mkgamedata.c | 45 | ||||
-rw-r--r-- | src/build/skiplist.h | 206 | ||||
-rw-r--r-- | src/build/vec.h | 95 |
8 files changed, 1220 insertions, 1207 deletions
diff --git a/src/build/cmeta.c b/src/build/cmeta.c index 8a2416d..1903e84 100644 --- a/src/build/cmeta.c +++ b/src/build/cmeta.c @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -15,22 +15,12 @@ */ #include <stdio.h> -#include <string.h> +#include <stdlib.h> #include "../intdefs.h" +#include "../langext.h" #include "../os.h" #include "cmeta.h" -#include "vec.h" - -/* - * This file does C metadata parsing/scraping for the build system. This - * facilitates tasks ranging from determining header dependencies to searching - * for certain magic macros (for example cvar/command declarations) to generate - * other code. - * - * It's a bit of a mess since it's kind of just hacked together for use at build - * time. Don't worry about it too much. - */ // lazy inlined 3rd party stuff {{{ // too lazy to write a C tokenizer at the moment, or indeed probably ever, so @@ -56,350 +46,218 @@ Type *ty_double = &(Type){TY_DOUBLE, 8, 8}; Type *ty_ldouble = &(Type){TY_LDOUBLE, 16, 16}; // inline just a couple more things, super lazy, but whatever static Type *new_type(TypeKind kind, int size, int align) { - Type *ty = calloc(1, sizeof(Type)); - ty->kind = kind; - ty->size = size; - ty->align = align; - return ty; + Type *ty = calloc(1, sizeof(Type)); + ty->kind = kind; + ty->size = size; + ty->align = align; + return ty; } Type *array_of(Type *base, int len) { - Type *ty = new_type(TY_ARRAY, base->size * len, base->align); - ty->base = base; - ty->array_len = len; - return ty; + Type *ty = new_type(TY_ARRAY, base->size * len, base->align); + ty->base = base; + ty->array_len = len; + return ty; } #include "../3p/chibicc/hashmap.c" #include "../3p/chibicc/strings.c" #include "../3p/chibicc/tokenize.c" -// one more copypaste from preprocess.c for #include <filename> and then I'm -// done I promise -static char *join_tokens(const Token *tok, const Token *end) { - int len = 1; - for (const Token *t = tok; t != end && t->kind != TK_EOF; t = t->next) { - if (t != tok && t->has_space) - len++; - len += t->len; - } - char *buf = calloc(1, len); - int pos = 0; - for (const Token *t = tok; t != end && t->kind != TK_EOF; t = t->next) { - if (t != tok && t->has_space) - buf[pos++] = ' '; - strncpy(buf + pos, t->loc, t->len); - pos += t->len; - } - buf[pos] = '\0'; - return buf; -} // }}} #ifdef _WIN32 #include "../3p/openbsd/asprintf.c" // missing from libc; plonked here for now #endif -static void die(const char *s) { +static noreturn die(int status, const char *s) { fprintf(stderr, "cmeta: fatal: %s\n", s); - exit(100); + exit(status); } -static char *readsource(const os_char *path) { +struct cmeta cmeta_loadfile(const os_char *path) { int f = os_open_read(path); - if (f == -1) return 0; - uint bufsz = 8192; - char *buf = malloc(bufsz); - if (!buf) die("couldn't allocate memory"); - int nread; - int off = 0; - while ((nread = os_read(f, buf + off, bufsz - off)) > 0) { - off += nread; - if (off == bufsz) { - bufsz *= 2; - // somewhat arbitrary cutoff - if (bufsz == 1 << 30) die("input file is too large"); - buf = realloc(buf, bufsz); - if (!buf) die("couldn't reallocate memory"); - } - } - if (nread == -1) die("couldn't read file"); - buf[off] = 0; + if (f == -1) die(100, "couldn't open file"); + vlong len = os_fsize(f); + if (len > 1u << 30 - 1) die(2, "input file is far too large"); + struct cmeta ret; + ret.sbase = malloc(len + 1); + ret.sbase[len] = '\0'; // chibicc needs a null terminator + if (!ret.sbase) die(100, "couldn't allocate memory"); + if (os_read(f, ret.sbase, len) != len) die(100, "couldn't read file"); + int maxitems = len / 4; // shortest word is "END" + ret.nitems = 0; + // eventual overall memory requirement: file size * 6. seems fine to me. + // current memory requirement: file size * 10, + all the chibicc linked list + // crap. not as good but we'll continue tolerating it... probably for years! + //ret.itemoffs = malloc(maxitems * sizeof(*ret.itemoffs)); + //if (!ret.itemoffs) die(100, "couldn't allocate memory"); + ret.itemtoks = malloc(maxitems * sizeof(*ret.itemtoks)); + if (!ret.itemtoks) die(100, "couldn't allocate memory"); + ret.itemtypes = malloc(maxitems * sizeof(*ret.itemtypes)); + if (!ret.itemtypes) die(100, "couldn't allocate memory"); os_close(f); - return buf; -} - -// as per cmeta.h this is totally opaque; it's actually just a Token in disguise -struct cmeta; - -const struct cmeta *cmeta_loadfile(const os_char *path) { - char *buf = readsource(path); - if (!buf) return 0; #ifdef _WIN32 char *realname = malloc(wcslen(path) + 1); - if (!realname) die("couldn't allocate memory"); + if (!realname) die(100, "couldn't allocate memory"); // XXX: being lazy about Unicode right now; a general purpose tool should // implement WTF8 or something. SST itself doesn't have any unicode paths - // though, so don't really care as much. + // though, so we don't really care as much. this code still sucks though. *realname = *path; for (const ushort *p = path + 1; p[-1]; ++p) realname[p - path] = *p; #else const char *realname = f; #endif - return (const struct cmeta *)tokenize_buf(realname, buf); -} - -// NOTE: we don't care about conditional includes, nor do we expand macros. We -// just parse the minimum info to get what we need for SST. Also, there's not -// too much in the way of syntax checking; if an error gets ignored the compiler -// picks it up anyway, and gives far better diagnostics. -void cmeta_includes(const struct cmeta *cm, - void (*cb)(const char *f, bool issys, void *ctxt), void *ctxt) { - const Token *tp = (const Token *)cm; - if (!tp || !tp->next || !tp->next->next) return; // #, include, "string" - while (tp) { - if (!tp->at_bol || !equal(tp, "#")) { tp = tp->next; continue; } - if (!equal(tp->next, "include")) { tp = tp->next->next; continue; } - tp = tp->next->next; - if (!tp) break; - if (tp->at_bol) tp = tp->next; - if (!tp) break; - if (tp->kind == TK_STR) { - // include strings are a special case; they don't have \escapes. - char *copy = malloc(tp->len - 1); - if (!copy) die("couldn't allocate memory"); - memcpy(copy, tp->loc + 1, tp->len - 2); - copy[tp->len - 2] = '\0'; - cb(copy, false, ctxt); - //free(copy); // ?????? + struct Token *t = tokenize_buf(realname, ret.sbase); + // everything is THING() or THING {} so we need at least 3 tokens ahead - if + // we have fewer tokens left in the file we can bail + if (t && t->next) while (t->next->next) { + if (!t->at_bol) { + t = t->next; + continue; } - else if (equal(tp, "<")) { - tp = tp->next; - if (!tp) break; - const Token *end = tp; - while (!equal(end, ">")) { - end = end->next; - if (!end) return; // shouldn't happen in valid source obviously - if (end->at_bol) break; // ?????? - } - char *joined = join_tokens(tp, end); // just use func from chibicc - cb(joined, true, ctxt); - //free(joined); // ?????? + int type; + if ((equal(t, "DEF_CVAR") || equal(t, "DEF_CVAR_MIN") || + equal(t, "DEF_CVAR_MAX") || equal(t, "DEF_CVAR_MINMAX") || + equal(t, "DEF_CVAR_UNREG") || equal(t, "DEF_CVAR_MIN_UNREG") || + equal(t, "DEF_CVAR_MAX_UNREG") || + equal(t, "DEF_CVAR_MINMAX_UNREG") || + equal(t, "DEF_FEAT_CVAR") || equal(t, "DEF_FEAT_CVAR_MIN") || + equal(t, "DEF_FEAT_CVAR_MAX") || + equal(t, "DEF_FEAT_CVAR_MINMAX")) && equal(t->next, "(")) { + type = CMETA_ITEM_DEF_CVAR; } - // get to the next line (standard allows extra tokens because) - while (!tp->at_bol) { - tp = tp->next; - if (!tp) return; + else if ((equal(t, "DEF_CCMD") || equal(t, "DEF_CCMD_HERE") || + equal(t, "DEF_CCMD_UNREG") || equal(t, "DEF_CCMD_HERE_UNREG") || + equal(t, "DEF_CCMD_PLUSMINUS") || + equal(t, "DEF_CCMD_PLUSMINUS_UNREG") || + equal(t, "DEF_FEAT_CCMD") || equal(t, "DEF_FEAT_CCMD_HERE") || + equal(t, "DEF_FEAT_CCMD_PLUSMINUS")) && equal(t->next, "(")) { + type = CMETA_ITEM_DEF_CCMD; } - } -} - -// AGAIN, NOTE: this doesn't *perfectly* match top level decls only in the event -// that someone writes something weird, but we just don't really care because -// we're not writing something weird. Don't write something weird! -void cmeta_conmacros(const struct cmeta *cm, - void (*cb)(const char *, bool, bool)) { - const Token *tp = (const Token *)cm; - if (!tp || !tp->next || !tp->next->next) return; // DEF_xyz, (, name - while (tp) { - bool isplusminus = false, isvar = false; - bool unreg = false; - // this is like the worst thing ever, but oh well it's just build time - // XXX: tidy this up some day, though, probably - if (equal(tp, "DEF_CCMD_PLUSMINUS")) { - isplusminus = true; + else if ((equal(t, "DEF_EVENT") || equal(t, "DEF_PREDICATE")) && + equal(t->next, "(")) { + type = CMETA_ITEM_DEF_EVENT; + } + else if (equal(t, "HANDLE_EVENT") && equal(t->next, "(")) { + type = CMETA_ITEM_HANDLE_EVENT; } - else if (equal(tp, "DEF_CCMD_PLUSMINUS_UNREG")) { - isplusminus = true; - unreg = true; + else if (equal(t, "FEATURE") && equal(t->next, "(")) { + type = CMETA_ITEM_FEATURE; } - else if (equal(tp, "DEF_CVAR") || equal(tp, "DEF_CVAR_MIN") || - equal(tp, "DEF_CVAR_MAX") || equal(tp, "DEF_CVAR_MINMAX")) { - isvar = true; + else if ((equal(t, "REQUIRE") || equal(t, "REQUIRE_GAMEDATA") || + equal(t, "REQUIRE_GLOBAL") || equal(t, "REQUEST")) && + equal(t->next, "(")) { + type = CMETA_ITEM_REQUIRE; } - else if (equal(tp, "DEF_CVAR_UNREG") || - equal(tp, "DEF_CVAR_MIN_UNREG") || - equal(tp, "DEF_CVAR_MAX_UNREG") || - equal(tp, "DEF_CVAR_MINMAX_UNREG")) { - isvar = true; - unreg = true; + else if (equal(t, "GAMESPECIFIC") && equal(t->next, "(")) { + type = CMETA_ITEM_GAMESPECIFIC; } - else if (equal(tp, "DEF_CCMD_UNREG") || - equal(tp, "DEF_CCMD_HERE_UNREG")) { - unreg = true; + else if (equal(t, "PREINIT") && equal(t->next, "{")) { + type = CMETA_ITEM_PREINIT; } - else if (!equal(tp, "DEF_CCMD") && !equal(tp, "DEF_CCMD_HERE")) { - tp = tp->next; continue; + else if (equal(t, "INIT") && equal(t->next, "{")) { + type = CMETA_ITEM_INIT; } - if (!equal(tp->next, "(")) { tp = tp->next->next; continue; } - tp = tp->next->next; - if (isplusminus) { - // XXX: this is stupid but whatever - char *plusname = malloc(sizeof("PLUS_") + tp->len); - if (!plusname) die("couldn't allocate memory"); - memcpy(plusname, "PLUS_", 5); - memcpy(plusname + sizeof("PLUS_") - 1, tp->loc, tp->len); - plusname[sizeof("PLUS_") - 1 + tp->len] = '\0'; - cb(plusname, false, unreg); - char *minusname = malloc(sizeof("MINUS_") + tp->len); - if (!minusname) die("couldn't allocate memory"); - memcpy(minusname, "MINUS_", 5); - memcpy(minusname + sizeof("MINUS_") - 1, tp->loc, tp->len); - minusname[sizeof("MINUS_") - 1 + tp->len] = '\0'; - cb(minusname, false, unreg); + else if (equal(t, "END") && equal(t->next, "{")) { + type = CMETA_ITEM_END; } else { - char *name = malloc(tp->len + 1); - if (!name) die("couldn't allocate memory"); - memcpy(name, tp->loc, tp->len); - name[tp->len] = '\0'; - cb(name, isvar, unreg); + t = t->next; + continue; } - tp = tp->next; + ret.itemtoks[ret.nitems] = t; + ret.itemtypes[ret.nitems] = type; + ++ret.nitems; + // this is kind of inefficient; in most cases we can skip more stuff, + // but then also, we're always scanning for something specific, so who + // cares actually, this will do for now. + t = t->next->next; } + return ret; } -const char *cmeta_findfeatmacro(const struct cmeta *cm) { - const Token *tp = (const Token *)cm; - if (!tp || !tp->next) return 0; // FEATURE, ( - while (tp) { - if (equal(tp, "FEATURE") && equal(tp->next, "(")) { - if (equal(tp->next->next, ")")) return ""; // no arg = no desc - if (!tp->next->next || tp->next->next->kind != TK_STR) { - return 0; // it's invalid, whatever, just return... - } - return tp->next->next->str; - } - tp = tp->next; +int cmeta_flags_cvar(const struct cmeta *cm, u32 i) { + struct Token *t = cm->itemtoks[i]; + switch_exhaust (t->len) { + // It JUST so happens all of the possible tokens here have a unique + // length. I swear this wasn't planned. But it IS convenient! + case 8: case 12: case 15: return 0; + case 14: case 18: case 21: return CMETA_CVAR_UNREG; + case 13: case 17: case 20: return CMETA_CVAR_FEAT; } - return 0; } -void cmeta_featinfomacros(const struct cmeta *cm, void (*cb)( - enum cmeta_featmacro type, const char *param, void *ctxt), void *ctxt) { - const Token *tp = (const Token *)cm; - if (!tp || !tp->next) return; - while (tp) { - int type = -1; - if (equal(tp, "PREINIT")) { - type = CMETA_FEAT_PREINIT; - } - else if (equal(tp, "INIT")) { - type = CMETA_FEAT_INIT; - } - else if (equal(tp, "END")) { - type = CMETA_FEAT_END; - } - if (type != - 1) { - if (equal(tp->next, "{")) { - cb(type, 0, ctxt); - tp = tp->next; - } - tp = tp->next; - continue; - } - if (equal(tp, "REQUIRE")) { - type = CMETA_FEAT_REQUIRE; - } - else if (equal(tp, "REQUIRE_GAMEDATA")) { - type = CMETA_FEAT_REQUIREGD; - } - else if (equal(tp, "REQUIRE_GLOBAL")) { - type = CMETA_FEAT_REQUIREGLOBAL; - } - else if (equal(tp, "REQUEST")) { - type = CMETA_FEAT_REQUEST; - } - if (type != -1) { - if (equal(tp->next, "(") && tp->next->next) { - tp = tp->next->next; - char *param = malloc(tp->len + 1); - if (!param) die("couldn't allocate memory"); - memcpy(param, tp->loc, tp->len); - param[tp->len] = '\0'; - cb(type, param, ctxt); - tp = tp->next; - } - } - tp = tp->next; +int cmeta_flags_ccmd(const struct cmeta *cm, u32 i) { + struct Token *t = cm->itemtoks[i]; + switch_exhaust (t->len) { + case 13: if (t->loc[4] == 'F') return CMETA_CCMD_FEAT; + case 8: return 0; + case 18: if (t->loc[4] == 'F') return CMETA_CCMD_FEAT; + return CMETA_CCMD_PLUSMINUS; + case 14: case 19: return CMETA_CCMD_UNREG; + case 23: return CMETA_CCMD_FEAT | CMETA_CCMD_PLUSMINUS; + case 24: return CMETA_CCMD_UNREG | CMETA_CCMD_PLUSMINUS; } } -struct vec_str VEC(const char *); +int cmeta_flags_event(const struct cmeta *cm, u32 i) { + // assuming CMETA_EVENT_ISPREDICATE remains 1, the ternary should + // optimise out + return cm->itemtoks[i]->len == 13 ? CMETA_EVENT_ISPREDICATE : 0; +} -static void pushmacroarg(const Token *last, const char *start, - struct vec_str *list) { - int len = last->loc - start + last->len; - char *dup = malloc(len + 1); - if (!dup) die("couldn't allocate memory"); - memcpy(dup, start, len); - dup[len] = '\0'; - if (!vec_push(list, dup)) die("couldn't append to array"); +int cmeta_flags_require(const struct cmeta *cm, u32 i) { + struct Token *t = cm->itemtoks[i]; + // NOTE: this is somewhat more flexible to enable REQUEST_GAMEDATA or + // something in future, although that's kind of useless currently + int optflag = t->loc[4] == 'E'; // REQU[E]ST + switch_exhaust (t->len) { + case 7: return optflag; + case 16: return optflag | CMETA_REQUIRE_GAMEDATA; + case 14: return optflag | CMETA_REQUIRE_GLOBAL; + }; } -// XXX: maybe this should be used for the other functions too. it'd be less ugly -// and handle closing parentheses better, but alloc for tokens we don't care -// about. probably a worthy tradeoff? -static const Token *macroargs(const Token *t, struct vec_str *list) { - int paren = 1; - const Token *last; // avoids copying extra ws/comments in - for (const char *start = t->loc; t; last = t, t = t->next) { - if (equal(t, "(")) { - ++paren; - } - else if (equal(t, ")")) { - if (!--paren) { - pushmacroarg(last, start, list); - return t->next; - } - } - else if (paren == 1 && equal(t, ",")) { - pushmacroarg(last, start, list); - t = t->next; - if (t) start = t->loc; // slightly annoying... - } +int cmeta_nparams(const struct cmeta *cm, u32 i) { + int argc = 1, nest = 0; + struct Token *t = cm->itemtoks[i]->next->next; + if (equal(t, ")")) return 0; // XXX: stupid special case, surely improvable? + for (; t; t = t->next) { + if (equal(t, "(")) { ++nest; continue; } + if (!nest && equal(t, ",")) ++argc; + else if (equal(t, ")") && !nest--) break; } - // I guess we handle this here. - fprintf(stderr, "cmeta: fatal: unexpected EOF in %s\n", t->filename); - exit(2); + if (nest != -1) return 0; // XXX: any need to do anything better here? + return argc; +} + +struct cmeta_param_iter cmeta_param_iter_init(const struct cmeta *cm, u32 i) { + return (struct cmeta_param_iter){cm->itemtoks[i]->next->next}; } -void cmeta_evdefmacros(const struct cmeta *cm, void (*cb)(const char *name, - const char *const *params, int nparams, bool predicate)) { - const Token *tp = (const Token *)cm; - if (!tp || !tp->next || !tp->next->next) return; // DEF_EVENT, (, name - while (tp) { - bool predicate = true; - if (equal(tp, "DEF_EVENT") && equal(tp->next, "(")) { - predicate = false; +struct cmeta_slice cmeta_param_iter(struct cmeta_param_iter *it) { + int nest = 0; + const char *start = it->cur->loc; + for (struct Token *last = 0; it->cur; + last = it->cur, it->cur = it->cur->next) { + if (equal(it->cur, "(")) { ++nest; continue; } + if (!nest && equal(it->cur, ",")) { + if (!last) { // , immediately after (, for some reason. treat as "" + return (struct cmeta_slice){start, 0}; + } + it->cur = it->cur->next; } - else if (!equal(tp, "DEF_PREDICATE") || !equal(tp->next, "(")) { - tp = tp->next; - continue; + else if (equal(it->cur, ")") && !nest--) { + if (!last) break; } - tp = tp->next->next; - struct vec_str args = {0}; - tp = macroargs(tp, &args); - if (args.sz == 0) { - fprintf(stderr, "cmeta: fatal: missing event parameters in %s\n", - tp->filename); - exit(2); + else { + continue; } - cb(args.data[0], args.data + 1, args.sz - 1, predicate); + return (struct cmeta_slice){start, last->loc - start + last->len}; } + return (struct cmeta_slice){0, 0}; } -void cmeta_evhandlermacros(const struct cmeta *cm, const char *modname, - void (*cb_handler)(const char *evname, const char *modname)) { - const Token *tp = (const Token *)cm; - while (tp) { - if (equal(tp, "HANDLE_EVENT") && equal(tp->next, "(")) { - tp = tp->next->next; - char *name = malloc(tp->len + 1); - if (!name) die("couldn't allocate memory"); - memcpy(name, tp->loc, tp->len); - name[tp->len] = '\0'; - cb_handler(name, modname); - } - tp = tp->next; - } +u32 cmeta_line(const struct cmeta *cm, u32 i) { + return cm->itemtoks[i]->line_no; } // vi: sw=4 ts=4 noet tw=80 cc=80 fdm=marker diff --git a/src/build/cmeta.h b/src/build/cmeta.h index 86ae0ec..2a3f741 100644 --- a/src/build/cmeta.h +++ b/src/build/cmeta.h @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,71 +17,72 @@ #ifndef INC_CMETA_H #define INC_CMETA_H +#include "../intdefs.h" #include "../os.h" -struct cmeta; +// XXX: leaking chibicc internals. won't matter after we do away with that +typedef struct Token Token; -const struct cmeta *cmeta_loadfile(const os_char *path); +enum cmeta_item { + CMETA_ITEM_DEF_CVAR, // includes all min/max/unreg variants + CMETA_ITEM_DEF_CCMD, // includes plusminus/unreg variants + CMETA_ITEM_DEF_EVENT, // includes predicates + CMETA_ITEM_HANDLE_EVENT, + CMETA_ITEM_FEATURE, + CMETA_ITEM_REQUIRE, // includes all REQUIRE_*/REQUEST variants + CMETA_ITEM_GAMESPECIFIC, + CMETA_ITEM_PREINIT, + CMETA_ITEM_INIT, + CMETA_ITEM_END +}; -/* - * Iterates through all the #include directives in a file, passing each one in - * turn to the callback cb. - */ -void cmeta_includes(const struct cmeta *cm, - void (*cb)(const char *f, bool issys, void *ctxt), void *ctxt); +struct cmeta { + char *sbase; + u32 nitems; // number of interesting macros + //u32 *itemoffs; // file offsets of interesting macros (ONE DAY!) + Token **itemtoks; // crappy linked token structures, for the time being + u8 *itemtypes; // CMETA_ITEM_* enum values +}; -/* - * Iterates through all commands and variables declared using the macros in - * con_.h, passing each one in turn to the callback cb. - */ -void cmeta_conmacros(const struct cmeta *cm, - void (*cb)(const char *name, bool isvar, bool unreg)); +enum cmeta_flag_cvar { + CMETA_CVAR_UNREG = 1, + CMETA_CVAR_FEAT = 2, +}; +enum cmeta_flag_ccmd { + CMETA_CCMD_UNREG = 1, + CMETA_CCMD_FEAT = 2, + CMETA_CCMD_PLUSMINUS = 4 +}; +enum cmeta_flag_event { + CMETA_EVENT_ISPREDICATE = 1 +}; +enum cmeta_flag_require { + CMETA_REQUIRE_OPTIONAL = 1, // i.e. REQUEST() macro, could be extended + CMETA_REQUIRE_GAMEDATA = 2, + CMETA_REQUIRE_GLOBAL = 4 +}; -/* - * Looks for a feature description macro in file, returning the description - * string if it exists, an empty string if the feature is defined without a - * user-facing description, and null if source file does not define a feature. - */ -const char *cmeta_findfeatmacro(const struct cmeta *cm); +struct cmeta_slice { const char *s; int len; }; -/* - * the various kinds of feature specficiation macros, besides the feature - * declaration macro itself - */ -enum cmeta_featmacro { - CMETA_FEAT_REQUIRE, - CMETA_FEAT_REQUIREGD, - CMETA_FEAT_REQUIREGLOBAL, - CMETA_FEAT_REQUEST, - CMETA_FEAT_PREINIT, - CMETA_FEAT_INIT, - CMETA_FEAT_END -}; +struct cmeta cmeta_loadfile(const os_char *path); +int cmeta_flags_cvar(const struct cmeta *cm, u32 i); +int cmeta_flags_ccmd(const struct cmeta *cm, u32 i); +int cmeta_flags_event(const struct cmeta *cm, u32 i); +int cmeta_flags_require(const struct cmeta *cm, u32 i); -/* - * Iterates through all feature dependency macros and init/end/preinit - * indicators, passing each bit of information to the callback cb. - * - * PREINT, INIT and END macros don't pass anything to param. - * - * This one takes a context pointer, while the others don't, because this is all - * cobbled together without much consistent abstraction. - */ -void cmeta_featinfomacros(const struct cmeta *cm, void (*cb)( - enum cmeta_featmacro type, const char *param, void *ctxt), void *ctxt); +int cmeta_nparams(const struct cmeta *cm, u32 i); +struct cmeta_param_iter { Token *cur; }; +struct cmeta_param_iter cmeta_param_iter_init(const struct cmeta *cm, u32 i); +struct cmeta_slice cmeta_param_iter(struct cmeta_param_iter *it); + +#define cmeta_param_foreach(varname, cm, u32) \ + switch (0) for (struct cmeta_slice varname; 0;) default: \ + for (struct cmeta_param_iter _it = cmeta_param_iter_init(cm, i); \ + varname = cmeta_param_iter(&_it), varname.s;) \ + /* {...} */ + +u32 cmeta_line(const struct cmeta *cm, u32 i); -/* - * Iterates through all event-related macros and takes note of which events are - * defined, giving a callback for each. - */ -void cmeta_evdefmacros(const struct cmeta *cm, void (*cb)(const char *name, - const char *const *params, int nparams, bool predicate)); -/* - * Iterates through all event-related macros and gives a callback for each event - * that is handled by the given module. - */ -void cmeta_evhandlermacros(const struct cmeta *cm, const char *modname, - void (*cb)(const char *evname, const char *modname)); #endif // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/build/codegen.c b/src/build/codegen.c deleted file mode 100644 index 70b5e12..0000000 --- a/src/build/codegen.c +++ /dev/null @@ -1,537 +0,0 @@ -/* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - */ - -#include <ctype.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "../intdefs.h" -#include "../langext.h" -#include "../os.h" -#include "cmeta.h" -#include "skiplist.h" -#include "vec.h" - -#ifdef _WIN32 -#define fS "S" -#else -#define fS "s" -#endif - -static void die(const char *s) { - fprintf(stderr, "codegen: fatal: %s\n", s); - exit(100); -} - -#define MAXENT 65536 // arbitrary limit! -static struct conent { - const char *name; - bool unreg; - bool isvar; // false for cmd -} conents[MAXENT]; -static int nconents; - -#define PUT(name_, isvar_, unreg_) do { \ - if (nconents == countof(conents)) { \ - fprintf(stderr, "codegen: out of space; make conents bigger!\n"); \ - exit(1); \ - } \ - conents[nconents].name = name_; \ - conents[nconents].isvar = isvar_; conents[nconents++].unreg = unreg_; \ -} while (0) - -static void oncondef(const char *name, bool isvar, bool unreg) { - PUT(name, isvar, unreg); -} - -struct vec_str VEC(const char *); -struct vec_usize VEC(usize); -struct vec_featp VEC(struct feature *); - -enum { UNSEEN, SEEING, SEEN }; - -DECL_SKIPLIST(static, feature, struct feature, const char *, 4) -DECL_SKIPLIST(static, feature_bydesc, struct feature, const char *, 4) -struct feature { - const char *modname; - const char *desc; - const struct cmeta *cm; // backref for subsequent options pass - struct vec_featp needs; - // keep optionals in a separate array mainly so we have separate counts - struct vec_featp wants; - uint dfsstate : 2; // used for sorting and cycle checking - bool has_preinit : 1, /*has_init : 1, <- required anyway! */ has_end : 1; - bool has_evhandlers : 1; - bool is_requested : 1; // determines if has_ variable needs to be extern - //char pad : 2; - //char pad[3]; - struct vec_str need_gamedata; - struct vec_str need_globals; - struct skiplist_hdr_feature hdr; // by id/modname - struct skiplist_hdr_feature_bydesc hdr_bydesc; -}; -static inline int cmp_feature(struct feature *e, const char *s) { - return strcmp(e->modname, s); -} -static inline int cmp_feature_bydesc(struct feature *e, const char *s) { - for (const char *p = e->desc; ; ++p, ++s) { - // shortest string first - if (!*p) return !!*s; if (!*s) return -1; - // case insensitive sort where possible - if (tolower((uchar)*p) > tolower((uchar)*s)) return 1; - if (tolower((uchar)*p) < tolower((uchar)*s)) return -1; - // prioritise upper-case if same letter - if (isupper((uchar)*p) && islower((uchar)*s)) return 1; - if (islower((uchar)*p) && isupper((uchar)*s)) return -1; - } - return 0; -} -static inline struct skiplist_hdr_feature *hdr_feature(struct feature *e) { - return &e->hdr; -} -static inline struct skiplist_hdr_feature_bydesc *hdr_feature_bydesc( - struct feature *e) { - return &e->hdr_bydesc; -} -DEF_SKIPLIST(static, feature, cmp_feature, hdr_feature) -DEF_SKIPLIST(static, feature_bydesc, cmp_feature_bydesc, hdr_feature_bydesc) -static struct skiplist_hdr_feature features = {0}; -// sort in two different ways, so we can alphabetise the user-facing display -// NOTE: not all features will show up in this second list! -static struct skiplist_hdr_feature_bydesc features_bydesc = {0}; - -static void onfeatinfo(enum cmeta_featmacro type, const char *param, - void *ctxt) { - struct feature *f = ctxt; - switch_exhaust_enum (cmeta_featmacro, type) { - case CMETA_FEAT_REQUIRE:; bool optional = false; goto dep; - case CMETA_FEAT_REQUEST: optional = true; -dep:; struct feature *dep = skiplist_get_feature(&features, param); - if (optional) dep->is_requested = true; - if (!dep) { - fprintf(stderr, "codegen: error: feature `%s` tried to depend " - "on non-existent feature `%s`\n", f->modname, param); - exit(1); \ - } - if (!vec_push(optional ? &f->wants : &f->needs, dep)) { - die("couldn't allocate memory"); - } - break; - case CMETA_FEAT_REQUIREGD:; - struct vec_str *vecp = &f->need_gamedata; - goto push; - case CMETA_FEAT_REQUIREGLOBAL: - vecp = &f->need_globals; -push: if (!vec_push(vecp, param)) die("couldn't allocate memory"); - break; - case CMETA_FEAT_PREINIT: f->has_preinit = true; break; - case CMETA_FEAT_END: f->has_end = true; break; - case CMETA_FEAT_INIT:; // nop for now, I guess - } -} - -DECL_SKIPLIST(static, event, struct event, const char *, 4) - struct event { - usize name; // string, but tagged pointer - see below - const char *const *params; - int nparams; - //char pad[4]; - struct vec_usize handlers; // strings, but with tagged pointers - see below - struct skiplist_hdr_event hdr; -}; -static inline int cmp_event(struct event *e, const char *s) { - return strcmp((const char *)(e->name & ~1ull), s); -} -static inline struct skiplist_hdr_event *hdr_event(struct event *e) { - return &e->hdr; -} -DEF_SKIPLIST(static, event, cmp_event, hdr_event) -static struct skiplist_hdr_event events = {0}; - -static void onevdef(const char *name, const char *const *params, int nparams, - bool predicate) { - struct event *e = skiplist_get_event(&events, name); - if (!e) { - struct event *e = malloc(sizeof(*e)); - if (!e) die("couldn't allocate memory"); - // hack: using unused pointer bit to distinguish the two types of event - e->name = (usize)name | predicate; - e->params = params; e->nparams = nparams; - e->handlers = (struct vec_usize){0}; - e->hdr = (struct skiplist_hdr_event){0}; - skiplist_insert_event(&events, name, e); - } - else { - fprintf(stderr, "codegen: error: duplicate event definition `%s`\n", - name); - exit(2); - } -} - -static void onevhandler(const char *evname, const char *modname) { - struct event *e = skiplist_get_event(&events, evname); - if (!e) { - fprintf(stderr, "codegen: error: module `%s` trying to handle " - "non-existent event `%s`\n", modname, evname); - exit(2); - } - usize taggedptr = (usize)modname; - struct feature *f = skiplist_get_feature(&features, modname); - f->has_evhandlers = true; - // hack: using unused pointer bit to determine whether a handler is tied to - // a feature and thus conditional. relies on malloc alignment! - if (f) taggedptr |= 1ull; - // NOTE: not bothering to check for more than one handler in a file. - // compiler will get that anyway. - if (!vec_push(&e->handlers, taggedptr)) die("couldn't allocate memory"); -} - -struct passinfo { - const struct cmeta *cm; - const os_char *path; -}; -static struct vec_passinfo VEC(struct passinfo) pass2 = {0}; - -#define _(x) \ - if (fprintf(out, "%s\n", x) < 0) die("couldn't write to file"); -#define F(f, ...) \ - if (fprintf(out, f "\n", __VA_ARGS__) < 0) die("couldn't write to file"); -#define H_() \ - _( "/* This file is autogenerated by "__FILE__". DO NOT EDIT! */") -#define H() H_() _( "") - -static struct vec_featp endstack = {0}; // stack for reversing order - -static void featdfs(FILE *out, struct feature *f) { - if (f->dfsstate == SEEN) return; - if (f->dfsstate == SEEING) { - // XXX: could unwind for full cycle listing like in build. - // purely being lazy by not doing that here, and assuming there won't - // actually be cycles anyway, because this is not a general purpose tool - // and people working on this codebase are very smart. - fprintf(stderr, "codegen: error: dependency cycle found at feature `%s`\n", - f->modname); - exit(2); - } - f->dfsstate = SEEING; - // easier to do wants first, then we can do the conditional counter nonsense - // without worrying about how that fits in... - for (struct feature *const *pp = f->wants.data; - pp - f->wants.data < f->wants.sz; ++pp) { - featdfs(out, *pp); - } -F( " char status_%s = FEAT_OK;", f->modname); - const char *else_ = ""; - if (f->needs.sz == 1) { - featdfs(out, f->needs.data[0]); -F( " if (status_%s != FEAT_OK) status_%s = FEAT_REQFAIL;", - f->needs.data[0]->modname, f->modname) - else_ = "else "; - } - else if (f->needs.sz > 1) { - for (struct feature *const *pp = f->needs.data; - pp - f->needs.data < f->needs.sz; ++pp) { - featdfs(out, *pp); - } -F( " bool metdeps_%s =", f->modname) - for (struct feature *const *pp = f->needs.data; - pp - f->needs.data < f->needs.sz; ++pp) { -F( " status_%s == FEAT_OK%s", (*pp)->modname, - pp - f->needs.data == f->needs.sz - 1 ? ";" : " &&") // dumb but oh well - } -F( " if (!metdeps_%s) status_%s = FEAT_REQFAIL;", f->modname, f->modname) - else_ = "else "; - } - if (f->has_preinit) { -F( " %sif (!_feature_preinit_%s()) status_%s = FEAT_PREFAIL;", else_, - f->modname, f->modname); - else_ = "else "; - } - for (const char **pp = f->need_gamedata.data; - pp - f->need_gamedata.data < f->need_gamedata.sz; ++pp) { -F( " %sif (!has_%s) status_%s = FEAT_NOGD;", else_, *pp, f->modname) - else_ = "else "; // blegh - } - for (const char **pp = f->need_globals.data; - pp - f->need_globals.data < f->need_globals.sz; ++pp) { -F( " %sif (!%s) status_%s = FEAT_NOGLOBAL;", else_, *pp, f->modname) - else_ = "else "; // blegh 2 - } -F( " %sif (!_feature_init_%s()) status_%s = FEAT_FAIL;", else_, f->modname, - f->modname) - if (f->has_end || f->has_evhandlers || f->is_requested) { -F( " has_%s = status_%s == FEAT_OK;", f->modname, f->modname) - } - if (!vec_push(&endstack, f)) die("couldn't allocate memory"); - f->dfsstate = SEEN; -} - -int OS_MAIN(int argc, os_char *argv[]) { - for (++argv; *argv; ++argv) { - const struct cmeta *cm = cmeta_loadfile(*argv); - if (!cm) { - fprintf(stderr, "codegen: fatal: couldn't load file %" fS "\n", - *argv); - exit(100); - } - cmeta_conmacros(cm, &oncondef); - cmeta_evdefmacros(cm, &onevdef); - if (!vec_push(&pass2, ((struct passinfo){cm, *argv}))) { - die("couldn't allocate memory"); - } - } - - // we have to do a second pass for features and event handlers. also, - // there's a bunch of terrible garbage here. don't stare for too long... - for (struct passinfo *pi = pass2.data; pi - pass2.data < pass2.sz; ++pi) { - // XXX: I guess we should cache these by name or something! - const struct cmeta *cm = pi->cm; -#ifdef _WIN32 - int arglen = wcslen(pi->path); - char *p = malloc(arglen + 1); - if (!p) die("couldn't allocate string"); - // XXX: Unicode isn't real, it can't hurt you. - for (const ushort *q = pi->path; q - pi->path < arglen; ++q) { - p[q - pi->path] = *q; // ugh this is stupid - } - p[arglen] = '\0'; -#else - const char *p = pi->path; -#endif - const char *lastslash = p - 1; - for (; *p; ++p) { -#ifdef _WIN32 - if (*p == '/' || *p == '\\') { -#else - if (*p == '/') { -#endif - lastslash = p; - } - } - int len = strlen(lastslash + 1); - if (len <= 3 || lastslash[len - 1] != '.' || lastslash[len] != 'c') { - fprintf(stderr, "filenames should end in .c probably\n"); - exit(2); - } - char *modname = malloc(len - 1); - if (!modname) die("couldn't allocate string"); - memcpy(modname, lastslash + 1, len - 2); - modname[len - 2] = '\0'; - // ugh. same dumb hacks from compile scripts - if (!strcmp(modname, "con_")) { - free(modname); // might as well - modname = "con"; - } - else if (!strcmp(modname, "sst")) { - continue; // I guess??? - } - const char *featdesc = cmeta_findfeatmacro(cm); - if (featdesc) { - struct feature *f = malloc(sizeof(*f)); - if (!f) die("couldn't allocate memory"); - *f = (struct feature){ - .modname = modname, - .desc = featdesc[0] ? featdesc : 0, - .cm = cm - }; - skiplist_insert_feature(&features, modname, f); - if (f->desc) { - skiplist_insert_feature_bydesc(&features_bydesc, f->desc, f); - } - } - cmeta_evhandlermacros(cm, modname, &onevhandler); - } - // yet another pass because I am stupid and don't want to think harder :) - for (struct feature *f = features.x[0]; f; f = f->hdr.x[0]) { - cmeta_featinfomacros(f->cm, &onfeatinfo, f); - } - - FILE *out = fopen(".build/include/cmdinit.gen.h", "wb"); - if (!out) die("couldn't open cmdinit.gen.h"); -H(); - for (const struct conent *p = conents; p - conents < nconents; ++p) { -F( "extern struct con_%s *%s;", p->isvar ? "var" : "cmd", p->name) - } -_( "") -_( "static void regcmds(void) {") - for (const struct conent *p = conents; p - conents < nconents; ++p) { - if (p->isvar) { -F( " initval(%s);", p->name) - } - if (!p->unreg) { -F( " con_reg(%s);", p->name) - } - } -_( "}") -_( "") -_( "static void freevars(void) {") - for (const struct conent *p = conents; p - conents < nconents; ++p) { - if (p->isvar) { -F( " extfree(%s->strval);", p->name) - } - } -_( "}") - if (fclose(out) == EOF) die("couldn't fully write cmdinit.gen.h"); - - out = fopen(".build/include/featureinit.gen.h", "wb"); - if (!out) die("couldn't open featureinit.gen.h"); - H() - // XXX: I dunno whether this should just be defined in sst.c. It's sort of - // internal to the generated stuff hence tucking it away here, but that's at - // the cost of extra string-spaghettiness -_( "enum {") -_( " FEAT_OK,") -_( " FEAT_REQFAIL,") -_( " FEAT_PREFAIL,") -_( " FEAT_NOGD,") -_( " FEAT_NOGLOBAL,") -_( " FEAT_FAIL") -_( "};") -_( "") -_( "static const char *const featmsgs[] = {") -_( " \" [ OK! ] %s\\n\",") -_( " \" [ skipped ] %s (requires another feature)\\n\",") -_( " \" [ skipped ] %s (not applicable or useful)\\n\",") -_( " \" [ unsupported ] %s (missing gamedata)\\n\",") -_( " \" [ FAILED! ] %s (failed to access engine)\\n\",") -_( " \" [ FAILED! ] %s (error in initialisation)\\n\"") -_( "};") -_( "") - for (struct feature *f = features.x[0]; f; f = f->hdr.x[0]) { - if (f->has_preinit) { -F( "extern bool _feature_preinit_%s(void);", f->modname) - } -F( "extern bool _feature_init_%s(void);", f->modname) - if (f->has_end) { -F( "extern bool _feature_end_%s(void);", f->modname) - } - if (f->is_requested) { -F( "bool has_%s = false;", f->modname) - } - else if (f->has_end || f->has_evhandlers) { -F( "static bool has_%s = false;", f->modname) - } - } -_( "") -_( "static void initfeatures(void) {") - for (struct feature *f = features.x[0]; f; f = f->hdr.x[0]) featdfs(out, f); -_( "") - // note: old success message is moved in here, to get the ordering right -_( " con_colourmsg(&(struct rgba){64, 255, 64, 255},") -_( " LONGNAME \" v\" VERSION \" successfully loaded\");") -_( " con_colourmsg(&(struct rgba){255, 255, 255, 255}, \" for game \");") -_( " con_colourmsg(&(struct rgba){0, 255, 255, 255}, \"%s\\n\", ") -_( " gameinfo_title);") -_( " struct rgba white = {255, 255, 255, 255};") -_( " struct rgba green = {128, 255, 128, 255};") -_( " struct rgba red = {255, 128, 128, 255};") -_( " con_colourmsg(&white, \"---- List of plugin features ---\\n\");"); - for (const struct feature *f = features_bydesc.x[0]; f; - f = f->hdr_bydesc.x[0]) { -F( " con_colourmsg(status_%s == FEAT_OK ? &green : &red,", f->modname) -F( " featmsgs[(int)status_%s], \"%s\");", f->modname, f->desc) - } -_( "}") -_( "") -_( "static void endfeatures(void) {") - for (struct feature **pp = endstack.data + endstack.sz - 1; - pp - endstack.data >= 0; --pp) { - if ((*pp)->has_end) { -F( " if (has_%s) _feature_end_%s();", (*pp)->modname, (*pp)->modname) - } - } -_( "}") -_( "") - if (fclose(out) == EOF) die("couldn't fully write featureinit.gen.h"); - - out = fopen(".build/include/evglue.gen.h", "wb"); - if (!out) die("couldn't open evglue.gen.h"); - H_() - for (const struct event *e = events.x[0]; e; e = e->hdr.x[0]) { -_( "") - // gotta break from the string emit macros for a sec in order to do the - // somewhat more complicated task sometimes referred to as a "for loop" - fprintf(out, "%s_%s(", e->name & 1 ? "bool CHECK" : "void EMIT", - (const char *)(e->name & ~1ull)); - for (int n = 0; n < (int)e->nparams - 1; ++n) { - fprintf(out, "typeof(%s) a%d, ", e->params[n], n + 1); - } - if (e->nparams && strcmp(e->params[0], "void")) { - fprintf(out, "typeof(%s) a%d", e->params[e->nparams - 1], - e->nparams); - } - else { - // just unilaterally doing void for now. when we're fully on C23 - // eventually we can unilaterally do nothing instead - fputs("void", out); - } -_( ") {") - for (usize *pp = e->handlers.data; - pp - e->handlers.data < e->handlers.sz; ++pp) { - const char *modname = (const char *)(*pp & ~1ull); - fprintf(out, "\t%s _evhandler_%s_%s(", e->name & 1 ? "bool" : "void", - modname, (const char *)(e->name & ~1ull)); - for (int n = 0; n < (int)e->nparams - 1; ++n) { - fprintf(out, "typeof(%s) a%d, ", e->params[n], n + 1); - } - if (e->nparams && strcmp(e->params[0], "void")) { - fprintf(out, "typeof(%s) a%d", e->params[e->nparams - 1], - e->nparams); - } - else { - fputs("void", out); - } - fputs(");\n\t", out); - // conditional and non-conditional cases - in theory could be - // unified a bit but this is easier to make output relatively pretty - // note: has_* variables are already included by this point (above) - if (e->name & 1) { - if (*pp & 1) fprintf(out, "if (has_%s && !", modname); - else fprintf(out, "if (!"); - fprintf(out, "_evhandler_%s_%s(", modname, - (const char *)(e->name & ~1ull)); - // XXX: much repetitive drivel here - for (int n = 0; n < (int)e->nparams - 1; ++n) { - fprintf(out, "a%d,", n + 1); - } - if (e->nparams && strcmp(e->params[0], "void")) { - fprintf(out, "a%d", e->nparams); - } - fputs(")) return false;\n", out); - } - else { - if (*pp & 1) fprintf(out, "if (has_%s) ", modname); - fprintf(out, "_evhandler_%s_%s(", modname, - (const char *)(e->name & ~1ull)); - for (int n = 0; n < (int)e->nparams - 1; ++n) { - fprintf(out, "a%d,", n + 1); - } - if (e->nparams && strcmp(e->params[0], "void")) { - fprintf(out, "a%d", e->nparams); - } - fputs(");\n", out); - } - } - if (e->name & 1) fputs("\treturn true;\n", out); -_( "}") - } - if (fclose(out) == EOF) die("couldn't fully write evglue.gen.h"); - - return 0; -} - -// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/build/gluegen.c b/src/build/gluegen.c new file mode 100644 index 0000000..4a0f4a1 --- /dev/null +++ b/src/build/gluegen.c @@ -0,0 +1,963 @@ +/* + * Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../intdefs.h" +#include "../langext.h" +#include "../os.h" +#include "cmeta.h" + +#ifdef _WIN32 +#define fS "S" +#else +#define fS "s" +#endif + +static inline noreturn die(int status, const char *s) { + fprintf(stderr, "gluegen: fatal: %s\n", s); + exit(status); +} + +static inline noreturn diefile(int status, const os_char *f, int line, + const char *s) { + if (line) { + fprintf(stderr, "gluegen: fatal: %" fS ":%d: %s\n", f, line, s); + } + else { + fprintf(stderr, "gluegen: fatal: %" fS ": %s\n", f, s); + } + exit(status); +} + +#define ARENASZ (1024 * 1024) +static _Alignas(64) char _arena[ARENASZ] = {0}, *const arena = _arena - 64; +static int arena_last = 0; +static int arena_used = 64; // using 0 indices as null; reserve and stay aligned + +static inline void _arena_align(void) { + enum { ALIGN = ssizeof(void *) }; + if (arena_used & ALIGN - 1) arena_used = (arena_used + ALIGN) & ~(ALIGN - 1); +} + +static inline int arena_bump(int amt) { + if_cold (arena_used + amt >= ARENASZ) die(2, "out of arena memory"); + int ret = arena_used; + arena_used += amt; + return ret; +} + +static int arena_new(int sz) { + _arena_align(); + arena_last = arena_used; + int off = arena_bump(sz); + return off; +} + +#define LIST_MINALLOC 64 +#define LIST_MINSPACE (LIST_MINALLOC - ssizeof(struct list_chunkhdr)) + +struct list_chunkhdr { int next, sz; }; +struct list_chunk { struct list_chunkhdr hdr; char space[LIST_MINSPACE]; }; + +static struct list_grow_ret { + struct list_chunkhdr *tail; + void *data; +} list_grow(struct list_chunkhdr *tail, int amt) { + if_hot ((char *)tail == arena + arena_last) { + arena_bump(amt); + } + else if (tail->sz + amt > LIST_MINSPACE) { + int allocsz = ssizeof(struct list_chunkhdr) + amt; + int new = arena_new(allocsz > LIST_MINALLOC ? allocsz : LIST_MINALLOC); + struct list_chunkhdr *newptr = (struct list_chunkhdr *)(arena + new); + newptr->next = 0; newptr->sz = 0; + tail->next = new; + return (struct list_grow_ret){newptr, (char *)(newptr + 1)}; + } + struct list_grow_ret r = {tail, (char *)(tail + 1) + tail->sz}; + tail->sz += amt; + return r; +} + +static inline void *list_grow_p(struct list_chunkhdr **tailp, int amt) { + struct list_grow_ret r = list_grow(*tailp, amt); + *tailp = r.tail; + return r.data; +} + +#define list_append_p(T, tailp) ((T *)list_grow_p((tailp), ssizeof(T))) +#define list_append(tailp, x) ((void)(*list_append_p(typeof(x), (tailp)) = (x))) + +#define _list_foreach(T, varname, firstchunkp, extra) \ + for (struct list_chunkhdr *_h = &(firstchunkp)->hdr; _h; \ + _h = _h->next ? (struct list_chunkhdr *)(arena + _h->next) : 0) \ + for (typeof(T) *varname = (typeof(T) *)(_h + 1); \ + (char *)varname < (char *)(_h + 1) + _h->sz && (extra); \ + ++varname) \ + /* ... */ + +#define list_foreach_p(T, varname, firstchunkp) \ + _list_foreach(T, varname, firstchunkp, 1) + /* {...} */ + +#define list_foreach(T, varname, firstchunkp) \ + /* N.B. this crazy looking construct avoids unused warnings on varname + in case we just want to count up the entries or something */ \ + switch (0) for (typeof(T) varname; 0;) if ((void)varname, 0) case 0: \ + _list_foreach(T, _foreachp, firstchunkp, (varname = *_foreachp, 1)) \ + /* ... */ + +#define DEF_NEW(type, func, nvar, maxvar, desc) \ + static inline type func(void) { \ + if_cold (nvar == maxvar) { \ + die(2, "out of " desc " - increase " #maxvar " in gluegen.c!"); \ + } \ + return nvar++; \ + } + +// trickery to enable 1-based indexing (and thus 0-as-null) for various arrays +#define SHUNT(T, x) typeof(T) _array_##x[], *const x = _array_##x - 1, _array_##x + +#define MAX_MODULES 512 +// note that by 1-indexing these, we enable the radix stuff above to use the +// default initialisation as null and we *also* allow argc to be used as a +// direct index into each array when looping through files passed to main()! +// XXX: padded 16-byte structs. could eventually switch to u32 indices. for now, +// locality is favoured over packing. no clue whether that's the right call! +static SHUNT(struct cmeta_slice, mod_names)[MAX_MODULES] = {0}; +static SHUNT(struct cmeta_slice, mod_featdescs)[MAX_MODULES] = {0}; +static SHUNT(struct cmeta_slice, mod_gamespecific)[MAX_MODULES] = {0}; +enum { + HAS_INIT = 1, // really, this means this *is a feature*! + HAS_PREINIT = 2, + HAS_END = 4, + HAS_EVENTS = 8, + HAS_OPTDEPS = 16, // something else depends on *us* with REQUEST() + DFS_SEEING = 64, // for REQUIRE() cycle detection + DFS_SEEN = 128 +}; +static u8 mod_flags[MAX_MODULES] = {0}; +static SHUNT(struct list_chunk, mod_needs)[MAX_MODULES] = {0}; +static SHUNT(struct list_chunk, mod_wants)[MAX_MODULES] = {0}; +static SHUNT(struct list_chunk, mod_gamedata)[MAX_MODULES] = {0}; +static SHUNT(struct list_chunk, mod_globals)[MAX_MODULES] = {0}; +static int nmods = 1; + +static s16 feat_initorder[MAX_MODULES]; +static int nfeatures = 0; + +#define MAX_CVARS 8192 +#define MAX_CCMDS MAX_CVARS +static SHUNT(struct cmeta_slice, cvar_names)[MAX_CVARS]; +static SHUNT(struct cmeta_slice, ccmd_names)[MAX_CCMDS]; +static SHUNT(s16, cvar_feats)[MAX_CVARS]; +static SHUNT(s16, ccmd_feats)[MAX_CVARS]; +static SHUNT(u8, cvar_flags)[MAX_CVARS]; +static SHUNT(u8, ccmd_flags)[MAX_CVARS]; +static int ncvars = 1, nccmds = 1; +DEF_NEW(s16, cvar_new, ncvars, MAX_CVARS, "cvar entries") +DEF_NEW(s16, ccmd_new, nccmds, MAX_CCMDS, "ccmd entries") + +#define MAX_EVENTS 512 +static SHUNT(struct cmeta_slice, event_names)[MAX_EVENTS]; +static SHUNT(s16, event_owners)[MAX_EVENTS]; +static SHUNT(struct list_chunk, event_handlers)[MAX_EVENTS] = {0}; +static SHUNT(struct list_chunkhdr *, event_handlers_tails)[MAX_EVENTS] = {0}; +static SHUNT(struct list_chunk, event_params)[MAX_EVENTS] = {0}; +// XXX: would simply things a little if we could segregate the regular event and +// predicate arrays, but because of how the event.h API is currently set up, +// HANDLE_EVENT doesn't give context for what kind of event something is, so we +// can't do that in a single pass (right now we create placeholder event entries +// and check at the end to make sure we didn't miss one). fixing this is tricky! +static SHUNT(bool, event_predicateflags)[MAX_EVENTS] = {0}; +static int nevents = 1; +DEF_NEW(s16, event_new, nevents, MAX_EVENTS, "event entries") + +// a "crit-nybble tree" (see also: djb crit-bit trees) +struct radix { s16 children[16], critpos; }; +static SHUNT(struct radix, radices)[MAX_MODULES * 2 + MAX_EVENTS]; +static int nradices = 1; // also reserve a null value + +// NOTE: this will never fail, as node count is bounded by modules * 2 + events +static inline s16 radix_new(void) { return nradices++; } + +static int matchlen(const char *s1, const char *s2, int len, bool ignorecase) { + uchar c1, c2; + for (int i = 0; i < len; ++i) { + c1 = s1[i]; c2 = s2[i]; + if (ignorecase) { + c1 |= (c1 >= 'A' && c1 <= 'Z') << 5; // if A-Z, |= 32 -> a-z + c2 |= (c2 >= 'A' && c2 <= 'Z') << 5; // " + } + int diff = c1 ^ c2; + if (diff) return (i << 1) | (diff < 16); + } + return (len << 1); +} +static inline int radixbranch(const char *s, int i, bool ignorecase) { + uchar c = s[i >> 1]; + if (ignorecase) c |= (c >= 'A' && c <= 'Z') << 5; + return c >> ((~i & 1) << 2) & 0xF; +} + +/* + * Tries to insert a string index into a radix/crit-nybble/whatever trie. + * Does not actually put the index in; that way an item can be allocated only + * after determining that it doesn't already exist. Callers have to check + * ret.isnew and act accordingly. + */ +static struct radix_insert_ret { + bool isnew; + union { + s16 *strp; /* If isnew, caller must do `*ret.strp = -stridx;`. */ + s16 stridx; /* If !isnew, `stridx` points at the existing string. */ + }; +} radix_insert(s16 *nodep, const struct cmeta_slice *strlist, + const char *s, int len, bool ignorecase) { + // special case: an empty tree is just null; replace it with a leaf node + if (!*nodep) return (struct radix_insert_ret){true, nodep}; + s16 *insp = nodep; + for (s16 cur;;) { + cur = *nodep; + assume(cur); + if (cur < 0) { + // once we find an existing leaf node, we have to compare our string + // against it to find the critical position (i.e. the point where + // the strings differ) and then insert a new node at that point. + const struct cmeta_slice existing = strlist[-cur]; + int ml; + if (existing.len == len) { + ml = matchlen(existing.s, s, len, ignorecase); + if (ml == len << 1) { + return (struct radix_insert_ret){false, .stridx = -cur}; + } + } + else { + // ugh this is a little inelegant, can we think of a better way? + ml = matchlen(existing.s, s, + len < existing.len ? len : existing.len, ignorecase); + } + int oldbranch = radixbranch(existing.s, ml, ignorecase); + int newbranch = radixbranch(s, ml, ignorecase); + assume(oldbranch != newbranch); + for (;;) { + // splice in front of an existing string *or* an empty slot. + if (*insp <= 0) break; + struct radix *r = radices + *insp; + if (r->critpos > ml) break; + insp = r->children + radixbranch(s, r->critpos, ignorecase); + } + s16 new = radix_new(); + radices[new].critpos = ml; + radices[new].children[oldbranch] = *insp; + s16 *strp = radices[new].children + newbranch; + *insp = new; + return (struct radix_insert_ret){true, strp}; + } + // try to take the exact path to match common prefixes, but otherwise + // just pick any path that lets us find a string to compare with. + // we always have to compare against an existing string to determine the + // exact correct point at which to insert a new node. + int branch = 0; + if_hot (radices[cur].critpos <= len << 1) { + branch = radixbranch(s, radices[cur].critpos, ignorecase); + } + while (!radices[cur].children[branch]) branch = (branch + 1) & 15; + nodep = radices[cur].children + branch; + } +} + +/* + * Inserts an entry that's already in a string list. Can be used for + * dupe-checking after appending a new entry. Returns false on duplicate + * entries. + */ +static bool radix_insertidx(s16 *nodep, const struct cmeta_slice *strlist, + s16 stridx, bool ignorecase) { + const char *s = strlist[stridx].s; int len = strlist[stridx].len; + struct radix_insert_ret r = radix_insert(nodep, strlist, s, len, ignorecase); + if (r.isnew) *r.strp = -stridx; + return r.isnew; +} + +/* + * Returns the string index of an existing entry matching `s` and `len`. + * Returns 0 (the reserved null index) if an entry does not exist. + */ +static s16 radix_lookup(s16 node, const struct cmeta_slice *strlist, + const char *s, int len, bool ignorecase) { + while (node) { + if (node < 0) { + const struct cmeta_slice matched = strlist[-node]; + if (matched.len != len) return 0; + if (matchlen(matched.s, s, len, ignorecase) == len << 1) { + return -node; + } + return 0; + } + if (radices[node].critpos >= len << 1) return 0; + int branch = radixbranch(s, radices[node].critpos, ignorecase); + node = radices[node].children[branch]; + } + return 0; +} + +static inline void handle(s16 mod, s16 mods, s16 *featdescs, s16 *events, + const os_char *file, const struct cmeta *cm) { + bool isfeat = false; + const char *needfeat = 0; + bool canpreinit = true, haspreinit = false, hasinit = false; + struct list_chunkhdr *tail_needs = &mod_needs[mod].hdr; + struct list_chunkhdr *tail_wants = &mod_wants[mod].hdr; + struct list_chunkhdr *tail_gamedata = &mod_gamedata[mod].hdr; + struct list_chunkhdr *tail_globals = &mod_globals[mod].hdr; + for (u32 i = 0; i != cm->nitems; ++i) { + switch_exhaust_enum(cmeta_item, cm->itemtypes[i]) { + case CMETA_ITEM_DEF_CVAR: + if (!cmeta_nparams(cm, i)) { + diefile(2, file, cmeta_line(cm, i), + "cvar macro missing required parameter"); + } + int flags = cmeta_flags_cvar(cm, i); + s16 idx = cvar_new(); + cvar_flags[idx] = flags; + // NOTE: always hooking cvar/ccmd up to a feat entry for + // GAMESPECIFIC checks, even if it's not a DEF_FEAT_*. + // If a module doesn't declare FEATURE, its cvars/ccmds will + // end up getting initialised/registered unconditionally. + cvar_feats[idx] = mod; + if (flags & CMETA_CVAR_FEAT) needfeat = "feature cvar defined"; + cmeta_param_foreach (name, cm, i) { + cvar_names[idx] = name; + break; // ignore subsequent args + } + break; + case CMETA_ITEM_DEF_CCMD: + if (!cmeta_nparams(cm, i)) { + diefile(2, file, cmeta_line(cm, i), + "ccmd macro missing required parameter"); + } + flags = cmeta_flags_ccmd(cm, i); + if (flags & CMETA_CCMD_FEAT) { + needfeat = "feature ccmd defined"; + } + if (flags & CMETA_CCMD_PLUSMINUS) { + // split PLUSMINUS entries in two; makes stuff easier later. + flags &= ~CMETA_CCMD_PLUSMINUS; + idx = ccmd_new(); + ccmd_flags[idx] = flags; + ccmd_feats[idx] = mod; + cmeta_param_foreach (name, cm, i) { + char *s = arena + arena_new(5 + name.len); + memcpy(s, "PLUS_", 5); + memcpy(s + 5, name.s, name.len); + ccmd_names[idx] = (struct cmeta_slice){s, 5 + name.len}; + break; + } + idx = ccmd_new(); + ccmd_flags[idx] = flags; + ccmd_feats[idx] = mod; + cmeta_param_foreach (name, cm, i) { + char *s = arena + arena_new(6 + name.len); + memcpy(s, "MINUS_", 6); + memcpy(s + 6, name.s, name.len); + ccmd_names[idx] = (struct cmeta_slice){s, 6 + name.len}; + break; + } + } + else { + idx = ccmd_new(); + ccmd_flags[idx] = flags; + ccmd_feats[idx] = mod; + cmeta_param_foreach (name, cm, i) { + ccmd_names[idx] = name; + break; + } + } + break; + case CMETA_ITEM_DEF_EVENT: + if (!cmeta_nparams(cm, i)) { + diefile(2, file, cmeta_line(cm, i), + "event macro missing required parameter"); + } + flags = cmeta_flags_event(cm, i); + struct cmeta_param_iter it = cmeta_param_iter_init(cm, i); + struct cmeta_slice evname = cmeta_param_iter(&it); + struct radix_insert_ret r = radix_insert(events, event_names, + evname.s, evname.len, false); + int e; + if (r.isnew) { + e = event_new(); + *r.strp = -e; + event_names[e] = evname; + event_handlers_tails[e] = &event_handlers[e].hdr; + } + else { + e = r.stridx; + if (event_owners[e]) { + diefile(2, file, cmeta_line(cm, i), + "conflicting event definition"); + } + } + event_owners[e] = mod; + event_predicateflags[e] = !!(flags & CMETA_EVENT_ISPREDICATE); + struct list_chunkhdr *tail = &event_params[e].hdr; + for (struct cmeta_slice param; param = cmeta_param_iter(&it), + param.s;) { + list_append(&tail, param); + } + break; + case CMETA_ITEM_HANDLE_EVENT: + int nparams = cmeta_nparams(cm, i); + if (!nparams) { + diefile(2, file, cmeta_line(cm, i), + "event handler macro missing required parameter"); + } + it = cmeta_param_iter_init(cm, i); + evname = cmeta_param_iter(&it); + r = radix_insert(events, event_names, evname.s, evname.len, + false); + if (r.isnew) { + e = event_new(); + *r.strp = -e; + event_names[e] = evname; + event_handlers_tails[e] = &event_handlers[e].hdr; + } + else { + e = r.stridx; + } + list_append(event_handlers_tails + e, mod); + mod_flags[mod] |= HAS_EVENTS; + break; + case CMETA_ITEM_FEATURE: + isfeat = true; + // note: if no param given, featdesc will still be null + cmeta_param_foreach (param, cm, i) { + mod_featdescs[mod] = param; + if (!radix_insertidx(featdescs, mod_featdescs, mod, true)) { + diefile(2, file, cmeta_line(cm, i), + "duplicate feature description text"); + } + break; + } + break; + case CMETA_ITEM_REQUIRE: + if (!cmeta_nparams(cm, i)) { + diefile(2, file, cmeta_line(cm, i), + "dependency macro missing required parameter"); + } + flags = cmeta_flags_require(cm, i); + struct list_chunkhdr **tailp; + switch_exhaust(flags) { + case 0: tailp = &tail_needs; break; + case CMETA_REQUIRE_OPTIONAL: tailp = &tail_wants; break; + case CMETA_REQUIRE_GAMEDATA: tailp = &tail_gamedata; break; + case CMETA_REQUIRE_GLOBAL: tailp = &tail_globals; + } + cmeta_param_foreach(param, cm, i) { + int modflags = 0; + switch_exhaust(flags) { + case CMETA_REQUIRE_OPTIONAL: + modflags = HAS_OPTDEPS; + case 0: + canpreinit = false; + s16 depmod = radix_lookup(mods, mod_names, param.s, + param.len, false); + if (!depmod) { + fprintf(stderr, "cmeta_fatal: %" fS ":%d: " + "feature `%.*s` does not exist\n", + file, cmeta_line(cm, i), + param.len, param.s); + exit(2); + } + mod_flags[depmod] |= modflags; + list_append(tailp, depmod); + break; + case CMETA_REQUIRE_GAMEDATA: case CMETA_REQUIRE_GLOBAL: + list_append(tailp, param); + } + } + break; + case CMETA_ITEM_GAMESPECIFIC: + canpreinit = false; + if_cold (!cmeta_nparams(cm, i)) { + diefile(2, file, cmeta_line(cm, i), + "GAMESPECIFIC macro missing required parameter"); + } + needfeat = "GAMESPECIFIC specified"; + if_cold (mod_gamespecific[mod].s) { + diefile(2, file, cmeta_line(cm, i), + "conflicting GAMESPECIFIC macros"); + } + cmeta_param_foreach(param, cm, i) { + mod_gamespecific[mod] = param; + break; + } + break; + case CMETA_ITEM_INIT: + if (hasinit) { + diefile(2, file, cmeta_line(cm, i), "multiple INIT blocks"); + } + hasinit = true; + mod_flags[mod] |= HAS_INIT; + needfeat = "INIT block defined"; + break; + case CMETA_ITEM_PREINIT: + if (haspreinit) { + diefile(2, file, cmeta_line(cm, i), + "multiple PREINIT blocks"); + } + haspreinit = true; + mod_flags[mod] |= HAS_PREINIT; + needfeat = "PREINIT block defined"; + break; + case CMETA_ITEM_END: + if (mod_flags[mod] & HAS_END) { + diefile(2, file, cmeta_line(cm, i), "multiple END blocks"); + } + mod_flags[mod] |= HAS_END; + needfeat = "END block defined"; + } + } + if (needfeat && !isfeat) { + fprintf(stderr, "gluegen: fatal: %" fS ": %s without FEATURE()", file, + needfeat); + exit(2); + } + if (isfeat && !hasinit) { + diefile(2, file, 0, "feature is missing INIT block"); + } + if (!canpreinit && haspreinit) { + diefile(2, file, 0, "cannot use dependencies along with PREINIT"); + } +} + +static int dfs(s16 mod, bool first); +static int dfs_inner(s16 mod, s16 dep, bool first) { + if (!(mod_flags[dep] & HAS_INIT)) { + fprintf(stderr, "gluegen: fatal: feature `%.*s` tried to depend on " + "non-feature module `%.*s`\n", + mod_names[mod].len, mod_names[mod].s, + mod_names[dep].len, mod_names[dep].s); + exit(2); + } + switch (dfs(dep, false)) { + // unwind the call stack by printing each node in the dependency cycle. + // ASCII arrows are kind of ugly here but windows' CRT handles unicode + // *so* horrendously and I'm not in the mood to replace stdio for this + // (maybe another time!) + case 1: + fprintf(stderr, ".-> %.*s\n", + mod_names[mod].len, mod_names[mod].s); + return 2; + case 2: + fprintf(stderr, first ? "'-- %.*s\n" : "| %.*s\n", + mod_names[mod].len, mod_names[mod].s); + return 2; + } + return 0; +} +static int dfs(s16 mod, bool first) { + if (mod_flags[mod] & DFS_SEEN) return 0; + if (mod_flags[mod] & DFS_SEEING) { + fprintf(stderr, "gluegen: fatal: feature dependency cycle:\n"); + return 1; + } + mod_flags[mod] |= DFS_SEEING; + list_foreach(s16, dep, mod_needs + mod) { + if (dfs_inner(mod, dep, first)) return 2; + } + list_foreach(s16, dep, mod_wants + mod) { + if (dfs_inner(mod, dep, first)) return 2; + } + feat_initorder[nfeatures++] = mod; + mod_flags[mod] |= DFS_SEEN; // note: no need to bother unsetting SEEING. + return 0; +} + +static inline void sortfeatures(void) { + for (int i = 1; i < nmods; ++i) { + if ((mod_flags[i] & HAS_INIT) && dfs(i, true)) exit(2); + } +} + +static inline noreturn diewrite(void) { die(100, "couldn't write to file"); } +#define _(x) \ + if (fprintf(out, "%s\n", x) < 0) diewrite(); +#define F(f, ...) \ + if (fprintf(out, f "\n", __VA_ARGS__) < 0) diewrite(); +#define H_() \ + _("/* This file is autogenerated by src/build/gluegen.c. DO NOT EDIT! */") +#define H() H_() _("") + +static void recursefeatdescs(FILE *out, s16 node) { + if (node < 0) { + if (mod_featdescs[-node].s) { +F( " if (status_%.*s != FEAT_SKIP) {", + mod_names[-node].len, mod_names[-node].s) +F( " con_colourmsg(status_%.*s == FEAT_OK ? &green : &red,", + mod_names[-node].len, mod_names[-node].s) +F( " featmsgs[status_%.*s], %.*s);", + mod_names[-node].len, mod_names[-node].s, + mod_featdescs[-node].len, mod_featdescs[-node].s) +_( " }") + } + } + else if (node > 0) { + for (int i = 0; i < 16; ++i) { + recursefeatdescs(out, radices[node].children[i]); + } + } +} + +static int evargs(FILE *out, s16 i, const char *suffix) { + int j = 1; + if (fprintf(out, "(") < 0) diewrite(); + list_foreach (struct cmeta_slice, param, event_params + i) { + if (param.len == 4 && !memcmp(param.s, "void", 4)) { + // UGH, crappy special case for (void). with C23 this could be + // redundant, but I think we still want to avoid blowing up gluegen + // if someone (me?) does it the old way out of habit. + // also if someone does void, int something_else this will create + // nonsense, but that's just garbage-in-garbage-out I guess. + break; + } + else if (fprintf(out, "%stypeof(%.*s) a%d", j == 1 ? "" : ", ", + param.len, param.s, j) < 0) { + diewrite(); + } + ++j; + } + if (fputs(suffix, out) < 0) diewrite(); + return j; +} + +static int evargs_notype(FILE *out, s16 i, const char *suffix) { + int j = 1; + if (fprintf(out, "(") < 0) diewrite(); + list_foreach(struct cmeta_slice, param, event_params + i) { + if (param.len == 4 && !memcmp(param.s, "void", 4)) { + break; + } + if (fprintf(out, "%sa%d", j == 1 ? "" : ", ", j) < 0) { + diewrite(); + } + ++j; + } + if (fputs(suffix, out) < 0) diewrite(); + return j; +} + +static inline void gencode(FILE *out, s16 featdescs) { + for (int i = 1; i < nmods; ++i) { + if (mod_flags[i] & HAS_INIT) { +F( "extern int _feat_init_%.*s(void);", mod_names[i].len, mod_names[i].s) + } + if (mod_flags[i] & HAS_PREINIT) { +F( "extern int _feat_preinit_%.*s(void);", mod_names[i].len, mod_names[i].s) + } + if (mod_flags[i] & HAS_END) { +F( "extern void _feat_end_%.*s(void);", mod_names[i].len, mod_names[i].s) + } + } +_( "") +_( "static struct {") + for (int i = 1; i < nmods; ++i) { + if (mod_flags[i] & HAS_OPTDEPS) continue; + if ((mod_flags[i] & HAS_INIT) && + (mod_flags[i] & (HAS_END | HAS_EVENTS))) { +F( " bool _has_%.*s : 1;", mod_names[i].len, mod_names[i].s) + } + } + for (int i = 1; i < nmods; ++i) { + if (mod_flags[i] & HAS_PREINIT) { +F( " int preinit_%.*s : 2;", mod_names[i].len, mod_names[i].s) + } + } +_( "} feats = {0};") +_( "") + for (int i = 1; i < nmods; ++i) { + if (!(mod_flags[i] & HAS_INIT)) continue; + if (mod_flags[i] & HAS_OPTDEPS) { + // If something REQUESTS a feature, it needs to be able to query + // has_*, so make it extern. XXX: this could be bitpacked as well + // potentially however the whole struct would then have to be + // included in all the places that use feature macros, which would + // probably screw up the potential for fast incremental builds... +F( "bool has_%.*s = false;", mod_names[i].len, mod_names[i].s) + } + else if (mod_flags[i] & (HAS_END | HAS_EVENTS)) { + // mildly stupid, but easier really. paper over the difference so we + // can generate the same has_ checks elsewhere :^) +F( "#define has_%.*s (feats._has_%.*s)", + mod_names[i].len, mod_names[i].s, mod_names[i].len, mod_names[i].s) + } + } +_( "") + for (int i = 1; i < ncvars; ++i) { +F( "extern struct con_var *%.*s;", cvar_names[i].len, cvar_names[i].s); + } + for (int i = 1; i < nccmds; ++i) { +F( "extern struct con_cmd *%.*s;", ccmd_names[i].len, ccmd_names[i].s); + } +_( "") +_( "static inline void preinitfeatures(void) {") + for (int i = 1; i < nmods; ++i) { + if (mod_flags[i] & HAS_PREINIT) { +F( " feats.preinit_%.*s = _feat_preinit_%.*s();", + mod_names[i].len, mod_names[i].s, mod_names[i].len, mod_names[i].s) + } + } +_( "}") +_( "") +_( "static inline void initfeatures(void) {") + for (int i = 0; i < nfeatures; ++i) { // N.B.: this *should* be 0-indexed! + const char *else_ = ""; + s16 mod = feat_initorder[i]; + if (mod_flags[mod] & HAS_PREINIT) { +F( " s8 status_%.*s = feats.preinit_%.*s;", + mod_names[mod].len, mod_names[mod].s, + mod_names[mod].len, mod_names[mod].s) + } + else { +F( " s8 status_%.*s;", mod_names[mod].len, mod_names[mod].s) + } + if (mod_gamespecific[mod].s) { +F( " %sif (!GAMETYPE_MATCHES(%.*s)) status_%.*s = FEAT_SKIP;", else_, + mod_gamespecific[mod].len, mod_gamespecific[mod].s, + mod_names[mod].len, mod_names[mod].s) + else_ = "else "; + } + list_foreach (struct cmeta_slice, gamedata, mod_gamedata + mod) { + // this is not a *totally* ideal way of doing this, but it's easy. + // if we had some info about what gamedata was doing, we could avoid + // having to ifdef these cases and could just directly generate the + // right thing. but that'd be quite a bit of work, so... we don't! + if (mod_gamespecific[mod].s) { +F( "#ifdef _GAMES_WITH_%.*s", gamedata.len, gamedata.s) +F( " %sif (!(_gametype_tag_%.*s & _GAMES_WITH_%.*s) && !has_%.*s) {", else_, + mod_gamespecific[mod].len, mod_gamespecific[mod].s, + gamedata.len, gamedata.s, gamedata.len, gamedata.s) +F( " status_%.*s = NOGD;", mod_names[mod].len, mod_names[mod].s) +_( " }") +_( "#else") + } +F( " %sif (!has_%.*s) status_%.*s = NOGD;", else_, + gamedata.len, gamedata.s, mod_names[mod].len, mod_names[mod].s) + if (mod_gamespecific[mod].s) { +_( "#endif") + } + else_ = "else "; + } + list_foreach (struct cmeta_slice, global, mod_globals + mod) { +F( " %sif (!(%.*s)) status_%.*s = NOGLOBAL;", else_, + global.len, global.s, mod_names[mod].len, mod_names[mod].s) + else_ = "else "; + } + list_foreach (s16, dep, mod_needs + mod) { +F( " %sif (status_%.*s != FEAT_OK) status_%.*s = REQFAIL;", else_, + mod_names[dep].len, mod_names[dep].s, + mod_names[mod].len, mod_names[mod].s) + else_ = "else "; + } + if (mod_flags[mod] & (HAS_END | HAS_EVENTS | HAS_OPTDEPS)) { +F( " %sif ((status_%.*s = _feat_init_%.*s()) == FEAT_OK) has_%.*s = true;", + else_, + mod_names[mod].len, mod_names[mod].s, + mod_names[mod].len, mod_names[mod].s, + mod_names[mod].len, mod_names[mod].s) + } + else { +F( " %sstatus_%.*s = _feat_init_%.*s();", else_, + mod_names[mod].len, mod_names[mod].s, + mod_names[mod].len, mod_names[mod].s) + } + } +_( "") + for (int i = 1; i < ncvars; ++i) { + if (!(cvar_flags[i] & CMETA_CVAR_UNREG)) { + if (cvar_flags[i] & CMETA_CVAR_FEAT) { + struct cmeta_slice modname = mod_names[cvar_feats[i]]; +F( " if (status_%.*s != FEAT_SKIP) con_regvar(%.*s);", + modname.len, modname.s, cvar_names[i].len, cvar_names[i].s) +F( " else if (status_%.*s != FEAT_OK) %.*s->base.flags |= CON_HIDDEN;", + modname.len, modname.s, cvar_names[i].len, cvar_names[i].s) + } + else { +F( " con_regvar(%.*s);", cvar_names[i].len, cvar_names[i].s) + } + } + } + for (int i = 1; i < nccmds; ++i) { + if (!(ccmd_flags[i] & CMETA_CCMD_UNREG)) { + if (ccmd_flags[i] & CMETA_CCMD_FEAT) { + struct cmeta_slice modname = mod_names[ccmd_feats[i]]; +F( " if (status_%.*s == FEAT_OK) con_regcmd(%.*s);", + modname.len, modname.s, ccmd_names[i].len, ccmd_names[i].s) + } + else { +F( " con_regcmd(%.*s);", ccmd_names[i].len, ccmd_names[i].s) + } + } + } +_( "") +_( " successbanner();") +_( " struct rgba white = {255, 255, 255, 255};") +_( " struct rgba green = {128, 255, 128, 255};") +_( " struct rgba red = {255, 128, 128, 255};") +_( " con_colourmsg(&white, \"---- List of plugin features ---\\n\");"); + recursefeatdescs(out, featdescs); +_( "}") +_( "") +_( "static inline void endfeatures(void) {") + for (int i = nfeatures - 1; i >= 0; --i) { + s16 mod = feat_initorder[i]; + if (mod_flags[mod] & HAS_END) { +F( " if (has_%.*s) _feat_end_%.*s();", + mod_names[mod].len, mod_names[mod].s, + mod_names[mod].len, mod_names[mod].s) + } + } +_( "}") +_( "") +_( "static inline void freevars(void) {") + for (int i = 1; i < ncvars; ++i) { +F( " extfree(%.*s->strval);", cvar_names[i].len, cvar_names[i].s) + } +_( "}") + for (int i = 1; i < nevents; ++i) { + const char *prefix = event_predicateflags[i] ? + "bool CHECK_" : "void EMIT_"; + if (fprintf(out, "\n%s%.*s", prefix, + event_names[i].len, event_names[i].s) < 0) { + diewrite(); + } + evargs(out, i, ") {\n"); + list_foreach(s16, mod, event_handlers + i) { + const char *type = event_predicateflags[i] ? "bool" : "void"; + if (fprintf(out, "\t%s _evhandler_%.*s_%.*s", type, + mod_names[mod].len, mod_names[mod].s, + event_names[i].len, event_names[i].s) < 0) { + diewrite(); + } + evargs(out, i, ");\n"); + if (event_predicateflags[i]) { + if (mod_flags[mod] & HAS_INIT) { + if (fprintf(out, "\tif (has_%.*s && !", + mod_names[mod].len, mod_names[mod].s) < 0) { + diewrite(); + } + } + else if (fputs("\tif (!", out) < 0) { + diewrite(); + } + if (fprintf(out, "_evhandler_%.*s_%.*s", + mod_names[mod].len, mod_names[mod].s, + event_names[i].len, event_names[i].s) < 0) { + diewrite(); + } + evargs_notype(out, i, ")) return false;\n"); + } + else { + if (fputc('\t', out) < 0) diewrite(); + if ((mod_flags[mod] & HAS_INIT) && fprintf(out, "if (has_%.*s) ", + mod_names[mod].len, mod_names[mod].s) < 0) { + diewrite(); + } + if (fprintf(out, "_evhandler_%.*s_%.*s", + mod_names[mod].len, mod_names[mod].s, + event_names[i].len, event_names[i].s) < 0) { + diewrite(); + } + evargs_notype(out, i, ");\n"); + } + } + if (event_predicateflags[i]) { +_( " return true;") + } +_( "}") + } +} + +int OS_MAIN(int argc, os_char *argv[]) { + s16 modlookup = 0, featdesclookup = 0, eventlookup = 0; + if (argc > MAX_MODULES) { + die(2, "too many files passed - increase MAX_MODULES in gluegen.c!"); + } + nmods = argc; + for (int i = 1; i < nmods; ++i) { + const os_char *f = argv[i]; + int len = 0; + int lastpart = 0; + for (; f[len]; ++len) { +#ifdef _WIN32 + if (f[len] == '/' || f[len] == '\\') lastpart = len + 1; +#else + if (f[len] == '/') lastpart = len + 1; +#endif + } + if_cold (!len) die(1, "empty file path given"); + if_cold (len < lastpart) diefile(1, f, 0, "invalid file path"); + if_cold (len < 3 || memcmp(f + len - 2, OS_LIT(".c"), + 2 * ssizeof(os_char))) { + diefile(1, f, 0, "not a C source file (.c)"); + } + struct cmeta_slice modname; + // ugh. same dumb hack from compile scripts + if_cold (len - lastpart == 6 && !memcmp(f + lastpart, OS_LIT("con_.c"), + 6 * ssizeof(os_char))) { + modname.s = "con"; modname.len = 3; + } + else { + char *p = arena + arena_new(len - lastpart - 2); + // XXX: Unicode isn't real, it can't hurt you. + for (int i = lastpart, j = 0; i < len - 2; ++i, ++j) p[j] = f[i]; + modname.s = p; modname.len = len - lastpart - 2; + } + mod_names[i] = modname; + if (!radix_insertidx(&modlookup, mod_names, i, false)) { + // XXX: might have to fix this some day to handle subdirs and such. + // for now we rely on it happening not to be a problem basically lol + diefile(2, f, 0, "duplicate module name"); + } + } + for (int i = 1; i < nmods; ++i) { + struct cmeta cm = cmeta_loadfile(argv[i]); + handle(i, modlookup, &featdesclookup, &eventlookup, argv[i], &cm); + } + // double check that events are defined. the compiler would also catch this, + // but we can do it faster and with arguably more helpful error information. + for (int i = 1; i < nevents; ++i) { + if (!event_owners[i]) { + fprintf(stderr, "gluegen: fatal: undefined event %.*s\n", + event_names[i].len, event_names[i].s); + exit(2); + } + } + sortfeatures(); + + FILE *out = fopen(".build/include/glue.gen.h", "wb"); + if (!out) die(100, "couldn't open .build/include/glue.gen.h"); + H() + gencode(out, featdesclookup); + if (fflush(out)) die(100, "couldn't finish writing output"); + return 0; +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/build/mkentprops.c b/src/build/mkentprops.c index cda1c02..3df21ed 100644 --- a/src/build/mkentprops.c +++ b/src/build/mkentprops.c @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -30,7 +30,7 @@ #endif static noreturn die(int status, const char *s) { - fprintf(stderr, "mkentprops: %s\n", s); + fprintf(stderr, "mkentprops: fatal: %s\n", s); exit(status); } static noreturn dieparse(const os_char *file, int line, const char *s) { @@ -194,7 +194,7 @@ static inline void handleentry(char *k, char *v, int vlen, } } -static void parse(const os_char *file, int len) { +static inline void parse(const os_char *file, int len) { char *s = sbase; // for convenience if (s[len - 1] != '\n') dieparse(file, 0, "invalid text file (missing EOL)"); enum { BOL = 0, KEY = 4, KWS = 8, VAL = 12, COM = 16, ERR = -1 }; @@ -279,14 +279,12 @@ Fi(" %s = off;", sbase + art_leaves[idx].varstr); _i(" if (mem_loads32(mem_offset(sp, off_SP_type)) == DPT_DataTable) {") _i(" int baseoff = off;") _i(" const struct SendTable *st = mem_loadptr(mem_offset(sp, off_SP_subtable));") -_i(" // BEGIN SUBTABLE") Fi(" for (int i = 0, need = %d; i < st->nprops && need; ++i) {", art_leaves[idx].nsubs + (art_leaves[idx].varstr != -1)) _i(" const struct SendProp *sp = mem_offset(st->props, sz_SendProp * i);") _i(" const char *p = mem_loadptr(mem_offset(sp, off_SP_varname));") dosendtables(out, art_leaves[idx].subtree, indent + 4); _i(" }") -_i(" // END SUBTABLE") _i(" }") } Fi(" --need;") @@ -338,7 +336,7 @@ _i(" } break;") _i("}") } -static void dodecls(FILE *out) { +static inline void dodecls(FILE *out) { for (int i = 0; i < ndecls; ++i) { const char *s = sbase + decls[i]; F( "extern int %s;", s); @@ -346,7 +344,7 @@ F( "#define has_%s (!!%s)", s, s); // offsets will NEVER be 0, due to vtable! } } -static void doinit(FILE *out) { +static inline void doinit(FILE *out) { for (int i = 0; i < ndecls; ++i) { const char *s = sbase + decls[i]; F( "int %s = 0;", s); diff --git a/src/build/mkgamedata.c b/src/build/mkgamedata.c index 1fce1cf..ed8cf97 100644 --- a/src/build/mkgamedata.c +++ b/src/build/mkgamedata.c @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -29,7 +29,7 @@ #endif static noreturn die(int status, const char *s) { - fprintf(stderr, "mkentprops: %s\n", s); + fprintf(stderr, "mkentprops: fatal: %s\n", s); exit(status); } @@ -156,7 +156,37 @@ static inline noreturn diewrite(void) { die(100, "couldn't write to file"); } _( "/* This file is autogenerated by src/build/mkgamedata.c. DO NOT EDIT! */") \ _( "") -static void decls(FILE *out) { +static inline void knowngames(FILE *out) { + // kind of tricky optimisation: if a gamedata entry has no default but + // does have game-specific values which match a feature's GAMESPECIFIC() + // macro, load-time conditional checks resulting from REQUIRE_GAMEDATA() can + // be elided at compile-time. + for (int i = 0, j; i < nents - 1; i = j) { + while (exprs[i]) { // if there's a default value, we don't need this + // skip to next unindented thing, return if there isn't one with at + // least one indented thing under it. + for (++i; indents[i] != 0; ++i) if (i == nents - 1) return; + } +F( "#line %d \"%" fS "\"", srclines[i], srcnames[srcfiles[i]]) + if (fprintf(out, "#define _GAMES_WITH_%s (", sbase + tags[i]) < 0) { + diewrite(); + } + const char *pipe = ""; + for (j = i + 1; j < nents && indents[j] != 0; ++j) { + // don't attempt to optimise for nested conditionals because that's + // way more complicated and also basically defeats the purpose. + if (indents[j] != 1) continue; + if (fprintf(out, "%s \\\n\t _gametype_tag_%s", pipe, + sbase + tags[j]) < 0) { + diewrite(); + } + pipe = " |"; + } + fputs(" \\\n)\n", out); + } +} + +static inline void decls(FILE *out) { for (int i = 0; i < nents; ++i) { if (indents[i] != 0) continue; F( "#line %d \"%" fS "\"", srclines[i], srcnames[srcfiles[i]]) @@ -175,13 +205,13 @@ F( "#line %d \"%" fS "\"", srclines[i], srcnames[srcfiles[i]]) if_cold (i == nents - 1 || !indents[i + 1]) { // no tags - it's constant F( "enum { %s = (%s) };", sbase + tags[i], sbase + exprs[i]) } - else { // global variable intialised by gamedata_init() call + else { // global variable intialised by initgamedata() call F( "extern int %s;", sbase + tags[i]); } } } -static void defs(FILE *out) { +static inline void defs(FILE *out) { for (int i = 0; i < nents; ++i) { if (indents[i] != 0) continue; if_hot (i < nents - 1 && indents[i + 1]) { @@ -196,8 +226,8 @@ F( "int %s = -2147483648;", sbase + tags[i]) } } -static void init(FILE *out) { -_( "void gamedata_init(void) {") +static inline void init(FILE *out) { +_( "static void initgamedata(void) {") int varidx; int indent = 0; for (int i = 0; i < nents; ++i) { @@ -261,6 +291,7 @@ int OS_MAIN(int argc, os_char *argv[]) { FILE *out = fopen(".build/include/gamedata.gen.h", "wb"); if (!out) die(100, "couldn't open gamedata.gen.h"); H(); + knowngames(out); decls(out); out = fopen(".build/include/gamedatainit.gen.h", "wb"); diff --git a/src/build/skiplist.h b/src/build/skiplist.h deleted file mode 100644 index ab6a920..0000000 --- a/src/build/skiplist.h +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef INC_SKIPLIST_H -#define INC_SKIPLIST_H - -#include <stdlib.h> - -#include "../intdefs.h" -#include "../os.h" - -#ifdef _WIN32 -static inline int _skiplist_ffs(uint x) { - uchar _BitScanForward(ulong *idx, ulong mask); - uint ret; - // on Windows, sizeof(ulong) == sizeof(uint) - if (_BitScanForward((ulong *)&ret, x)) return ret + 1; else return 0; -} -#else -#include <strings.h> -#define _skiplist_ffs ffs -#endif - -// WARNING: this is a really hacked-up version of the skiplist.h from cbits in -// order to support windows. It probably isn't a good idea to plop straight into -// your own use case. - -#if defined(__GNUC__) || defined(__clang__) -#define _skiplist_unused __attribute__((unused)) // heck off gcc -#else -#define _skiplist_unused -#endif - -// NOTE: using xoroshiro128++, a comparatively bad (i.e. non-cryptographic) prng -// for the sake of simplicity; original cbits skiplist.h relies on libcpoly to -// get arc4random() everywhere but since we're only using this at build time -// that seemed like a silly dependency to bother with. -//#define _skiplist_rng arc4random - -// ALSO NOTE: the PRNG code here is *decidedly not* thread safe. again, this -// isn't a problem for our use case. just keep it in mind if reusing this header -// for something else. or ideally, don't reuse this header for something else... -static inline uvlong _skiplist_rotl(const uvlong x, int k) { - return (x << k) | (x >> (64 - k)); -} -_skiplist_unused static uvlong _skiplist_rng(void) { - static uvlong s[2]; - static bool init = false; - if (!init) { os_randombytes(s, sizeof(s)); init = true; } - uvlong s0 = s[0], s1 = s[1]; - uvlong ret = _skiplist_rotl(s0 * 5, 7) * 9; - s1 ^= s0; - s[0] = _skiplist_rotl(s0, 24) ^ s1 ^ (s1 << 16); - s[1] = _skiplist_rotl(s1, 37); - return ret; -} - -/* - * Declares the skiplist header struct skiplist_hdr##name, but none of the - * associated functions. Use when the structure needs to be passed around in - * some way but actual operations on the list are a private implementation - * detail. Otherwise, see DECL_SKIPLIST below. - */ -#define DECL_SKIPLIST_TYPE(name, dtype, ktype, levels) \ -typedef dtype _skiplist_dt_##name; \ -typedef ktype _skiplist_kt_##name; \ -enum { skiplist_lvls_##name = (levels) }; \ -struct skiplist_hdr_##name { dtype *x[levels]; }; - -/* - * Declares the skiplist header struct skiplist_hdr_##name, with functions - * skiplist_{get,del,pop,insert}_##name for operating on the list. A single - * occurrence of DEF_SKIPLIST is required to actually implement the - * functions. - * - * This macro implies DECL_SKIPLIST_TYPE (both should not be used). - * - * mod should be either static or extern. - * - * dtype should be the struct type that the skiplist header will be embedded in, - * forming the linked structure. - * - * ktype should be the type of the struct member used for comparisons, for - * example int or char *. - * - * levels should be the number of levels in each node. 4 is probably a - * reasonable number, depending on the size of the structure and how many - * entries need to be stored and looked up. - * - * The resulting get, del, pop and insert functions are hopefully self- - * explanatory - get and del return the relevant node or a null pointer if - * no such node is found. - */ -#define DECL_SKIPLIST(mod, name, dtype, ktype, levels) \ -DECL_SKIPLIST_TYPE(name, dtype, ktype, levels) \ -\ -_skiplist_unused mod dtype *skiplist_get_##name(struct skiplist_hdr_##name *l, \ - ktype k); \ -_skiplist_unused mod dtype *skiplist_del_##name(struct skiplist_hdr_##name *l, \ - ktype k); \ -_skiplist_unused mod dtype *skiplist_pop_##name(struct skiplist_hdr_##name *l); \ -_skiplist_unused mod void skiplist_insert_##name(struct skiplist_hdr_##name *l, \ - ktype k, dtype *node); - -/* - * Implements the functions corresponding to a skiplist - must come after - * DECL_SKIPLIST with the same modifier and name. - * - * compfunc should be a function declared as follows (or an equivalent macro): - * int cf(dtype *x, ktype y); - * - * hdrfunc should be a function declared as follows (or an equivalent macro): - * struct skiplist_hdr_##name *hf(dtype *l); - */ -#define DEF_SKIPLIST(mod, name, compfunc, hdrfunc) \ -static inline int _skiplist_lvl_##name(void) { \ - int i; \ - /* for 2 levels we get 1 50% of the time, 2 25% of the time, 0 25% of the - time. loop if 0 to distribute this evenly (this gets less likely the more - levels there are: at 4 levels, only loops 6% of the time) */ \ - while (!(i = _skiplist_ffs(_skiplist_rng() & \ - ((1 << skiplist_lvls_##name) - 1)))); \ - /* ffs gives bit positions as 1-N but we actually want an array index */ \ - return i - 1; \ -} \ -\ -_skiplist_unused \ -mod _skiplist_dt_##name *skiplist_get_##name(struct skiplist_hdr_##name *l, \ - _skiplist_kt_##name k) { \ - for (int cmp, lvl = skiplist_lvls_##name - 1; lvl > -1; --lvl) { \ - while (l->x[lvl] && (cmp = compfunc(l->x[lvl], k)) < 0) { \ - l = hdrfunc(l->x[lvl]); \ - } \ - /* NOTE: cmp can be uninitialised here, but only if the list is - completely empty, in which case we'd return 0 anyway - so it doesn't - actually matter! */ \ - if (cmp == 0) return l->x[lvl]; \ - } \ - /* reached the end, no match */ \ - return 0; \ -} \ -\ -_skiplist_unused \ -_skiplist_dt_##name *skiplist_del_##name(struct skiplist_hdr_##name *l, \ - _skiplist_kt_##name k) { \ - _skiplist_dt_##name *ret = 0; \ - /* ALSO NOTE: in *this* case, cmp DOES need to be initialised to prevent a - possible null-deref via hdrfunc(l->x[lvl])->x */ \ - for (int cmp = 1, lvl = skiplist_lvls_##name - 1; lvl > -1; --lvl) { \ - while (l->x[lvl] && (cmp = compfunc(l->x[lvl], k)) < 0) { \ - l = hdrfunc(l->x[lvl]); \ - } \ - if (cmp == 0) { \ - ret = l->x[lvl]; \ - /* just shift each link by 1 */ \ - l->x[lvl] = hdrfunc(l->x[lvl])->x[0]; \ - /* ... and update every level of links via loop */ \ - } \ - } \ - /* reached the end, return whatever was found */ \ - return ret; \ -} \ -\ -_skiplist_unused \ -mod _skiplist_dt_##name *skiplist_pop_##name(struct skiplist_hdr_##name *l) { \ - _skiplist_dt_##name *cur = l->x[0]; \ - if (!cur) return 0; \ - l->x[0] = hdrfunc(cur)->x[0]; \ - for (int lvl = 1; lvl < skiplist_lvls_##name; ++lvl) { \ - if (l->x[lvl]) l->x[lvl] = hdrfunc(l->x[lvl])->x[lvl]; \ - } \ - return cur; \ -} \ -\ -_skiplist_unused \ -mod void skiplist_insert_##name(struct skiplist_hdr_##name *l, \ - _skiplist_kt_##name k, _skiplist_dt_##name *node) { \ - /* note: higher levels are unset but also skipped in other searches */ \ - int inslvl = _skiplist_lvl_##name(); \ - for (int lvl = skiplist_lvls_##name - 1; lvl > -1; --lvl) { \ - while (l->x[lvl] && compfunc(l->x[lvl], k) < 0) { \ - l = hdrfunc(l->x[lvl]); \ - } \ - if (lvl <= inslvl) { \ - hdrfunc(node)->x[lvl] = l->x[lvl]; \ - l->x[lvl] = node; \ - } \ - } \ -} \ - -#endif - -// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/build/vec.h b/src/build/vec.h deleted file mode 100644 index 6dfa645..0000000 --- a/src/build/vec.h +++ /dev/null @@ -1,95 +0,0 @@ -/* This file is dedicated to the public domain. */ - -#ifndef INC_VEC_H -#define INC_VEC_H - -#include <errno.h> -#include <stdlib.h> - -#include "../intdefs.h" - -struct _vec { - uint sz; - uint max; - void *data; -}; - -/* - * A dynamic array with push, pop and concatenate operations. - * - * Usage: struct VEC(my_type) myvec = {0}; - * Or: struct myvec VEC(my_type); - * Or: typedef struct VEC(my_type) myvec; - */ -#define VEC(type) { \ - uint sz; \ - uint max; \ - type *data; \ -} - -#if defined(__GNUC__) || defined(__clang__) -__attribute__((unused)) // heck off gcc -#endif -static bool _vec_ensure(struct _vec *v, uint tsize, uint newmax) { - // FIXME: potential overflow at least on 32-bit hosts (if any!?). - // should use reallocarray or something but didn't feel like porting right - // now. consider doing later. - void *new = realloc(v->data, tsize * newmax); - if (new) { v->data = new; v->max = newmax; } - return !!new; -} - -#if defined(__GNUC__) || defined(__clang__) -__attribute__((unused)) // heck off gcc 2 -#endif -static bool _vec_make_room(struct _vec *v, uint tsize, uint addcnt) { - // this overflow check is probably unnecessary, but just in case - u64 chk = v->max + addcnt; - if (chk > 1u << 30) { errno = ENOMEM; return false; } - u32 x = chk; - if (x < 16) { - x = 16; - } - else { - // round up to next 2*n - --x; - x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; - x++; - } - return _vec_ensure(v, tsize, x); -} - -// internal: for reuse by vec0 -#define _vec_push(v, val, slack) ( \ - ((v)->sz + (slack) < (v)->max || \ - _vec_make_room((struct _vec *)(v), sizeof(val), 1)) && /*NOLINT*/ \ - ((v)->data[(v)->sz++ - slack] = (val), true) \ -) - -#define _vec_pushall(v, vals, n, slack) ( \ - ((v)->sz + (n) + (slack) <= (v)->max || \ - _vec_make_room((struct _vec *)(v), sizeof(*(vals)), (n))) && \ - (memcpy((v)->data + (v)->sz - (slack), (vals), (n) * sizeof(*(vals))), \ - (v)->sz += (n), true) \ -) - -/* - * Appends an item to the end of a vector. Gives true on success and false if - * memory allocation fails. - */ -#define vec_push(v, val) _vec_push(v, val, 0) - -/* - * Appends n items from an array to the end of a vector. Gives true on success - * and false if memory allocation fails. - */ -#define vec_pushall(v, vals, n) _vec_pushall(v, vals, n, 0) - -/* - * Removes an item from the end of a vector and gives that item. - */ -#define vec_pop(v) ((v)->data[--(v)->sz]) - -#endif - -// vi: sw=4 ts=4 noet tw=80 cc=80 |