aboutsummaryrefslogtreecommitdiff
path: root/src/build
diff options
context:
space:
mode:
Diffstat (limited to 'src/build')
-rw-r--r--src/build/cmeta.c454
-rw-r--r--src/build/cmeta.h115
-rw-r--r--src/build/codegen.c537
-rw-r--r--src/build/gluegen.c963
-rw-r--r--src/build/mkentprops.c12
-rw-r--r--src/build/mkgamedata.c45
-rw-r--r--src/build/skiplist.h206
-rw-r--r--src/build/vec.h95
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