diff options
Diffstat (limited to 'src/build')
-rw-r--r-- | src/build/cmeta.c | 238 | ||||
-rw-r--r-- | src/build/cmeta.h | 44 | ||||
-rw-r--r-- | src/build/codegen.c | 95 | ||||
-rw-r--r-- | src/build/mkgamedata.c | 238 |
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 |