X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/49472b7d2f5a17161612ce992851732b197dda35..f640bcb3ba82576ec131c564f2b347e2f3accdd3:/lib/macros.c diff --git a/lib/macros.c b/lib/macros.c index e617203..31d765e 100644 --- a/lib/macros.c +++ b/lib/macros.c @@ -28,14 +28,44 @@ #include #include #include +#include +#include +#include +#include +#include #include "macros.h" #include "mem.h" #include "vector.h" #include "log.h" +#include "hash.h" +#include "sink.h" +#include "syscalls.h" VECTOR_TYPE(mx_node_vector, const struct mx_node *, xrealloc); +/** @brief Definition of an expansion */ +struct expansion { + /** @brief Minimum permitted arguments */ + int min; + + /** @brief Maximum permitted arguments */ + int max; + + /** @brief Flags */ + unsigned flags; + + /** @brief Callback (cast to appropriate type) */ + void (*callback)(); +}; + +/** @brief Expansion takes parsed templates, not strings */ +#define EXP_MAGIC 0x0001 + +static hash *expansions; + +/* Parsing ------------------------------------------------------------------ */ + /** @brief Parse a template * @param filename Input filename (for diagnostics) * @param line Line number (use 1 on initial call) @@ -236,6 +266,181 @@ char *mx_dump(const struct mx_node *m) { return d->vec; } +/* Expansion registration --------------------------------------------------- */ + +static void mx__register(unsigned flags, + const char *name, + int min, + int max, + void (*callback)()) { + struct expansion e[1]; + + if(!expansions) + expansions = hash_new(sizeof(struct expansion)); + e->min = min; + e->max = max; + e->flags = flags; + e->callback = callback; + hash_add(expansions, name, &e, HASH_INSERT_OR_REPLACE); +} + +/** @brief Register a simple expansion rule + * @param name Name + * @param min Minimum number of arguments + * @param max Maximum number of arguments + * @param callback Callback to write output + */ +void mx_register(const char *name, + int min, + int max, + mx_simple_callback *callback) { + mx__register(0, name, min, max, (void (*)())callback); +} + +/** @brief Register a magic expansion rule + * @param name Name + * @param min Minimum number of arguments + * @param max Maximum number of arguments + * @param callback Callback to write output + */ +void mx_magic_register(const char *name, + int min, + int max, + mx_magic_callback *callback) { + mx__register(EXP_MAGIC, name, min, max, (void (*)())callback); +} + +/* Expansion ---------------------------------------------------------------- */ + +/** @brief Expand a template + * @param m Where to start + * @param output Where to send output + * @param u User data + * @return 0 on success, non-0 on error∑ + * + * If a sink write fails then -1 is returned. If any callback returns non-zero + * then that value is returned. It is suggested that callbacks adopt this + * policy too and use positive values to mean other kinds of fatal error. + */ +int mx_expand(const struct mx_node *m, + struct sink *output, + void *u) { + const struct expansion *e; + int rc; + + if(!m) + return 0; + switch(m->type) { + case MX_TEXT: + if(sink_writes(output, m->text) < 0) + return -1; + break; + case MX_EXPANSION: + if(!(e = hash_find(expansions, m->name))) { + error(0, "%s:%d: unknown expansion name '%s'", + m->filename, m->line, m->name); + if(sink_printf(output, "[[%s unknown]]", m->name) < 0) + return -1; + } else if(m->nargs < e->min) { + error(0, "%s:%d: expansion '%s' requires %d args, only %d given", + m->filename, m->line, m->name, e->min, m->nargs); + if(sink_printf(output, "[[%s too few args]]", m->name) < 0) + return -1; + } else if(m->nargs > e->max) { + error(0, "%s:%d: expansion '%s' takes at most %d args, but %d given", + m->filename, m->line, m->name, e->max, m->nargs); + if(sink_printf(output, "[[%s too many args]]", m->name) < 0) + return -1; + } else if(e->flags & EXP_MAGIC) { + /* Magic callbacks we can call directly */ + if((rc = ((mx_magic_callback *)e->callback)(m->nargs, + m->args, + output, + u))) + return rc; + } else { + /* For simple callbacks we expand their arguments for them */ + char **args = xcalloc(1 + m->nargs, sizeof (char *)); + int n; + + for(n = 0; n < m->nargs; ++n) + if((rc = mx_expandstr(m->args[n], &args[n], u))) + return rc; + args[n] = NULL; + if((rc = ((mx_simple_callback *)e->callback)(m->nargs, + args, + output, + u))) + return rc; + } + break; + default: + assert(!"invalid m->type"); + } + return mx_expand(m, output, u); +} + +/** @brief Expand a template storing the result in a string + * @param m Where to start + * @param sp Where to store string + * @param u User data + * @return 0 on success, non-0 on error + * + * Same return conventions as mx_expand(). This wrapper is slightly more + * convenient to use from 'magic' expansions. + */ +int mx_expandstr(const struct mx_node *m, + char **sp, + void *u) { + struct dynstr d[1]; + int rc; + + if(!(rc = mx_expand(m, sink_dynstr(d), u))) { + dynstr_terminate(d); + *sp = d->vec; + } + return rc; +} + +/** @brief Expand a template file + * @param path Filename + * @param output Where to send output + * @param u User data + * @return 0 on success, non-0 on error + * + * Same return conventions as mx_expand(). + */ +int mx_expand_file(const char *path, + struct sink *output, + void *u) { + int fd, n; + struct stat sb; + char *b; + off_t sofar; + + if((fd = open(path, O_RDONLY)) < 0) + fatal(errno, "error opening %s", path); + if(fstat(fd, &sb) < 0) + fatal(errno, "error statting %s", path); + if(!S_ISREG(sb.st_mode)) + fatal(0, "%s: not a regular file", path); + sofar = 0; + b = xmalloc_noptr(sb.st_size); + while(sofar < sb.st_size) { + n = read(fd, b + sofar, sb.st_size - sofar); + if(n > 0) + sofar += n; + else if(n == 0) + fatal(0, "unexpected EOF reading %s", path); + else if(errno != EINTR) + fatal(errno, "error reading %s", path); + } + xclose(fd); + return mx_expand(mx_parse(path, 1, b, b + sb.st_size), + output, + u); +} + /* Local Variables: c-basic-offset:2