chiark / gitweb /
Macro expansion (untested). Tests for most builtin expansions.
[disorder] / lib / macros.c
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 */
 }
 
 /*