summaryrefslogtreecommitdiffhomepage
path: root/src/build/mkgamedata.c
diff options
context:
space:
mode:
authorMichael Smith <mikesmiffy128@gmail.com>2021-11-20 03:10:50 +0000
committerMichael Smith <mikesmiffy128@gmail.com>2021-11-20 03:18:08 +0000
commitda6f343032cb01597dc7866e66f091adf3243a62 (patch)
tree870f8cb8e82bb42202ab92bea03fc6ab35ada7ca /src/build/mkgamedata.c
Initial public snapshot
With code from Bill. Thanks Bill!
Diffstat (limited to 'src/build/mkgamedata.c')
-rw-r--r--src/build/mkgamedata.c238
1 files changed, 238 insertions, 0 deletions
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