diff options
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 |