From b36be3a1a6a4b3374669cadf3455b8f75912f089 Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Sun, 4 May 2008 11:58:21 +0100 Subject: [PATCH] Macro expansion (untested). Tests for most builtin expansions. Organization: Straylight/Edgeware From: Richard Kettlewell --- lib/macros-builtin.c | 228 +++++++++++++++++++++++++++++------ lib/macros.c | 280 ++++++++++++++++++++++++++++++++++++------- lib/macros.h | 9 +- lib/t-macros.c | 89 ++++++++++++++ 4 files changed, 526 insertions(+), 80 deletions(-) diff --git a/lib/macros-builtin.c b/lib/macros-builtin.c index 63aa930..53b2712 100644 --- a/lib/macros-builtin.c +++ b/lib/macros-builtin.c @@ -21,9 +21,8 @@ /** @file lib/macros-builtin.c * @brief Built-in expansions * - * This is a grab-bag of non-domain-specific expansions. Note that - * documentation will be generated from the comments at the head of - * each function. + * This is a grab-bag of non-domain-specific expansions. Documentation will be + * generated from the comments at the head of each function. */ #include @@ -34,13 +33,22 @@ #include #include #include +#include +#include +#include "mem.h" #include "macros.h" #include "sink.h" #include "syscalls.h" #include "log.h" #include "wstat.h" #include "kvp.h" +#include "hash.h" +#include "split.h" +#include "printf.h" +#include "vector.h" + +static struct vector include_path; /** @brief Return 1 if @p s is 'true' else 0 */ int mx_str2bool(const char *s) { @@ -52,16 +60,87 @@ const char *mx_bool2str(int n) { return n ? "true" : "false"; } -/* @include{TEMPLATE}@ +/** @brief Write a boolean result */ +static int mx_bool_result(struct sink *output, int result) { + if(sink_writes(output, mx_bool2str(result)) < 0) + return -1; + else + return 0; +} + +/* @include{TEMPLATE} + * + * Includes TEMPLATE. + * + * TEMPLATE can be an absolute filename starting with a '/'; only the file with + * exactly this name will be included. + * + * Alternatively it can be a relative filename, not starting with a '/'. In + * this case the file will be searched for in the include path. When searching + * paths, unreadable files are treated as if they do not exist (rather than + * matching then producing an error). * - * Includes TEMPLATE as if its text were substituted for the @include - * expansion. TODO + * If the name chosen ends ".tmpl" then the file will be expanded as a + * template. Anything else is included byte-for-byte without further + * modification. + * + * Only regular files are allowed (no devices, sockets or name pipes). */ static int exp_include(int attribute((unused)) nargs, - char attribute((unused)) **args, - struct sink attribute((unused)) *output, - void attribute((unused)) *u) { - assert(!"TODO implement search path"); + char **args, + struct sink *output, + void *u) { + const char *name = args[0]; + char *path; + int fd, n; + char buffer[4096]; + struct stat sb; + + if(name[0] == '/') { + if(access(name, O_RDONLY) < 0) { + error(errno, "cannot read template %s", name); + if(sink_printf(output, "[[cannot open template '%s']]", name) < 0) + return -1; + return -3; + } + path = xstrdup(name); + } else { + int n; + + /* Search the include path */ + for(n = 0; n < include_path.nvec; ++n) { + byte_xasprintf(&path, "%s/%s", include_path.vec[n], name); + if(access(path, O_RDONLY) == 0) + break; + } + if(n >= include_path.nvec) { + error(0, "cannot find template '%s'", name); + if(sink_printf(output, "[[cannot find template '%s']]", name) < 0) + return -1; + return -3; + } + } + /* If it's a template expand it */ + if(strlen(path) >= 5 && !strncmp(path + strlen(path) - 5, ".tmpl", 5)) + return mx_expand_file(path, output, u); + /* Read the raw file. As with mx_expand_file() we insist that the file is a + * regular file. */ + 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); + while((n = read(fd, buffer, sizeof buffer)) > 0) { + if(sink_write(output, buffer, n) < 0) { + xclose(fd); + return -1; + } + } + if(n < 0) + fatal(errno, "error reading %s", path); + xclose(fd); + return 0; } /* @include{COMMAND} @@ -125,7 +204,7 @@ static int exp_if(int nargs, char *s; int rc; - if((rc = mx_expandstr(args[0], &s, u))) + if((rc = mx_expandstr(args[0], &s, u, "argument #0 (CONDITION)"))) return rc; if(mx_str2bool(s)) return mx_expand(args[1], output, u); @@ -147,20 +226,18 @@ static int exp_and(int nargs, struct sink *output, void *u) { int n, result = 1, rc; - char *s; + char *s, *argname; for(n = 0; n < nargs; ++n) { - if((rc = mx_expandstr(args[n], &s, u))) + byte_xasprintf(&argname, "argument #%d", n); + if((rc = mx_expandstr(args[n], &s, u, argname))) return rc; if(!mx_str2bool(s)) { result = 0; break; } } - if(sink_writes(output, mx_bool2str(result)) < 0) - return -1; - else - return 0; + return mx_bool_result(output, result); } /* @or{BRANCH}{BRANCH}... @@ -175,20 +252,18 @@ static int exp_or(int nargs, struct sink *output, void *u) { int n, result = 0, rc; - char *s; + char *s, *argname; for(n = 0; n < nargs; ++n) { - if((rc = mx_expandstr(args[n], &s, u))) + byte_xasprintf(&argname, "argument #%d", n); + if((rc = mx_expandstr(args[n], &s, u, argname))) return rc; if(mx_str2bool(s)) { result = 1; break; } } - if(sink_writes(output, mx_bool2str(result)) < 0) - return -1; - else - return 0; + return mx_bool_result(output, result); } /* @not{CONDITION} @@ -199,10 +274,7 @@ static int exp_not(int attribute((unused)) nargs, char **args, struct sink *output, void attribute((unused)) *u) { - if(sink_writes(output, mx_bool2str(!mx_str2bool(args[0]))) < 0) - return -1; - else - return 0; + return mx_bool_result(output, !mx_str2bool(args[0])); } /* @#{...} @@ -232,19 +304,103 @@ static int exp_urlquote(int attribute((unused)) nargs, return 0; } +/* @eq{S1}{S2}... + * + * Expands to "true" if all the arguments are identical, otherwise to "false" + * (i.e. if any pair of arguments differs). + * + * If there are no arguments then expands to "true". Evaluates all arguments + * (with their side effects) even if that's not strictly necessary to discover + * the result. + */ +static int exp_eq(int nargs, + char **args, + struct sink *output, + void attribute((unused)) *u) { + int n, result = 1; + + for(n = 1; n < nargs; ++n) { + if(strcmp(args[n], args[0])) { + result = 0; + break; + } + } + return mx_bool_result(output, result); +} + +/* @ne{S1}{S2}... + * + * Expands to "true" if all of the arguments differ from one another, otherwise + * to "false" (i.e. if any value appears more than once). + * + * If there are no arguments then expands to "true". Evaluates all arguments + * (with their side effects) even if that's not strictly necessary to discover + * the result. + */ +static int exp_ne(int nargs, + char **args, + struct sink *output, + void attribute((unused))*u) { + hash *h = hash_new(sizeof (char *)); + int n, result = 1; + + for(n = 0; n < nargs; ++n) + if(hash_add(h, args[n], "", HASH_INSERT)) { + result = 0; + break; + } + return mx_bool_result(output, result); +} + +/* @discard{...} + * + * Expands to nothing. Unlike the comment expansion @#{...}, side effects of + * arguments are not suppressed. So this can be used to surround a collection + * of macro definitions with whitespace, free text commentary, etc. + */ +static int exp_discard(int attribute((unused)) nargs, + char attribute((unused)) **args, + struct sink attribute((unused)) *output, + void attribute((unused)) *u) { + return 0; +} + +/* @define{NAME}{ARG1 ARG2...}{DEFINITION} + * + * Define a macro. The macro will be called NAME and will act like an + * expansion. When it is expanded, the expansion is replaced by DEFINITION, + * with each occurence of @ARG1@ etc replaced by the parameters to the + * expansion. + */ +static int exp_define(int attribute((unused)) nargs, + const struct mx_node **args, + struct sink attribute((unused)) *output, + void attribute((unused)) *u) { + char **as, *name, *argnames; + int rc, nas; + + if((rc = mx_expandstr(args[0], &name, u, "argument #0 (NAME)"))) + return rc; + if((rc = mx_expandstr(args[1], &argnames, u, "argument #1 (ARGS)"))) + return rc; + as = split(argnames, &nas, 0, 0, 0); + mx_register_macro(name, nas, as, args[2]); + return 0; +} + void mx_register_builtin(void) { + mx_register_magic("#", 0, INT_MAX, exp_comment); + mx_register_magic("and", 0, INT_MAX, exp_and); + mx_register_magic("define", 3, 3, exp_define); + mx_register_magic("if", 2, 3, exp_if); + mx_register_magic("or", 0, INT_MAX, exp_or); + mx_register("discard", 0, INT_MAX, exp_discard); + mx_register("eq", 0, INT_MAX, exp_eq); mx_register("include", 1, 1, exp_include); - mx_register("shell", 1, 1, exp_shell); - mx_magic_register("if", 2, 3, exp_if); - mx_magic_register("and", 0, INT_MAX, exp_and); - mx_magic_register("or", 0, INT_MAX, exp_or); + mx_register("ne", 0, INT_MAX, exp_ne); mx_register("not", 1, 1, exp_not); - mx_magic_register("#", 0, INT_MAX, exp_comment); + mx_register("shell", 1, 1, exp_shell); mx_register("urlquote", 1, 1, exp_urlquote); - /* TODO: eq */ - /* TODO: ne */ - /* TODO: define */ - /* TODO: discard */ } /* diff --git a/lib/macros.c b/lib/macros.c index 31d765e..6257c92 100644 --- a/lib/macros.c +++ b/lib/macros.c @@ -41,6 +41,7 @@ #include "hash.h" #include "sink.h" #include "syscalls.h" +#include "printf.h" VECTOR_TYPE(mx_node_vector, const struct mx_node *, xrealloc); @@ -52,18 +53,58 @@ struct expansion { /** @brief Maximum permitted arguments */ int max; - /** @brief Flags */ + /** @brief Flags + * + * See: + * - @ref EXP_SIMPLE + * - @ref EXP_MAGIC + * - @ref EXP_MACRO + * - @ref EXP_TYPE_MASK + */ unsigned flags; - /** @brief Callback (cast to appropriate type) */ + /** @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 parsed templates, not strings */ +/** @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 @@ -268,11 +309,13 @@ char *mx_dump(const struct mx_node *m) { /* Expansion registration --------------------------------------------------- */ -static void mx__register(unsigned flags, - const char *name, - int min, - int max, - void (*callback)()) { +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) @@ -280,8 +323,12 @@ static void mx__register(unsigned flags, e->min = min; e->max = max; e->flags = flags; + e->args = args; e->callback = callback; - hash_add(expansions, name, &e, HASH_INSERT_OR_REPLACE); + 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 @@ -294,7 +341,7 @@ void mx_register(const char *name, int min, int max, mx_simple_callback *callback) { - mx__register(0, name, min, max, (void (*)())callback); + mx__register(EXP_SIMPLE, name, min, max, 0, (void (*)())callback, 0); } /** @brief Register a magic expansion rule @@ -303,11 +350,34 @@ void mx_register(const char *name, * @param max Maximum number of arguments * @param callback Callback to write output */ -void mx_magic_register(const char *name, +void mx_register_magic(const char *name, int min, int max, mx_magic_callback *callback) { - mx__register(EXP_MAGIC, name, min, max, (void (*)())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 ---------------------------------------------------------------- */ @@ -316,11 +386,17 @@ void mx_magic_register(const char *name, * @param m Where to start * @param output Where to send output * @param u User data - * @return 0 on success, non-0 on error∑ + * @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 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. + * 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, @@ -336,6 +412,7 @@ int mx_expand(const struct mx_node *m, 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); @@ -351,39 +428,64 @@ int mx_expand(const struct mx_node *m, 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, + } 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))) - return rc; + 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, output, u); + 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 @@ -391,14 +493,19 @@ int mx_expand(const struct mx_node *m, */ int mx_expandstr(const struct mx_node *m, char **sp, - void *u) { + 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; } @@ -413,10 +520,11 @@ int mx_expandstr(const struct mx_node *m, int mx_expand_file(const char *path, struct sink *output, void *u) { - int fd, n; + 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); @@ -436,9 +544,97 @@ int mx_expand_file(const char *path, fatal(errno, "error reading %s", path); } xclose(fd); - return mx_expand(mx_parse(path, 1, b, b + sb.st_size), - output, - u); + 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 */ } /* diff --git a/lib/macros.h b/lib/macros.h index aa20f2d..8a557fd 100644 --- a/lib/macros.h +++ b/lib/macros.h @@ -95,10 +95,14 @@ void mx_register(const char *name, int min, int max, mx_simple_callback *callback); -void mx_magic_register(const char *name, +void mx_register_magic(const char *name, int min, int max, mx_magic_callback *callback); +int mx_register_macro(const char *name, + int nargs, + char **args, + const struct mx_node *definition); void mx_register_builtin(void); @@ -110,7 +114,8 @@ int mx_expand(const struct mx_node *m, void *u); int mx_expandstr(const struct mx_node *m, char **sp, - void *u); + void *u, + const char *what); int mx_str2bool(const char *s); const char *mx_bool2str(int n); diff --git a/lib/t-macros.c b/lib/t-macros.c index cff5027..3330d36 100644 --- a/lib/t-macros.c +++ b/lib/t-macros.c @@ -25,7 +25,10 @@ static void test_macros(void) { #define L1 "this is just some\n" #define L2 "plain text\n" static const char plain[] = L1 L2; + char *s; + /* Plain text ------------------------------------------------------------- */ + /* As simple as it gets */ m = mx_parse("plaintext1", 1, "", NULL); insist(m == 0); @@ -48,6 +51,8 @@ static void test_macros(void) { insist(m->next == 0); check_string(mx_dump(m), L1); + /* Simple macro parsing --------------------------------------------------- */ + /* The simplest possible expansion */ m = mx_parse("macro1", 1, "@macro@", NULL); check_integer(m->type, MX_EXPANSION); @@ -167,6 +172,90 @@ static void test_macros(void) { * preserved. */ m = mx_parse("macro12", 1, "@macro {@macro2 {arg1} {arg2} }\n", NULL); check_string(mx_dump(m), "@macro{@macro2{arg1}{arg2}@ }@\n"); + + /* Simple expansions ------------------------------------------------------ */ + + mx_register_builtin(); + +#define check_macro(NAME, INPUT, OUTPUT) do { \ + m = mx_parse(NAME, 1, INPUT, NULL); \ + check_integer(mx_expandstr(m, &s, 0/*u*/, NAME), 0); \ + if(s && strcmp(s, OUTPUT)) { \ + fprintf(stderr, "%s:%d: test %s\n" \ + " INPUT: %s\n" \ + " EXPECTED: '%s'\n" \ + " GOT: '%s'\n", \ + __FILE__, __LINE__, NAME, INPUT, OUTPUT, s); \ + count_error(); \ + } \ +} while(0) + + check_macro("empty", "", ""); + check_macro("plain", plain, plain); + + check_macro("if1", "@if{true}{yes}{no}", "yes"); + check_macro("if2", "@if{true}{yes}", "yes"); + check_macro("if3", "@if{false}{yes}{no}", "no"); + check_macro("if4", "@if{false}{yes}", ""); + check_macro("if5", "@if{ true}{yes}", ""); + + check_macro("and1", "@and", "true"); + check_macro("and2", "@and{true}", "true"); + check_macro("and3", "@and{false}", "false"); + check_macro("and4", "@and{true}{true}", "true"); + check_macro("and5", "@and{false}{true}", "false"); + check_macro("and6", "@and{true}{false}", "false"); + check_macro("and7", "@and{false}{false}", "false"); + + check_macro("or1", "@or", "false"); + check_macro("or2", "@or{true}", "true"); + check_macro("or2", "@or{false}", "false"); + check_macro("or3", "@or{true}{true}", "true"); + check_macro("or4", "@or{false}{true}", "true"); + check_macro("or5", "@or{true}{false}", "true"); + check_macro("or7", "@or{false}{false}", "false"); + + check_macro("not1", "@not{true}", "false"); + check_macro("not2", "@not{false}", "true"); + check_macro("not3", "@not{wibble}", "true"); + + check_macro("comment1", "@#{wibble}", ""); + check_macro("comment2", "@#{comment with a\nnewline in}", ""); + + check_macro("discard1", "@discard{wibble}", ""); + check_macro("discard2", "@discard{comment with a\nnewline in}", ""); + + check_macro("eq1", "@eq", "true"); + check_macro("eq2", "@eq{}", "true"); + check_macro("eq3", "@eq{a}", "true"); + check_macro("eq4", "@eq{a}{a}", "true"); + check_macro("eq5", "@eq{a}{a}{a}", "true"); + check_macro("eq7", "@eq{a}{b}", "false"); + check_macro("eq8", "@eq{a}{b}{a}", "false"); + check_macro("eq9", "@eq{a}{a}{b}", "false"); + check_macro("eq10", "@eq{b}{a}{a}", "false"); + + check_macro("ne1", "@ne", "true"); + check_macro("ne2", "@ne{}", "true"); + check_macro("ne3", "@ne{a}", "true"); + check_macro("ne4", "@ne{a}{a}", "false"); + check_macro("ne5", "@ne{a}{a}{a}", "false"); + check_macro("ne7", "@ne{a}{b}", "true"); + check_macro("ne8", "@ne{a}{b}{a}", "false"); + check_macro("ne9", "@ne{a}{a}{b}", "false"); + check_macro("ne10", "@ne{b}{a}{a}", "false"); + check_macro("ne11", "@ne{a}{b}{c}", "true"); + + check_macro("sh1", "@shell{true}", ""); + check_macro("sh2", "@shell{echo spong}", "spong\n"); + fprintf(stderr, "expxect error message from macro expander:\n"); + check_macro("sh3", "@shell{echo spong;exit 3}", "spong\n"); + + check_macro("url1", "@urlquote{unreserved}", "unreserved"); + check_macro("url2", "@urlquote{has space}", "has%20space"); + check_macro("url3", "@urlquote{\xc0\xc1}", "%c0%c1"); + + } TEST(macros); -- [mdw]