X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/dd0f422a724b04d43e51be47c3517ce5fe4d99fe..6ebc4527c6a103d0532c08744fb916f951018413:/lib/macros.c diff --git a/lib/macros.c b/lib/macros.c index 2a5559c..e11c264 100644 --- a/lib/macros.c +++ b/lib/macros.c @@ -2,43 +2,37 @@ * This file is part of DisOrder * Copyright (C) 2008 Richard Kettlewell * - * This program is free software; you can redistribute it and/or modify + * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA + * along with this program. If not, see . */ /** @file lib/macros.c * @brief Macro expansion */ -#include -#include "types.h" +#include "common.h" -#include #include -#include -#include #include #include #include #include +#include "hash.h" #include "macros.h" #include "mem.h" #include "vector.h" #include "log.h" -#include "hash.h" #include "sink.h" #include "syscalls.h" #include "printf.h" @@ -107,6 +101,13 @@ static int mx__expand_macro(const struct expansion *e, /* Parsing ------------------------------------------------------------------ */ +static int next_non_whitespace(const char *input, + const char *end) { + while(input < end && isspace((unsigned char)*input)) + ++input; + return input < end ? *input : -1; +} + /** @brief Parse a template * @param filename Input filename (for diagnostics) * @param line Line number (use 1 on initial call) @@ -126,18 +127,21 @@ const struct mx_node *mx_parse(const char *filename, int line, const char *input, const char *end) { - int braces, expansion_start_line, argument_start_line; - const char *argument_start, *argument_end, *p; + int braces, argument_start_line, obracket, cbracket; + const char *argument_start, *argument_end; struct mx_node_vector v[1]; struct dynstr d[1]; struct mx_node *head = 0, **tailp = &head, *e; - int omitted_terminator; if(!end) end = input + strlen(input); while(input < end) { if(*input != '@') { - expansion_start_line = line; + e = xmalloc(sizeof *e); + e->next = 0; + e->filename = filename; + e->line = line; + e->type = MX_TEXT; dynstr_init(d); /* Gather up text without any expansions in. */ while(input < end && *input != '@') { @@ -146,126 +150,119 @@ const struct mx_node *mx_parse(const char *filename, dynstr_append(d, *input++); } dynstr_terminate(d); - e = xmalloc(sizeof *e); - e->next = 0; - e->filename = filename; - e->line = expansion_start_line; - e->type = MX_TEXT; e->text = d->vec; *tailp = e; tailp = &e->next; continue; } - mx_node_vector_init(v); - braces = 0; - p = input; - ++input; - expansion_start_line = line; - omitted_terminator = 0; - while(!omitted_terminator && input < end && *input != '@') { - /* Skip whitespace */ - if(isspace((unsigned char)*input)) { - if(*input == '\n') - ++line; - ++input; - continue; + if(input + 1 < end) + switch(input[1]) { + case '@': + /* '@@' expands to '@' */ + e = xmalloc(sizeof *e); + e->next = 0; + e->filename = filename; + e->line = line; + e->type = MX_TEXT; + e->text = "@"; + *tailp = e; + tailp = &e->next; + input += 2; + continue; + case '#': + /* '@#' starts a (newline-eating comment), like dnl */ + input += 2; + while(input < end && *input != '\n') + ++input; + if(*input == '\n') { + ++line; + ++input; + } + continue; + case '_': + /* '@_' expands to nothing. It's there to allow dump to terminate + * expansions without having to know what follows. */ + input += 2; + continue; } - if(*input == '{') { - /* This is a bracketed argument. We'll walk over it counting - * braces to figure out where the end is. */ - ++input; - argument_start = input; - argument_start_line = line; - while(input < end && (*input != '}' || braces > 0)) { - switch(*input++) { - case '{': ++braces; break; - case '}': --braces; break; - case '\n': ++line; break; - } - } - /* If we run out of input without seeing a '}' that's an error */ - if(input >= end) - fatal(0, "%s:%d: unterminated expansion '%.*s'", - filename, argument_start_line, - (int)(input - argument_start), argument_start); + /* It's a full expansion */ + ++input; + e = xmalloc(sizeof *e); + e->next = 0; + e->filename = filename; + e->line = line; + e->type = MX_EXPANSION; + /* Collect the expansion name. Expansion names start with an alnum and + * consist of alnums and '-'. We don't permit whitespace between the '@' + * and the name. */ + dynstr_init(d); + if(input == end) + disorder_fatal(0, "%s:%d: invalid expansion syntax (truncated)", + filename, e->line); + if(!isalnum((unsigned char)*input)) + disorder_fatal(0, "%s:%d: invalid expansion syntax (unexpected %#x)", + filename, e->line, (unsigned char)*input); + while(input < end && (isalnum((unsigned char)*input) || *input == '-')) + dynstr_append(d, *input++); + dynstr_terminate(d); + e->name = d->vec; + /* See what the bracket character is */ + obracket = next_non_whitespace(input, end); + switch(obracket) { + case '(': cbracket = ')'; break; + case '[': cbracket = ']'; break; + case '{': cbracket = '}'; break; + default: cbracket = obracket = -1; break; /* no arguments */ + } + mx_node_vector_init(v); + if(obracket >= 0) { + /* Gather up arguments */ + while(next_non_whitespace(input, end) == obracket) { + while(isspace((unsigned char)*input)) { + if(*input == '\n') + ++line; + ++input; + } + ++input; /* the bracket */ + braces = 0; + /* Find the end of the argument */ + argument_start = input; + argument_start_line = line; + while(input < end && (*input != cbracket || braces > 0)) { + const int c = *input++; + + if(c == obracket) + ++braces; + else if(c == cbracket) + --braces; + else if(c == '\n') + ++line; + } + if(input >= end) { + /* We ran out of input without encountering a balanced cbracket */ + disorder_fatal(0, "%s:%d: unterminated expansion argument '%.*s'", + filename, argument_start_line, + (int)(input - argument_start), argument_start); + } /* Consistency check */ - assert(*input == '}'); + assert(*input == cbracket); /* Record the end of the argument */ argument_end = input; - /* Step over the '}' */ + /* Step over the cbracket */ ++input; - if(input < end && isspace((unsigned char)*input)) { - /* There is at least some whitespace after the '}'. Look - * ahead and see what is after all the whitespace. */ - for(p = input; p < end && isspace((unsigned char)*p); ++p) - ; - /* Now we are looking after the whitespace. If it's - * anything other than '{', including the end of the input, - * then we infer that this expansion finished at the '}' we - * just saw. (NB that we don't move input forward to p - - * the whitespace is NOT part of the expansion.) */ - if(p == end || *p != '{') - omitted_terminator = 1; - } - } else { - /* We are looking at an unbracketed argument. (A common example would - * be the expansion or macro name.) This is terminated by an '@' - * (indicating the end of the expansion), a ':' (allowing a subsequent - * unbracketed argument) or a '{' (allowing a bracketed argument). The - * end of the input will also do. */ - argument_start = input; - argument_start_line = line; - while(input < end - && *input != '@' && *input != '{' && *input != ':') { - if(*input == '\n') ++line; - ++input; - } - argument_end = input; - /* Trailing whitespace is not significant in unquoted arguments (and - * leading whitespace is eliminated by the whitespace skip above). */ - while(argument_end > argument_start - && isspace((unsigned char)argument_end[-1])) - --argument_end; - /* Step over the ':' if that's what we see */ - if(input < end && *input == ':') - ++input; + /* Now we have an argument in [argument_start, argument_end), and we + * know its filename and initial line number. This is sufficient to + * parse it. */ + mx_node_vector_append(v, mx_parse(filename, argument_start_line, + argument_start, argument_end)); } - /* Now we have an argument in [argument_start, argument_end), and we know - * its filename and initial line number. This is sufficient to parse - * it. */ - mx_node_vector_append(v, mx_parse(filename, argument_start_line, - argument_start, argument_end)); } - /* We're at the end of an expansion. We might have hit the end of the - * input, we might have hit an '@' or we might have matched the - * omitted_terminator criteria. */ - if(input < end) { - if(!omitted_terminator) { - assert(*input == '@'); - ++input; - } - } - /* @@ terminates this file */ - if(v->nvec == 0) - break; - /* Currently we require that the first element, the expansion name, is - * always plain text. Removing this restriction would raise some - * interesting possibilities but for the time being it is considered an - * error. */ - if(v->vec[0]->type != MX_TEXT) - fatal(0, "%s:%d: expansion names may not themselves contain expansions", - v->vec[0]->filename, v->vec[0]->line); /* Guarantee a NULL terminator (for the case where there's more than one * argument) */ mx_node_vector_terminate(v); - e = xmalloc(sizeof *e); - e->next = 0; - e->filename = filename; - e->line = expansion_start_line; - e->type = MX_EXPANSION; - e->name = v->vec[0]->text; - e->nargs = v->nvec - 1; - e->args = v->nvec > 1 ? &v->vec[1] : 0; + /* Fill in the remains of the node */ + e->nargs = v->nvec; + e->args = v->vec; *tailp = e; tailp = &e->next; } @@ -274,11 +271,14 @@ const struct mx_node *mx_parse(const char *filename, static void mx__dump(struct dynstr *d, const struct mx_node *m) { int n; - + const struct mx_node *mm; + if(!m) return; switch(m->type) { case MX_TEXT: + if(m->text[0] == '@') + dynstr_append(d, '@'); dynstr_append_string(d, m->text); break; case MX_EXPANSION: @@ -289,7 +289,22 @@ static void mx__dump(struct dynstr *d, const struct mx_node *m) { mx__dump(d, m->args[n]); dynstr_append(d, '}'); } - dynstr_append(d, '@'); + /* If the next non-whitespace is '{', add @_ to stop it being + * misinterpreted */ + mm = m->next; + while(mm && mm->type == MX_TEXT) { + switch(next_non_whitespace(mm->text, mm->text + strlen(mm->text))) { + case -1: + mm = mm->next; + continue; + case '{': + dynstr_append_string(d, "@_"); + break; + default: + break; + } + break; + } break; default: assert(!"invalid m->type"); @@ -297,7 +312,10 @@ static void mx__dump(struct dynstr *d, const struct mx_node *m) { mx__dump(d, m->next); } -/** @brief Dump a parse macro expansion to a string */ +/** @brief Dump a parse macro expansion to a string + * + * Not of production quality! Only intended for testing! + */ char *mx_dump(const struct mx_node *m) { struct dynstr d[1]; @@ -326,9 +344,7 @@ static int mx__register(unsigned 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); + return hash_add(expansions, name, &e, HASH_INSERT_OR_REPLACE); } /** @brief Register a simple expansion rule @@ -370,11 +386,13 @@ int mx_register_macro(const char *name, const struct mx_node *definition) { if(mx__register(EXP_MACRO, name, nargs, nargs, args, 0/*callback*/, definition)) { +#if 0 /* 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); + disorder_error(0, "%s:%d: duplicate definition of '%s'", + definition->filename, definition->line, name); +#endif return -2; } return 0; @@ -414,17 +432,17 @@ int mx_expand(const struct mx_node *m, 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); + disorder_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); + disorder_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", + disorder_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; @@ -470,8 +488,8 @@ int mx_expand(const struct mx_node *m, 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); + disorder_error(0, " ...in @%s at %s:%d", + m->name, m->filename, m->line); return rc; } break; @@ -505,7 +523,7 @@ int mx_expandstr(const struct mx_node *m, } else *sp = 0; if(rc && rc != -1 && what) - error(0, " ...in %s at %s:%d", what, m->filename, m->line); + disorder_error(0, " ...in %s at %s:%d", what, m->filename, m->line); return rc; } @@ -527,11 +545,11 @@ int mx_expand_file(const char *path, const struct mx_node *m; if((fd = open(path, O_RDONLY)) < 0) - fatal(errno, "error opening %s", path); + disorder_fatal(errno, "error opening %s", path); if(fstat(fd, &sb) < 0) - fatal(errno, "error statting %s", path); + disorder_fatal(errno, "error statting %s", path); if(!S_ISREG(sb.st_mode)) - fatal(0, "%s: not a regular file", path); + disorder_fatal(0, "%s: not a regular file", path); sofar = 0; b = xmalloc_noptr(sb.st_size); while(sofar < sb.st_size) { @@ -539,27 +557,59 @@ int mx_expand_file(const char *path, if(n > 0) sofar += n; else if(n == 0) - fatal(0, "unexpected EOF reading %s", path); + disorder_fatal(0, "unexpected EOF reading %s", path); else if(errno != EINTR) - fatal(errno, "error reading %s", path); + disorder_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); + disorder_error(0, " ...in inclusion of file '%s'", path); return rc; } -/** @brief Rewrite a parse tree substituting in macro arguments +/* Macros ------------------------------------------------------------------- */ + +/** @brief Rewrite a parse tree substituting sub-expansions * @param m Parse tree to rewrite (from macro definition) + * @param ... Name/value pairs to rewrite + * @return Rewritten parse tree + * + * The name/value pair list consists of pairs of strings and is terminated by + * (char *)0. Names and values are both copied so need not survive the call. + */ +const struct mx_node *mx_rewritel(const struct mx_node *m, + ...) { + va_list ap; + hash *h = hash_new(sizeof (struct mx_node *)); + const char *n, *v; + struct mx_node *e; + + va_start(ap, m); + while((n = va_arg(ap, const char *))) { + v = va_arg(ap, const char *); + e = xmalloc(sizeof *e); + e->next = 0; + e->filename = m->filename; + e->line = m->line; + e->type = MX_TEXT; + e->text = xstrdup(v); + hash_add(h, n, &e, HASH_INSERT); + /* hash_add() copies n */ + } + return mx_rewrite(m, h); +} + +/** @brief Rewrite a parse tree substituting in macro arguments + * @param definition 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 *definition, - hash *h) { - const struct mx_node *head = 0, **tailp = &head, *argvalue, *m, *mm; +const struct mx_node *mx_rewrite(const struct mx_node *definition, + hash *h) { + const struct mx_node *head = 0, **tailp = &head, *argvalue, *m, *mm, **ap; struct mx_node *nm; int n; @@ -574,7 +624,7 @@ static const struct mx_node *mx__rewrite(const struct mx_node *definition, break; case MX_EXPANSION: if(m->nargs == 0 - && (argvalue = *(const struct mx_node **)hash_find(h, m->name))) { + && (ap = 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 @@ -584,6 +634,7 @@ static const struct mx_node *mx__rewrite(const struct mx_node *definition, * We need to recreate the list structure but a shallow copy will * suffice here. */ + argvalue = *ap; for(mm = argvalue; mm; mm = mm->next) { nm = xmalloc(sizeof *nm); *nm = *mm; @@ -598,7 +649,7 @@ static const struct mx_node *mx__rewrite(const struct mx_node *definition, *nm = *m; nm->args = xcalloc(nm->nargs, sizeof (struct mx_node *)); for(n = 0; n < nm->nargs; ++n) - nm->args[n] = mx__rewrite(m->args[n], h); + nm->args[n] = mx_rewrite(m->args[n], h); nm->next = 0; *tailp = nm; tailp = (const struct mx_node **)&nm->next; @@ -632,7 +683,7 @@ static int mx__expand_macro(const struct expansion *e, 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); + m = mx_rewrite(e->definition, h); /* Expand the result */ return mx_expand(m, output, u); /* mx_expand() will update the backtrace */