/* * 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 #include "../intdefs.h" #include "../kv.h" #include "../noreturn.h" #include "../os.h" #include "vec.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 as possible. Default values are * specified as direct key-value pairs: * * * * Game- or engine-specific values are set using blocks: * * { ... [default ] } * * The most complicated it can get is if conditionals are nested, which * basically translates directly into nested ifs: * { { } } * [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 * unless all the conditions are met. */ struct vec_ent VEC(struct ent *); struct ent { const char *name; // (or condition tag, in a child node) const char *defexpr; struct vec_ent subents; struct ent *parent; // to back up a level during parse }; // root only contains subents list but it's easier to use the same struct static struct ent root = {0}; struct parsestate { const os_char *filename; struct kv_parser *parser; struct ent *curent; // current ent lol bool haddefault; // blegh; }; 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:; if (len == 7 && !memcmp(p, "default", 7)) { // special case! if (state->curent == &root) { badparse(state, "unexpected default keyword at top level"); } struct ent *e = state->curent; if (e->defexpr) { badparse(state, "multiple default keywords"); } state->haddefault = true; break; } state->haddefault = false; char *k = malloc(len + 1); if (!k) die("couldn't allocate key string"); // FIXME(?): should check and prevent duplicate keys probably! // need table.h or something to avoid O(n^2) :) memcpy(k, p, len); k[len] = '\0'; struct ent *e = malloc(sizeof(*e)); if (!e) die("couldn't allocate memory"); e->name = k; e->defexpr = 0; e->subents = (struct vec_ent){0}; if (!vec_push(&state->curent->subents, e)) { die("couldn't append to array"); } e->parent = state->curent; state->curent = e; break; case KV_NEST_START: if (state->haddefault) badparse(state, "default cannot be a block"); break; case KV_NEST_END: if (!state->curent->parent) { badparse(state, "unexpected closing brace"); } state->curent = state->curent->parent; break; case KV_VAL: case KV_VAL_QUOTED: char *s = malloc(len + 1); if (!s) die("couldn't allocate value string"); memcpy(s, p, len); s[len] = '\0'; state->curent->defexpr = s; if (!state->haddefault) { // a non-default value is just a node that itself only has a // default value. state->curent = state->curent->parent; } break; case KV_COND_PREFIX: case KV_COND_SUFFIX: badparse(state, "unexpected conditional"); } } static inline noreturn diewrite(void) { die("couldn't write to file"); } #define _doindent \ for (int _indent = 0; _indent < indent; ++_indent) { \ if (fputs("\t", out) == -1) diewrite(); \ } #define _(x) \ if (fprintf(out, "%s\n", x) < 0) diewrite(); #define _i(x) _doindent _(x) #define F(f, ...) \ if (fprintf(out, f "\n", __VA_ARGS__) < 0) diewrite(); #define Fi(...) _doindent F(__VA_ARGS__) #define H() \ _( "/* This file is autogenerated by src/build/mkgamedata.c. DO NOT EDIT! */") \ _( "") static void decls(FILE *out) { for (struct ent *const *pp = root.subents.data; pp - root.subents.data < root.subents.sz; ++pp) { if ((*pp)->defexpr) { F( "#define has_%s true", (*pp)->name) if ((*pp)->subents.sz) { F( "extern int %s;", (*pp)->name) } else { F( "enum { %s = %s };", (*pp)->name, (*pp)->defexpr) } } else { F( "extern bool has_%s;", (*pp)->name) F( "extern int %s;", (*pp)->name) } } } static void inits(FILE *out, const char *var, struct vec_ent *v, bool needhas, int indent) { for (struct ent *const *pp = v->data; pp - v->data < v->sz; ++pp) { Fi("if (GAMETYPE_MATCHES(%s)) {", (*pp)->name) if ((*pp)->defexpr) { if (needhas) { Fi(" has_%s = true;", var); } Fi(" %s = %s;", var, (*pp)->defexpr); } inits(out, var, &(*pp)->subents, needhas && !(*pp)->defexpr, indent + 1); _i("}") } } static void defs(FILE *out) { for (struct ent *const *pp = root.subents.data; pp - root.subents.data < root.subents.sz; ++pp) { if ((*pp)->defexpr) { if ((*pp)->subents.sz) { F( "int %s = %s;", (*pp)->name, (*pp)->defexpr); } } else { F( "int %s;", (*pp)->name); F( "bool has_%s = false;", (*pp)->name); } } _( "") _( "void gamedata_init(void) {") for (struct ent *const *pp = root.subents.data; pp - root.subents.data < root.subents.sz; ++pp) { inits(out, (*pp)->name, &(*pp)->subents, !(*pp)->defexpr, 1); } _( "}") } 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, &root}; 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(); decls(out); out = fopen(".build/include/gamedatainit.gen.h", "wb"); if (!out) die("couldn't open gamedatainit.gen.h"); H(); defs(out); return 0; } // vi: sw=4 ts=4 noet tw=80 cc=80