/* * Copyright © 2022 Michael Smith * * 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 #include #include #include "../intdefs.h" #include "../kv.h" #include "../noreturn.h" #include "../os.h" #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: * * * { ... [default ] } * [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; } break; case KV_COND_PREFIX: case KV_COND_SUFFIX: badparse(state, "unexpected conditional"); } } #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"); if (!kv_parser_feed(&kv, buf, nread, &kv_cb, &state)) goto ep; } if (!kv_parser_done(&kv)) { 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