chiark / gitweb /
Main template expander and some built-in expansions. All untested.
[disorder] / lib / macros.c
index e617203..31d765e 100644 (file)
 #include <string.h>
 #include <ctype.h>
 #include <assert.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
 
 #include "macros.h"
 #include "mem.h"
 #include "vector.h"
 #include "log.h"
+#include "hash.h"
+#include "sink.h"
+#include "syscalls.h"
 
 VECTOR_TYPE(mx_node_vector, const struct mx_node *, xrealloc);
 
+/** @brief Definition of an expansion */
+struct expansion {
+  /** @brief Minimum permitted arguments */
+  int min;
+
+  /** @brief Maximum permitted arguments */
+  int max;
+
+  /** @brief Flags */
+  unsigned flags;
+
+  /** @brief Callback (cast to appropriate type) */
+  void (*callback)();
+};
+
+/** @brief Expansion takes parsed templates, not strings */
+#define EXP_MAGIC 0x0001
+
+static hash *expansions;
+
+/* Parsing ------------------------------------------------------------------ */
+
 /** @brief Parse a template
  * @param filename Input filename (for diagnostics)
  * @param line Line number (use 1 on initial call)
@@ -236,6 +266,181 @@ char *mx_dump(const struct mx_node *m) {
   return d->vec;
 }
 
+/* Expansion registration --------------------------------------------------- */
+
+static void mx__register(unsigned flags,
+                         const char *name,
+                         int min,
+                         int max,
+                         void (*callback)()) {
+  struct expansion e[1];
+
+  if(!expansions)
+    expansions = hash_new(sizeof(struct expansion));
+  e->min = min;
+  e->max = max;
+  e->flags = flags;
+  e->callback = callback;
+  hash_add(expansions, name, &e, HASH_INSERT_OR_REPLACE);
+}
+
+/** @brief Register a simple expansion rule
+ * @param name Name
+ * @param min Minimum number of arguments
+ * @param max Maximum number of arguments
+ * @param callback Callback to write output
+ */
+void mx_register(const char *name,
+                 int min,
+                 int max,
+                 mx_simple_callback *callback) {
+  mx__register(0,  name, min, max, (void (*)())callback);
+}
+
+/** @brief Register a magic expansion rule
+ * @param name Name
+ * @param min Minimum number of arguments
+ * @param max Maximum number of arguments
+ * @param callback Callback to write output
+ */
+void mx_magic_register(const char *name,
+                       int min,
+                       int max,
+                       mx_magic_callback *callback) {
+  mx__register(EXP_MAGIC, name, min, max, (void (*)())callback);
+}
+
+/* Expansion ---------------------------------------------------------------- */
+
+/** @brief Expand a template
+ * @param m Where to start
+ * @param output Where to send output
+ * @param u User data
+ * @return 0 on success, non-0 on error∑
+ *
+ * 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.
+ */
+int mx_expand(const struct mx_node *m,
+              struct sink *output,
+              void *u) {
+  const struct expansion *e;
+  int rc;
+
+  if(!m)
+    return 0;
+  switch(m->type) {
+  case MX_TEXT:
+    if(sink_writes(output, m->text) < 0)
+      return -1;
+    break;
+  case MX_EXPANSION:
+    if(!(e = hash_find(expansions, m->name))) {
+      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);
+      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",
+            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,
+                                                   args,
+                                                   output,
+                                                   u)))
+        return rc;
+    }
+    break;
+  default:
+    assert(!"invalid m->type");
+  }
+  return mx_expand(m, 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
+ * @return 0 on success, non-0 on error
+ *
+ * Same return conventions as mx_expand().  This wrapper is slightly more
+ * convenient to use from 'magic' expansions.
+ */
+int mx_expandstr(const struct mx_node *m,
+                 char **sp,
+                 void *u) {
+  struct dynstr d[1];
+  int rc;
+
+  if(!(rc = mx_expand(m, sink_dynstr(d), u))) {
+    dynstr_terminate(d);
+    *sp = d->vec;
+  }
+  return rc;
+}
+
+/** @brief Expand a template file
+ * @param path Filename
+ * @param output Where to send output
+ * @param u User data
+ * @return 0 on success, non-0 on error
+ *
+ * Same return conventions as mx_expand().
+ */
+int mx_expand_file(const char *path,
+                   struct sink *output,
+                   void *u) {
+  int fd, n;
+  struct stat sb;
+  char *b;
+  off_t sofar;
+
+  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);
+  sofar = 0;
+  b = xmalloc_noptr(sb.st_size);
+  while(sofar < sb.st_size) {
+    n = read(fd, b + sofar, sb.st_size - sofar);
+    if(n > 0)
+      sofar += n;
+    else if(n == 0)
+      fatal(0, "unexpected EOF reading %s", path);
+    else if(errno != EINTR)
+      fatal(errno, "error reading %s", path);
+  }
+  xclose(fd);
+  return mx_expand(mx_parse(path, 1, b, b + sb.st_size),
+                   output,
+                   u);
+}
+
 /*
 Local Variables:
 c-basic-offset:2