From 1dcdf455db2282ab2d0ed2895788552e7a45e172 Mon Sep 17 00:00:00 2001 Message-Id: <1dcdf455db2282ab2d0ed2895788552e7a45e172.1714043923.git.mdw@distorted.org.uk> From: Mark Wooding Date: Sat, 3 May 2008 20:42:13 +0100 Subject: [PATCH] Copy macro parse into lib/ and make a start on tests for it. Organization: Straylight/Edgeware From: Richard Kettlewell --- .bzrignore | 1 + lib/Makefile.am | 8 +- lib/macros.c | 211 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/macros.h | 76 +++++++++++++++++ lib/t-macros.c | 144 +++++++++++++++++++++++++++++++++ 5 files changed, 439 insertions(+), 1 deletion(-) create mode 100644 lib/macros.c create mode 100644 lib/macros.h create mode 100644 lib/t-macros.c diff --git a/.bzrignore b/.bzrignore index 420efc6..8b39f2e 100644 --- a/.bzrignore +++ b/.bzrignore @@ -172,3 +172,4 @@ lib/t-utf8 lib/t-vector lib/t-words lib/t-wstat +lib/t-macros diff --git a/lib/Makefile.am b/lib/Makefile.am index 5320060..852852d 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -21,7 +21,8 @@ TESTS=t-addr t-basen t-bits t-cache t-casefold t-cookies \ t-filepart t-hash t-heap t-hex t-kvp t-mime t-printf \ t-regsub t-selection t-signame t-sink t-split t-syscalls \ - t-trackname t-unicode t-url t-utf8 t-vector t-words t-wstat + t-trackname t-unicode t-url t-utf8 t-vector t-words t-wstat \ + t-macros noinst_LIBRARIES=libdisorder.a include_HEADERS=disorder.h @@ -59,6 +60,7 @@ libdisorder_a_SOURCES=charset.c charset.h \ kvp.c kvp.h \ log.c log.h log-impl.h \ logfd.c logfd.h \ + macros.c macros.h \ mem.c mem.h mem-impl.h \ mime.h mime.c \ mixer.c mixer.h mixer-oss.c mixer-alsa.c \ @@ -164,6 +166,10 @@ t_kvp_SOURCES=t-kvp.c test.c test.h t_kvp_LDADD=libdisorder.a $(LIBPCRE) $(LIBICONV) $(LIBGC) t_kvp_DEPENDENCIES=libdisorder.a +t_macros_SOURCES=t-macros.c test.c test.h +t_macros_LDADD=libdisorder.a $(LIBPCRE) $(LIBICONV) $(LIBGC) +t_macros_DEPENDENCIES=libdisorder.a + t_mime_SOURCES=t-mime.c test.c test.h t_mime_LDADD=libdisorder.a $(LIBPCRE) $(LIBICONV) $(LIBGC) t_mime_DEPENDENCIES=libdisorder.a diff --git a/lib/macros.c b/lib/macros.c new file mode 100644 index 0000000..7cf7494 --- /dev/null +++ b/lib/macros.c @@ -0,0 +1,211 @@ +/* + * This file is part of DisOrder + * Copyright (C) 2008 Richard Kettlewell + * + * 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 + * (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. + * + * 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 + */ + +/** @file lib/macros.c + * @brief Macro expansion + */ + +#include +#include "types.h" + +#include +#include +#include + +#include "macros.h" +#include "mem.h" +#include "vector.h" +#include "log.h" + +VECTOR_TYPE(mx_node_vector, const struct mx_node *, xrealloc); + +/** @brief Parse a template + * @param filename Input filename (for diagnostics) + * @param line Line number (use 1 on initial call) + * @param input Start of text to parse + * @param end End of text to parse or NULL + * @return Pointer to parse tree root node + * + * Parses the text in [start, end) and returns an (immutable) parse + * tree representing it. + * + * If @p end is NULL then the whole string is parsed. + * + * Note that the @p filename value stored in the parse tree is @p filename, + * i.e. it is not copied. + */ +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; + 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; + dynstr_init(d); + /* Gather up text without any expansions in. */ + while(input < end && *input != '@') { + if(*input == '\n') + ++line; + 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 == '{') { + /* 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); + /* Consistency check */ + assert(*input == '}'); + /* Record the end of the argument */ + argument_end = input; + /* Step over the '}' */ + ++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)); + } + /* 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; + *tailp = e; + tailp = &e->next; + } + return head; +} + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/lib/macros.h b/lib/macros.h new file mode 100644 index 0000000..8996f57 --- /dev/null +++ b/lib/macros.h @@ -0,0 +1,76 @@ +/* + * This file is part of DisOrder + * Copyright (C) 2008 Richard Kettlewell + * + * 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 + * (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. + * + * 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 + */ + +/** @file lib/macros.h + * @brief Macro expansion + */ + +#ifndef MACROS_H +#define MACROS_H + +/** @brief One node in a macro expansion parse tree */ +struct mx_node { + /** @brief Next element or NULL at end of list */ + struct mx_node *next; + + /** @brief Node type, @ref MX_TEXT or @ref MX_EXPANSION. */ + int type; + + /** @brief Filename containing this node */ + const char *filename; + + /** @brief Line number at start of this node */ + int line; + + /** @brief Plain text (if @p type is @ref MX_TEXT) */ + char *text; + + /** @brief Expansion name (if @p type is @ref MX_EXPANSION) */ + char *name; + + /** @brief Argument count (if @p type is @ref MX_EXPANSION) */ + int nargs; + + /** @brief Argument values, parsed recursively (or NULL if @p nargs is 0) */ + const struct mx_node **args; +}; + +/** @brief Text node */ +#define MX_TEXT 0 + +/** @brief Expansion node */ +#define MX_EXPANSION 1 + +const struct mx_node *mx_parse(const char *filename, + int line, + const char *input, + const char *end); + +#endif /* MACROS_H */ + + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/lib/t-macros.c b/lib/t-macros.c new file mode 100644 index 0000000..77164d8 --- /dev/null +++ b/lib/t-macros.c @@ -0,0 +1,144 @@ +/* + * This file is part of DisOrder. + * Copyright (C) 2008 Richard Kettlewell + * + * 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 + * (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. + * + * 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 + */ +#include "test.h" +#include "macros.h" + +static void test_macros(void) { + const struct mx_node *m; +#define L1 "this is just some\n" +#define L2 "plain text\n" + static const char plain[] = L1 L2; + + /* As simple as it gets */ + m = mx_parse("plaintext1", 1, "", NULL); + insist(m == 0); + + /* Almost as simple as that */ + m = mx_parse("plaintext1", 1, plain, NULL); + insist(m->type == MX_TEXT); + check_string(m->filename, "plaintext1"); + insist(m->line == 1); + check_string(m->text, L1 L2); + insist(m->next == 0); + + /* Check that partial parses stop in the right place */ + m = mx_parse("plaintext2", 5, plain, plain + strlen(L1)); + insist(m->type == MX_TEXT); + check_string(m->filename, "plaintext2"); + insist(m->line == 5); + check_string(m->text, L1); + insist(m->next == 0); + + /* The simplest possible expansion */ + m = mx_parse("macro1", 1, "@macro@", NULL); + insist(m->type == MX_EXPANSION); + check_string(m->filename, "macro1"); + insist(m->line == 1); + check_string(m->name, "macro"); + insist(m->nargs == 0); + insist(m->args == 0); + insist(m->next == 0); + + /* Spacing variants of the above */ + m = mx_parse("macro2", 1, "@ macro@", NULL); + insist(m->type == MX_EXPANSION); + check_string(m->filename, "macro2"); + insist(m->line == 1); + check_string(m->name, "macro"); + insist(m->nargs == 0); + insist(m->args == 0); + insist(m->next == 0); + m = mx_parse("macro3", 1, "@macro @", NULL); + insist(m->type == MX_EXPANSION); + check_string(m->filename, "macro3"); + insist(m->line == 1); + check_string(m->name, "macro"); + insist(m->nargs == 0); + insist(m->args == 0); + insist(m->next == 0); + + /* Unterminated variants */ + m = mx_parse("macro4", 1, "@macro", NULL); + insist(m->type == MX_EXPANSION); + check_string(m->filename, "macro4"); + insist(m->line == 1); + check_string(m->name, "macro"); + insist(m->nargs == 0); + insist(m->args == 0); + insist(m->next == 0); + m = mx_parse("macro5", 1, "@macro ", NULL); + insist(m->type == MX_EXPANSION); + check_string(m->filename, "macro5"); + insist(m->line == 1); + check_string(m->name, "macro"); + insist(m->nargs == 0); + insist(m->args == 0); + insist(m->next == 0); + + /* Macros with a :-separated argument */ + m = mx_parse("macro5", 1, "@macro:arg@", NULL); + insist(m->type == MX_EXPANSION); + check_string(m->filename, "macro5"); + insist(m->line == 1); + check_string(m->name, "macro"); + insist(m->nargs == 1); + insist(m->next == 0); + + insist(m->args[0]->type == MX_TEXT); + check_string(m->args[0]->filename, "macro5"); + insist(m->args[0]->line == 1); + check_string(m->args[0]->text, "arg"); + insist(m->args[0]->next == 0); + + /* Multiple :-separated arguments, and spacing, and newlines */ + m = mx_parse("macro6", 1, "@macro : \n arg1 : \n arg2@", NULL); + insist(m->type == MX_EXPANSION); + check_string(m->filename, "macro6"); + insist(m->line == 1); + check_string(m->name, "macro"); + insist(m->nargs == 2); + insist(m->next == 0); + + insist(m->args[0]->type == MX_TEXT); + check_string(m->args[0]->filename, "macro6"); + insist(m->args[0]->line == 2); + check_string(m->args[0]->text, "arg1"); + insist(m->args[0]->next == 0); + + insist(m->args[1]->type == MX_TEXT); + check_string(m->args[1]->filename, "macro6"); + insist(m->args[1]->line == 3); + check_string(m->args[1]->text, "arg2"); + insist(m->args[1]->next == 0); + + + +} + +TEST(macros); + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ -- [mdw]