X-Git-Url: https://git.distorted.org.uk/~mdw/disorder/blobdiff_plain/9dbb630e213c2fc6e5f4ff078eb972d270aec84c..b36be3a1a6a4b3374669cadf3455b8f75912f089:/lib/macros.c diff --git a/lib/macros.c b/lib/macros.c index e617203..6257c92 100644 --- a/lib/macros.c +++ b/lib/macros.c @@ -28,14 +28,85 @@ #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" +#include "printf.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 + * + * See: + * - @ref EXP_SIMPLE + * - @ref EXP_MAGIC + * - @ref EXP_MACRO + * - @ref EXP_TYPE_MASK + */ + unsigned flags; + + /** @brief Macro argument names */ + char **args; + + /** @brief Callback (cast to appropriate type) + * + * Cast to @ref mx_simple_callback or @ref mx_magic_callback as required. */ + void (*callback)(); + + /** @brief Macro definition + * + * Only for @ref EXP_MACRO expansions. */ + const struct mx_node *definition; +}; + +/** @brief Expansion takes pre-expanded strings + * + * @p callback is cast to @ref mx_simple_callback. */ +#define EXP_SIMPLE 0x0000 + +/** @brief Expansion takes parsed templates, not strings + * + * @p callback is cast to @ref mx_magic_callback. The callback must do its own + * expansion e.g. via mx_expandstr() where necessary. */ +#define EXP_MAGIC 0x0001 + +/** @brief Expansion is a macro */ +#define EXP_MACRO 0x0002 + +/** @brief Mask of types */ +#define EXP_TYPE_MASK 0x0003 + +/** @brief Hash of all expansions + * + * Created by mx_register(), mx_register_macro() or mx_register_magic(). + */ +static hash *expansions; + +static int mx__expand_macro(const struct expansion *e, + const struct mx_node *m, + struct sink *output, + void *u); + +/* Parsing ------------------------------------------------------------------ */ + /** @brief Parse a template * @param filename Input filename (for diagnostics) * @param line Line number (use 1 on initial call) @@ -236,6 +307,336 @@ char *mx_dump(const struct mx_node *m) { return d->vec; } +/* Expansion registration --------------------------------------------------- */ + +static int mx__register(unsigned flags, + const char *name, + int min, + int max, + char **args, + void (*callback)(), + const struct mx_node *definition) { + struct expansion e[1]; + + if(!expansions) + expansions = hash_new(sizeof(struct expansion)); + e->min = min; + e->max = max; + e->flags = flags; + e->args = args; + e->callback = callback; + e->definition = definition; + return hash_add(expansions, name, &e, + ((flags & EXP_TYPE_MASK) == EXP_MACRO) + ? HASH_INSERT : 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(EXP_SIMPLE, name, min, max, 0, (void (*)())callback, 0); +} + +/** @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_register_magic(const char *name, + int min, + int max, + mx_magic_callback *callback) { + mx__register(EXP_MAGIC, name, min, max, 0, (void (*)())callback, 0); +} + +/** @brief Register a macro + * @param name Name + * @param nargs Number of arguments + * @param args Argument names + * @param definition Macro definition + * @return 0 on success, negative on error + */ +int mx_register_macro(const char *name, + int nargs, + char **args, + const struct mx_node *definition) { + if(mx__register(EXP_MACRO, name, nargs, nargs, args, 0/*callback*/, + definition)) { + /* This locates the error to the definition, which may be a line or two + * beyond the @define command itself. The backtrace generated by + * mx_expand() may help more. */ + error(0, "%s:%d: duplicate definition of '%s'", + definition->filename, definition->line, name); + return -2; + } + return 0; +} + +/* 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 + * + * Interpretation of return values: + * - 0 means success + * - -1 means an error writing to the sink. + * - other negative values mean errors generated from with the macro + * expansion system + * - positive values are reserved for the application + * + * If any callback returns non-zero then that value is returned, abandoning + * further expansion. + */ +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: + rc = 0; + 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 switch(e->flags & EXP_TYPE_MASK) { + case EXP_MAGIC: { + /* Magic callbacks we can call directly */ + rc = ((mx_magic_callback *)e->callback)(m->nargs, + m->args, + output, + u); + break; + } + case EXP_SIMPLE: { + /* For simple callbacks we expand their arguments for them. */ + char **args = xcalloc(1 + m->nargs, sizeof (char *)), *argname; + int n; + + for(n = 0; n < m->nargs; ++n) { + /* Argument numbers are at least clear from looking at the text; + * adding names as well would be nice. TODO */ + byte_xasprintf(&argname, "argument #%d", n); + if((rc = mx_expandstr(m->args[n], &args[n], u, argname))) + break; + } + if(!rc) { + args[n] = NULL; + rc = ((mx_simple_callback *)e->callback)(m->nargs, + args, + output, + u); + } + break; + } + case EXP_MACRO: { + /* Macros we expand by rewriting their definition with argument values + * substituted and then expanding that. */ + rc = mx__expand_macro(e, m, output, u); + break; + } + default: + assert(!"impossible EXP_TYPE_MASK value"); + } + if(rc) { + /* For non-IO errors we generate some backtrace */ + if(rc != -1) + error(0, " ...in '%s' at %s:%d", + m->name, m->filename, m->line); + return rc; + } + break; + default: + assert(!"invalid m->type"); + } + return mx_expand(m->next, 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 + * @param what Token for backtrace, or NULL + * @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, + const char *what) { + struct dynstr d[1]; + int rc; + + dynstr_init(d); + if(!(rc = mx_expand(m, sink_dynstr(d), u))) { + dynstr_terminate(d); + *sp = d->vec; + } else + *sp = 0; + if(rc && rc != -1 && what) + error(0, " ...in %s at %s:%d", what, m->filename, m->line); + 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, rc; + struct stat sb; + char *b; + off_t sofar; + const struct mx_node *m; + + 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); + m = mx_parse(path, 1, b, b + sb.st_size); + rc = mx_expand(m, output, u); + if(rc && rc != -1) + /* Mention inclusion in backtrace */ + error(0, " ...in inclusion of file '%s'", path); + return rc; +} + +/** @brief Rewrite a parse tree substituting in macro arguments + * @param m Parse tree to rewrite (from macro definition) + * @param h Hash mapping argument names to argument values + * @return Rewritten parse tree + */ +static const struct mx_node *mx__rewrite(const struct mx_node *m, + hash *h) { + const struct mx_node *head = 0, **tailp = &head, *arg, *mm; + struct mx_node *nm; + int n; + + for(; m; m = m->next) { + switch(m->type) { + case MX_TEXT: + nm = xmalloc(sizeof *nm); + *nm = *m; /* Dumb copy of text node fields */ + nm->next = 0; /* Maintain list structure */ + *tailp = nm; + tailp = (const struct mx_node **)&nm->next; + break; + case MX_EXPANSION: + if(m->nargs == 0 + && (arg = hash_find(h, m->name))) { + /* This expansion has no arguments and its name matches one of the + * macro arguments. (Even if it's a valid expansion name we override + * it.) We insert its value at this point. We do NOT recursively + * rewrite the argument's value - it is outside the lexical scope of + * the argument name. + * + * We need to recreate the list structure but a shallow copy will + * suffice here. + */ + for(mm = arg; mm; mm = mm->next) { + nm = xmalloc(sizeof *nm); + *nm = *mm; + nm->next = 0; + *tailp = nm; + tailp = (const struct mx_node **)&nm->next; + } + } else { + /* This is some other expansion. We recursively rewrite its argument + * values according to h. */ + nm = xmalloc(sizeof *nm); + *nm = *mm; + for(n = 0; n < nm->nargs; ++n) + nm->args[n] = mx__rewrite(m->args[n], h); + nm->next = 0; + *tailp = nm; + tailp = (const struct mx_node **)&nm->next; + } + break; + default: + assert(!"invalid m->type"); + } + } + *tailp = 0; /* Mark end of list */ + return head; +} + +/** @brief Expand a macro + * @param e Macro definition + * @param m Macro expansion + * @param output Where to send output + * @param u User data + * @return 0 on success, non-0 on error + */ +static int mx__expand_macro(const struct expansion *e, + const struct mx_node *m, + struct sink *output, + void *u) { + hash *h = hash_new(sizeof (struct mx_node *)); + int n; + + /* We store the macro arguments in a hash. Currently there is no check for + * duplicate argument names (and this would be the wrong place for it + * anyway); if you do that you just lose in some undefined way. */ + for(n = 0; n < m->nargs; ++n) + hash_add(h, e->args[n], m->args[n], HASH_INSERT); + /* Generate a rewritten parse tree */ + m = mx__rewrite(e->definition, h); + /* Expand the result */ + return mx_expand(m, output, u); + /* mx_expand() will update the backtrace */ +} + /* Local Variables: c-basic-offset:2