aboutsummaryrefslogtreecommitdiff
path: root/src/build/codegen.c
diff options
context:
space:
mode:
authorGravatar Michael Smith <mikesmiffy128@gmail.com> 2025-03-10 02:37:19 +0000
committerGravatar Michael Smith <mikesmiffy128@gmail.com> 2025-04-06 16:41:13 +0100
commit244fea664121acf12871ab5858a5fe95a2606b52 (patch)
treee42b1990ef97adc0f0ab48b9be7e11de7fee0558 /src/build/codegen.c
parentd86b7b41453c69b3854baa7cdc05a79a3cdfe092 (diff)
downloadsst-244fea664121acf12871ab5858a5fe95a2606b52.tar.gz
sst-244fea664121acf12871ab5858a5fe95a2606b52.zip
Rewrite and redesign codegen and feature system
Also switch to somewhat proper C23 flags while we're at it. This is a huge change. It took me forever, in between being really busy. Sorry about that. But the good news is I'm now free to start integrating the various patches that have accumulated since last release. Well, at least in between still being really busy. Gotta manage expectations. The main benefit of introducing GAMESPECIFIC() is that features that don't apply to a particular game no longer show up *at all*, and less time is wasted on init. It also enables a cool optimisation wherein unnecessary REQUIRE_GAMEDATA() checks can elided at compile time whenever the gamedata is known up-front to always exist in supported games. The DEF_FEAT_CVAR macro family meanwhile makes it easier to manage the lifecycle of cvars/ccmds, with less manual registering, unhiding and such. Originally I was going to try and just hack these features into the existing codegen abomination, but it just got too terrible. This rewrite should make it easier to continue tweaking codegen behaviour in future. It also has slightly better error messages.
Diffstat (limited to 'src/build/codegen.c')
-rw-r--r--src/build/codegen.c537
1 files changed, 0 insertions, 537 deletions
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