summaryrefslogtreecommitdiffhomepage
path: root/src/build
diff options
context:
space:
mode:
authorMichael Smith <mikesmiffy128@gmail.com>2022-07-31 16:02:10 +0100
committerMichael Smith <mikesmiffy128@gmail.com>2022-08-10 22:40:52 +0100
commit5e921bf59373d79d27c322ff86e8b5a37b151e45 (patch)
tree4d40e39543b085ce4c8cb9b1a7b3c0108de680c0 /src/build
parentc8d7588251fd4fe63ac6afe2a90ca7066c786609 (diff)
Add magical feature codegen system, at long last
Diffstat (limited to 'src/build')
-rw-r--r--src/build/cmeta.c83
-rw-r--r--src/build/cmeta.h33
-rw-r--r--src/build/codegen.c326
3 files changed, 401 insertions, 41 deletions
diff --git a/src/build/cmeta.c b/src/build/cmeta.c
index 3d49927..260d33f 100644
--- a/src/build/cmeta.c
+++ b/src/build/cmeta.c
@@ -20,6 +20,7 @@
#include "../intdefs.h"
#include "../os.h"
+#include "cmeta.h"
/*
* This file does C metadata parsing/scraping for the build system. This
@@ -31,10 +32,12 @@
* time. Don't worry about it too much.
*/
-// too lazy to write a C tokenizer at the moment, so let's just yoink some code
-// from a hacked-up copy of chibicc, a nice minimal C compiler with code that's
-// pretty easy to work with. it does leak memory by design, but build stuff is
-// all one-shot so that's fine.
+// lazy inlined 3rd party stuff {{{
+// too lazy to write a C tokenizer at the moment, or indeed probably ever, so
+// let's just yoink some code from a hacked-up copy of chibicc, a nice minimal C
+// compiler with code that's pretty easy to work with. it does leak memory by
+// design, but build stuff is all one-shot so that's fine.
+#include "../3p/chibicc/chibicc.h"
#include "../3p/chibicc/unicode.c"
// type sentinels from type.c (don't bring in the rest of type.c because it
// circularly depends on other stuff and we really only want tokenize here)
@@ -88,6 +91,7 @@ static char *join_tokens(Token *tok, Token *end) {
buf[pos] = '\0';
return buf;
}
+// }}}
#ifdef _WIN32
#include "../3p/openbsd/asprintf.c" // missing from libc; plonked here for now
@@ -266,7 +270,74 @@ void cmeta_conmacros(const struct cmeta *cm,
}
}
-void cmeta_evdefmacros(const struct cmeta *cm, void (*cb_def)(const char *name)) {
+const char *cmeta_findfeatmacro(const struct cmeta *cm) {
+ Token *tp = (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;
+ }
+ return 0;
+}
+
+void cmeta_featinfomacros(const struct cmeta *cm, void (*cb)(
+ enum cmeta_featmacro type, const char *param, void *ctxt), void *ctxt) {
+ Token *tp = (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) die1("couldn't allocate memory");
+ memcpy(param, tp->loc, tp->len);
+ param[tp->len] = '\0';
+ cb(type, param, ctxt);
+ tp = tp->next;
+ }
+ }
+ tp = tp->next;
+ }
+}
+
+void cmeta_evdefmacros(const struct cmeta *cm,
+ void (*cb_def)(const char *name)) {
Token *tp = (Token *)cm;
if (!tp || !tp->next || !tp->next->next) return; // DEF_EVENT, (, name
while (tp) {
@@ -298,4 +369,4 @@ void cmeta_evhandlermacros(const struct cmeta *cm, const char *modname,
}
}
-// vi: sw=4 ts=4 noet tw=80 cc=80
+// 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 125ce2c..40c4ac5 100644
--- a/src/build/cmeta.h
+++ b/src/build/cmeta.h
@@ -40,6 +40,39 @@ void cmeta_conmacros(const struct cmeta *cm,
void (*cb)(const char *name, bool isvar, bool unreg));
/*
+ * 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);
+
+/*
+ * 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
+};
+
+/*
+ * 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);
+
+/*
* Iterates through all event-related macros and takes note of which events are
* defined, giving a callback for each.
*/
diff --git a/src/build/codegen.c b/src/build/codegen.c
index 1dee6a1..04a9058 100644
--- a/src/build/codegen.c
+++ b/src/build/codegen.c
@@ -14,35 +14,37 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include "../intdefs.h"
#include "../os.h"
#include "cmeta.h"
#include "skiplist.h"
#include "vec.h"
-#define MAXENT 65536 // arbitrary limit!
-static struct ent {
- const char *name;
- bool unreg;
- bool isvar; // false for cmd
-} ents[MAXENT];
-static int nents;
-
static void die(const char *s) {
fprintf(stderr, "codegen: %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 (nents == sizeof(ents) / sizeof(*ents)) { \
+ if (nconents == sizeof(conents) / sizeof(*conents)) { \
fprintf(stderr, "codegen: out of space; make ents bigger!\n"); \
exit(1); \
} \
- ents[nents].name = name_; \
- ents[nents].isvar = isvar_; ents[nents++].unreg = unreg_; \
+ conents[nconents].name = name_; \
+ conents[nconents].isvar = isvar_; conents[nconents++].unreg = unreg_; \
} while (0)
static void oncondef(const char *name, bool isvar, bool unreg) {
@@ -50,10 +52,95 @@ static void oncondef(const char *name, bool isvar, bool 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) {
+ // longer string first
+ if (!*s) return !!*p; else if (!*p) return -1;
+ // case insensitive sort where possible
+ if (tolower(*p) > tolower(*s)) return 1;
+ if (tolower(*p) < tolower(*s)) return -1;
+ // prioritise upper-case if same letter
+ if (isupper(*p) && islower(*s)) return 1;
+ if (islower(*p) && isupper(*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 (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 {
+ struct event {
const char *name;
- struct vec_str handlers;
+ 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) {
@@ -63,7 +150,7 @@ static inline struct skiplist_hdr_event *hdr_event(struct event *e) {
return &e->hdr;
}
DEF_SKIPLIST(static, event, cmp_event, hdr_event)
-struct skiplist_hdr_event events = {0};
+static struct skiplist_hdr_event events = {0};
static void onevdef(const char *name) {
struct event *e = skiplist_get_event(&events, name);
@@ -71,7 +158,7 @@ static void onevdef(const char *name) {
struct event *e = malloc(sizeof(*e));
if (!e) die("couldn't allocate memory");
e->name = name;
- e->handlers = (struct vec_str){0};
+ e->handlers = (struct vec_usize){0};
e->hdr = (struct skiplist_hdr_event){0};
skiplist_insert_event(&events, name, e);
}
@@ -89,24 +176,95 @@ static void onevhandler(const char *evname, const char *modname) {
"non-existent event `%s`\n", modname, evname);
exit(2);
}
+ usize taggedptr = (usize)modname;
+ struct feature *f = skiplist_get_feature(&features, modname);
+ // 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, modname)) die("couldn't allocate memory");
+ 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 src/build/codegen.c. DO NOT EDIT! */") \
-_( "")
+#define H_() \
+ _( "/* This file is autogenerated by "__FILE__". DO NOT EDIT! */")
+#define H() H_() _( "")
-struct passinfo {
- const struct cmeta *cm;
- const os_char *path;
-};
-struct vec_passinfo VEC(struct passinfo) pass2 = {0};
+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) {
@@ -118,8 +276,8 @@ int OS_MAIN(int argc, os_char *argv[]) {
}
}
- // we have to a second pass for event handlers. also, there's a bunch of
- // terrible garbage here. don't stare for too long...
+ // 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;
@@ -162,18 +320,36 @@ int OS_MAIN(int argc, os_char *argv[]) {
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 ent *p = ents; p - ents < nents; ++p) {
+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 ent *p = ents; p - ents < nents; ++p) {
+ for (const struct conent *p = conents; p - conents < nconents; ++p) {
if (p->isvar) {
F( " initval(%s);", p->name)
}
@@ -184,23 +360,103 @@ F( " con_reg(%s);", p->name)
_( "}")
_( "")
_( "static void freevars(void) {")
- for (const struct ent *p = ents; p - ents < nents; ++p) {
+ for (const struct conent *p = conents; p - conents < nconents; ++p) {
if (p->isvar) {
-F( " extfree(%s->strval);", p->name);
+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(RGBA(64, 255, 64, 255),")
+_( " LONGNAME \" v\" VERSION \" successfully loaded\");")
+_( " con_colourmsg(RGBA(255, 255, 255, 255), \" for game \");")
+_( " con_colourmsg(RGBA(0, 255, 255, 255), \"%s\\n\", gameinfo_title);")
+_( " struct con_colour white = {255, 255, 255, 255};")
+_( " struct con_colour green = {128, 255, 128, 255};")
+_( " struct con_colour 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()
+ H_()
for (const struct event *e = events.x[0]; e; e = e->hdr.x[0]) {
+_( "")
F( "void _evemit_%s(void) {", e->name)
- for (const char **pp = e->handlers.data;
+ for (usize *pp = e->handlers.data;
pp - e->handlers.data < e->handlers.sz; ++pp) {
-F( " void _evhandler_%s_%s(void);", *pp, e->name); // blegh.
-F( " _evhandler_%s_%s();", *pp, e->name);
+ const char *modname = (const char *)(*pp & ~1ull);
+F( " void _evhandler_%s_%s(void);", modname, e->name) // blegh.
+ if (*pp & 1ull) {
+ // note: has_* variables are already included by this point (above)
+F( " if (has_%s) _evhandler_%s_%s();", modname, modname, e->name)
+ }
+ else {
+F( " _evhandler_%s_%s();", modname, e->name)
+ }
}
_( "}")
}