summaryrefslogtreecommitdiffhomepage
path: root/src/build
diff options
context:
space:
mode:
Diffstat (limited to 'src/build')
-rw-r--r--src/build/cmeta.c238
-rw-r--r--src/build/cmeta.h44
-rw-r--r--src/build/codegen.c95
-rw-r--r--src/build/mkgamedata.c238
4 files changed, 615 insertions, 0 deletions
diff --git a/src/build/cmeta.c b/src/build/cmeta.c
new file mode 100644
index 0000000..b895253
--- /dev/null
+++ b/src/build/cmeta.c
@@ -0,0 +1,238 @@
+/*
+ * Copyright © 2021 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 <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "../intdefs.h"
+#include "../os.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.
+ */
+
+// 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.
+#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)
+Type *ty_void = &(Type){TY_VOID, 1, 1};
+Type *ty_bool = &(Type){TY_BOOL, 1, 1};
+Type *ty_char = &(Type){TY_CHAR, 1, 1};
+Type *ty_short = &(Type){TY_SHORT, 2, 2};
+Type *ty_int = &(Type){TY_INT, 4, 4};
+Type *ty_long = &(Type){TY_LONG, 8, 8};
+Type *ty_uchar = &(Type){TY_CHAR, 1, 1, true};
+Type *ty_ushort = &(Type){TY_SHORT, 2, 2, true};
+Type *ty_uint = &(Type){TY_INT, 4, 4, true};
+Type *ty_ulong = &(Type){TY_LONG, 8, 8, true};
+Type *ty_float = &(Type){TY_FLOAT, 4, 4};
+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 *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;
+}
+#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(Token *tok, Token *end) {
+ int len = 1;
+ for (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 (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 die1(const char *s) {
+ fprintf(stderr, "cmeta: fatal: %s\n", s);
+ exit(100);
+}
+
+static char *readsource(const os_char *f) {
+ int fd = os_open(f, O_RDONLY);
+#ifndef _WIN32
+ if (fd == -1) die2("couldn't open ", f);
+#else
+ // TODO/FIXME/TEMP this is dumb and bad
+ if (fd == -1) { fprintf(stderr, "couldn't open %S", f); exit(100); }
+#endif
+ uint bufsz = 8192;
+ char *buf = malloc(bufsz);
+ if (!buf) die1("couldn't allocate memory");
+ int nread;
+ int off = 0;
+ while ((nread = read(fd, buf + off, bufsz - off)) > 0) {
+ off += nread;
+ if (off == bufsz) {
+ bufsz *= 2;
+ // somewhat arbitrary cutoff
+ if (bufsz == 1 << 30) die1("input file is too large");
+ buf = realloc(buf, bufsz);
+ if (!buf) die1("couldn't reallocate memory");
+ }
+ }
+ if (nread == -1) die1("couldn't read file");
+ buf[off] = 0;
+ close(fd);
+ 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 *f) {
+ char *buf = readsource(f);
+#ifdef _WIN32
+ char *realname = malloc(wcslen(f) + 1);
+ if (!realname) die1("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.
+ *realname = *f;
+ for (const ushort *p = f + 1; p[-1]; ++p) realname[p - f] = *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 anyway, and gives far better diagnostics.
+void cmeta_includes(const struct cmeta *cm,
+ void (*cb)(const char *f, bool issys, void *ctxt), void *ctxt) {
+ Token *tp = (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) die1("couldn't allocate memory");
+ memcpy(copy, tp->loc + 1, tp->len - 2);
+ copy[tp->len - 2] = '\0';
+ cb(copy, false, ctxt);
+ //free(copy); // ??????
+ }
+ else if (equal(tp, "<")) {
+ tp = tp->next;
+ if (!tp) break;
+ 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); // ??????
+ }
+ // get to the next line (standard allows extra tokens because)
+ while (!tp->at_bol) {
+ tp = tp->next;
+ if (!tp) return;
+ }
+ }
+}
+
+// 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)) {
+ Token *tp = (Token *)cm;
+ if (!tp || !tp->next || !tp->next->next) return; // DEF_xyz, (, name
+ while (tp) {
+ bool isplusminus = false, isvar = false;
+ if (equal(tp, "DEF_CCMD_PLUSMINUS")) isplusminus = true;
+ else if (equal(tp, "DEF_CVAR") || equal(tp, "DEF_CVAR_MIN") ||
+ equal(tp, "DEF_CVAR_MINMAX")) {
+ isvar = true;
+ }
+ else if (!equal(tp, "DEF_CCMD") && !equal(tp, "DEF_CCMD_HERE")) {
+ tp = tp->next; continue;
+ }
+ 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) die1("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);
+ char *minusname = malloc(sizeof("MINUS_") + tp->len);
+ if (!minusname) die1("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);
+ }
+ else {
+ char *name = malloc(tp->len + 1);
+ if (!name) die1("couldn't allocate memory");
+ memcpy(name, tp->loc, tp->len);
+ name[tp->len] = '\0';
+ cb(name, isvar);
+ }
+ tp = tp->next;
+ }
+}
+
+// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/build/cmeta.h b/src/build/cmeta.h
new file mode 100644
index 0000000..3319e3a
--- /dev/null
+++ b/src/build/cmeta.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright © 2021 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_CMETA_H
+#define INC_CMETA_H
+
+#include <stdbool.h>
+
+#include "../os.h"
+
+struct cmeta;
+
+const struct cmeta *cmeta_loadfile(const os_char *f);
+
+/*
+ * 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);
+
+/*
+ * 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));
+
+#endif
+
+// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/build/codegen.c b/src/build/codegen.c
new file mode 100644
index 0000000..c9be0ef
--- /dev/null
+++ b/src/build/codegen.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright © 2021 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 "../os.h"
+#include "cmeta.h"
+
+static const char *cmdnames[4096]; // arbitrary limit!
+static int ncmdnames = 0;
+static const char *varnames[4096]; // arbitrary limit!
+static int nvarnames = 0;
+
+static void die(const char *s) {
+ fprintf(stderr, "codegen: %s\n", s);
+ exit(100);
+}
+
+#define PUT(array, ent) do { \
+ if (n##array == sizeof(array) / sizeof(*array)) { \
+ fprintf(stderr, "codegen: out of space; make " #array " bigger!\n"); \
+ exit(1); \
+ } \
+ array[n##array++] = ent; \
+} while (0)
+
+static void oncondef(const char *name, bool isvar) {
+ if (isvar) PUT(varnames, name); else PUT(cmdnames, name);
+}
+
+#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! */") \
+_( "")
+
+int OS_MAIN(int argc, os_char *argv[]) {
+ for (++argv; *argv; ++argv) {
+ const struct cmeta *cm = cmeta_loadfile(*argv);
+ cmeta_conmacros(cm, &oncondef);
+ }
+
+ FILE *out = fopen(".build/include/cmdinit.gen.h", "wb");
+ if (!out) die("couldn't open cmdinit.gen.h");
+ H();
+ for (const char *const *pp = cmdnames;
+ pp - cmdnames < ncmdnames; ++pp) {
+F( "extern struct con_cmd *%s;", *pp)
+ }
+ for (const char *const *pp = varnames;
+ pp - varnames < nvarnames; ++pp) {
+F( "extern struct con_var *%s;", *pp)
+ }
+_( "")
+_( "static void regcmds(void (*VCALLCONV f)(void *, void *)) {")
+ for (const char *const *pp = cmdnames;
+ pp - cmdnames < ncmdnames; ++pp) {
+F( " f(_con_iface, %s);", *pp)
+ }
+ for (const char *const *pp = varnames;
+ pp - varnames < nvarnames; ++pp) {
+F( " initval(%s);", *pp)
+F( " f(_con_iface, %s);", *pp)
+ }
+_( "}")
+_( "")
+_( "static void freevars(void) {")
+ for (const char *const *pp = varnames;
+ pp - varnames < nvarnames; ++pp) {
+F( " extfree(%s->strval);", *pp)
+ }
+_( "}")
+ if (fflush(out) == EOF) die("couldn't fully write cmdinit.gen.h");
+
+ return 0;
+}
+
+// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/build/mkgamedata.c b/src/build/mkgamedata.c
new file mode 100644
index 0000000..87de07d
--- /dev/null
+++ b/src/build/mkgamedata.c
@@ -0,0 +1,238 @@
+/*
+ * Copyright © 2021 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 "../kv.h"
+#include "../os.h"
+#include "../noreturn.h" // must come after os.h due to __declspec(noreturn)
+
+#ifdef _WIN32
+#define fS "S"
+#else
+#define fS "s"
+#endif
+
+static noreturn die(const char *s) {
+ fprintf(stderr, "mkgamedata: %s\n", s);
+ exit(100);
+}
+
+/*
+ * We keep the gamedata KV format as simple and flat as possible:
+ *
+ * <varname> <expr>
+ * <varname> { <gametype> <expr> <gametype> <expr> ... [default <expr>] }
+ * [however many entries...]
+ *
+ * If that doesn't make sense, just look at one of the existing data files and
+ * then it should be obvious. :^)
+ *
+ * Note: if `default` isn't given in a conditional block, that piece of gamedata
+ * is considered unavailable and modules that use it won't get initialised/used.
+ */
+struct ent {
+ const char *name;
+ // normally I'd be inclined to do some pointer bitpacking meme but that's
+ // annoying and doesn't matter here so here's an bool and 7 bytes of padding
+ bool iscond;
+ union {
+ struct {
+ struct ent_cond {
+ const char *name;
+ // note: can be any old C expression; just plopped in
+ const char *expr;
+ struct ent_cond *next;
+ } *cond;
+ // store user-specified defaults in a special place to make the
+ // actual codegen logic easier
+ const char *defval;
+ };
+ const char *expr;
+ };
+ struct ent *next;
+} *ents_head, **ents_tail = &ents_head;
+
+struct parsestate {
+ const os_char *filename;
+ struct kv_parser *parser;
+ const char *lastkey;
+ struct ent_cond **nextcond;
+ bool incond;
+};
+
+static noreturn badparse(struct parsestate *state, const char *e) {
+ fprintf(stderr, "mkgamedata: %" fS ":%d:%d: parse error: %s",
+ state->filename, state->parser->line, state->parser->col, e);
+ exit(1);
+}
+
+static void kv_cb(enum kv_token type, const char *p, uint len, void *ctxt) {
+ struct parsestate *state = ctxt;
+ switch (type) {
+ case KV_IDENT: case KV_IDENT_QUOTED:
+ char *k = malloc(len + 1);
+ if (!k) die("couldn't allocate key string");
+ memcpy(k, p, len);
+ k[len] = '\0';
+ state->lastkey = k;
+ break;
+ case KV_NEST_START:
+ if (state->incond) badparse(state, "unexpected nested object");
+ state->incond = true;
+ struct ent *e = malloc(sizeof(*e));
+ if (!e) die("couldn't allocate memory");
+ e->name = state->lastkey;
+ e->iscond = true;
+ e->cond = 0;
+ e->defval = 0;
+ state->nextcond = &e->cond;
+ e->next = 0;
+ *ents_tail = e;
+ ents_tail = &e->next;
+ break;
+ case KV_NEST_END:
+ state->incond = false;
+ break;
+ case KV_VAL: case KV_VAL_QUOTED:
+ if (state->incond) {
+ // dumb special case mentioned above
+ if (!strcmp(state->lastkey, "default")) {
+ (*ents_tail)->defval = state->lastkey;
+ break;
+ }
+ struct ent_cond *c = malloc(sizeof(*c));
+ if (!c) die("couldn't allocate memory");
+ c->name = state->lastkey;
+ char *expr = malloc(len + 1);
+ if (!expr) die("couldn't allocate value/expression string");
+ memcpy(expr, p, len);
+ expr[len] = '\0';
+ c->expr = expr;
+ c->next = 0;
+ *(state->nextcond) = c;
+ state->nextcond = &c->next;
+ }
+ else {
+ // also kind of dumb but whatever
+ struct ent *e = malloc(sizeof(*e));
+ if (!e) die("couldn't allocate memory");
+ e->name = state->lastkey;
+ e->iscond = false;
+ char *expr = malloc(len + 1);
+ if (!expr) die("couldn't allocate value/expression string");
+ memcpy(expr, p, len);
+ expr[len] = '\0';
+ e->expr = expr;
+ e->next = 0;
+ *ents_tail = e;
+ ents_tail = &e->next;
+ }
+ }
+}
+
+#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/mkgamedata.c. DO NOT EDIT! */") \
+_( "")
+
+int OS_MAIN(int argc, os_char *argv[]) {
+ for (++argv; *argv; ++argv) {
+ int fd = os_open(*argv, O_RDONLY);
+ if (fd == -1) die("couldn't open file");
+ struct kv_parser kv = {0};
+ struct parsestate state = {*argv, &kv};
+ char buf[1024];
+ int nread;
+ while (nread = read(fd, buf, sizeof(buf))) {
+ if (nread == -1) die("couldn't read file");
+ kv_parser_feed(&kv, buf, nread, &kv_cb, &state);
+ if (kv.state == KV_PARSER_ERROR) goto ep;
+ }
+ kv_parser_done(&kv);
+ if (kv.state == KV_PARSER_ERROR) {
+ep: fprintf(stderr, "mkgamedata: %" fS ":%d:%d: bad syntax: %s\n",
+ *argv, kv.line, kv.col, kv.errmsg);
+ exit(1);
+ }
+ close(fd);
+ }
+
+ FILE *out = fopen(".build/include/gamedata.gen.h", "wb");
+ if (!out) die("couldn't open gamedata.gen.h");
+ H();
+ for (struct ent *e = ents_head; e; e = e->next) {
+ if (e->iscond) {
+F( "extern int gamedata_%s;", e->name)
+ if (e->defval) {
+F( "#define gamedata_has_%s true", e->name)
+ }
+ else {
+F( "extern bool gamedata_has_%s;", e->name)
+ }
+ }
+ else {
+F( "enum { gamedata_%s = %s };", e->name, e->expr)
+F( "#define gamedata_has_%s true", e->name)
+ }
+ }
+
+ out = fopen(".build/include/gamedatainit.gen.h", "wb");
+ if (!out) die("couldn't open gamedatainit.gen.h");
+ H();
+ for (struct ent *e = ents_head; e; e = e->next) {
+ if (e->iscond) {
+ if (e->defval) {
+F( "int gamedata_%s = %s", e->name, e->defval);
+ }
+ else {
+F( "int gamedata_%s;", e->name);
+F( "bool gamedata_has_%s = false;", e->name);
+ }
+ }
+ }
+_( "")
+_( "void gamedata_init(void) {")
+ for (struct ent *e = ents_head; e; e = e->next) {
+ if (e->iscond) {
+ for (struct ent_cond *c = e->cond; c; c = c->next) {
+ if (!e->defval) {
+ // XXX: not bothering to generate `else`s. technically this
+ // has different semantics; we hope that the compiler can
+ // just do the right thing either way.
+F( " if (GAMETYPE_MATCHES(%s)) {", c->name)
+F( " gamedata_%s = %s;", e->name, c->expr)
+F( " gamedata_has_%s = true;", e->name)
+_( " }")
+ }
+ else {
+F( " if (GAMETYPE_MATCHES(%s)) %s = %s;", c->name, e->name, c->expr)
+ }
+ }
+ }
+ }
+_( "}")
+
+ return 0;
+}
+
+// vi: sw=4 ts=4 noet tw=80 cc=80