diff options
author | Michael Smith <mikesmiffy128@gmail.com> | 2021-11-20 03:10:50 +0000 |
---|---|---|
committer | Michael Smith <mikesmiffy128@gmail.com> | 2021-11-20 03:18:08 +0000 |
commit | da6f343032cb01597dc7866e66f091adf3243a62 (patch) | |
tree | 870f8cb8e82bb42202ab92bea03fc6ab35ada7ca /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.c | 238 |
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 |