chiark / gitweb /
Macro expansion (untested). Tests for most builtin expansions.
authorRichard Kettlewell <rjk@greenend.org.uk>
Sun, 4 May 2008 10:58:21 +0000 (11:58 +0100)
committerRichard Kettlewell <rjk@greenend.org.uk>
Sun, 4 May 2008 10:58:21 +0000 (11:58 +0100)
lib/macros-builtin.c
lib/macros.c
lib/macros.h
lib/t-macros.c

index 63aa9309553436025535cbf5e28549c4e64cbeba..53b2712f3f44eb6d5c29f51a55976423ba9a6fb9 100644 (file)
@@ -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 <config.h>
 #include <errno.h>
 #include <assert.h>
 #include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
 
+#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 */
 }
 
 /*
index 31d765e88cd298c187bc8a021884e5782678927f..6257c92548da0f9eedb85c2b4657380a7900b83b 100644 (file)
@@ -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 */
 }
 
 /*
index aa20f2db179f091e67d38874f1e708155e29e49b..8a557fd1422a26da847c4c5a621dddc66aa2e0a7 100644 (file)
@@ -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);
index cff502742718e8a98280579cdc5422257f5096dd..3330d36e0dba63c29ed117a97037e9950f2edfae 100644 (file)
@@ -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);