#include "printf.h"
#include "mime.h"
#include "unicode.h"
+#include "hash.h"
struct kvp *cgi_args;
char **columns;
};
+/* macros */
+struct cgi_macro {
+ int nargs;
+ char **args;
+ const char *value;
+};
+
+static hash *cgi_macros;
+
+/** @brief Parse of a template */
+struct cgi_element {
+ /** @brief Next element */
+ struct cgi_element *next;
+
+ /** @brief Element type */
+ int type;
+#define ELEMENT_TEXT 0
+#define ELEMENT_EXPANSION 1
+
+ /** @brief Line number at start of element */
+ int line;
+
+ /** @brief Plain text */
+ char *text;
+
+ /** @brief Expansion name */
+ char *name;
+
+ /** @brief Argument count */
+ int nargs;
+
+ /** @brief Argument values (NOT recursively expanded) */
+ char **args;
+};
+
#define RELIST(x) struct re *x, **x##_tail = &x
static int have_read_options;
static struct column *columns;
static void include_options(const char *name);
+static void cgi_expand_parsed(const char *name,
+ struct cgi_element *head,
+ const struct cgi_expansion *expansions,
+ size_t nexpansions,
+ cgi_sink *output,
+ void *u);
static void cgi_parse_get(void) {
const char *q;
cgi_expand_string(template, b, expansions, nexpansions, output, u);
}
-void cgi_expand_string(const char *name,
- const char *template,
- const struct cgi_expansion *expansions,
- size_t nexpansions,
- cgi_sink *output,
- void *u) {
- int braces, n, m, line = 1, sline;
- char *argname;
+/** @brief Return a linked list of the parse of @p template */
+static struct cgi_element *cgi_parse_string(const char *name,
+ const char *template) {
+ int braces, line = 1, sline;
const char *p;
struct vector v;
struct dynstr d;
- cgi_sink parameter_output;
-
+ struct cgi_element *head = 0, **tailp = &head, *e;
+
while(*template) {
if(*template != '@') {
- p = template;
- while(*p && *p != '@') {
- if(*p == '\n') ++line;
- ++p;
+ sline = line;
+ dynstr_init(&d);
+ /* Gather up text without any expansions in. */
+ while(*template && *template != '@') {
+ if(*template == '\n')
+ ++line;
+ dynstr_append(&d, *template++);
}
- output->sink->write(output->sink, template, p - template);
- template = p;
+ dynstr_terminate(&d);
+ e = xmalloc(sizeof *e);
+ e->next = 0;
+ e->line = sline;
+ e->type = ELEMENT_TEXT;
+ e->text = d.vec;
+ *tailp = e;
+ tailp = &e->next;
continue;
}
vector_init(&v);
braces = 0;
+ p = template;
++template;
sline = line;
while(*template != '@') {
+ /* Skip whitespace */
+ while(isspace((unsigned char)*template))
+ ++template;
dynstr_init(&d);
if(*template == '{') {
/* bracketed arg */
}
dynstr_append(&d, *template++);
}
- if(!*template) fatal(0, "%s:%d: unterminated expansion", name, sline);
+ if(!*template) fatal(0, "%s:%d: unterminated expansion '%.*s'",
+ name, sline, (int)(template - p), p);
++template;
- /* skip whitespace after closing bracket */
- while(isspace((unsigned char)*template))
- ++template;
+ if(isspace((unsigned char)*template)) {
+ /* We have @{...}<WHITESPACE><SOMETHING> */
+ for(p = template; isspace((unsigned char)*p); ++p)
+ ;
+ /* Now we are looking at <SOMETHING>. If it's "{" then that
+ * must be the next argument. Otherwise we infer that this
+ * is really the end of the expansion. */
+ if(*p != '{')
+ goto finished_expansion;
+ }
} else {
/* unbracketed arg */
- /* leading whitespace is not significant in unquoted args */
- while(isspace((unsigned char)*template))
- ++template;
while(*template
&& *template != '@' && *template != '{' && *template != ':') {
if(*template == '\n') ++line;
}
if(*template == ':')
++template;
- if(!*template) fatal(0, "%s:%d: unterminated expansion", name, sline);
+ if(!*template) fatal(0, "%s:%d: unterminated expansion '%.*s'",
+ name, sline, (int)(template - p), p);
/* trailing whitespace is not significant in unquoted args */
while(d.nvec && (isspace((unsigned char)d.vec[d.nvec - 1])))
--d.nvec;
vector_append(&v, d.vec);
}
++template;
+ finished_expansion:
vector_terminate(&v);
/* @@ terminates this file */
if(v.nvec == 0)
break;
- if((n = table_find(expansions,
- offsetof(struct cgi_expansion, name),
- sizeof (struct cgi_expansion),
- nexpansions,
- v.vec[0])) < 0)
- fatal(0, "%s:%d: unknown expansion '%s'", name, line, v.vec[0]);
- if(v.nvec - 1 < expansions[n].minargs)
- fatal(0, "%s:%d: insufficient arguments to @%s@ (min %d, got %d)",
- name, line, v.vec[0], expansions[n].minargs, v.nvec - 1);
- if(v.nvec - 1 > expansions[n].maxargs)
- fatal(0, "%s:%d: too many arguments to @%s@ (max %d, got %d)",
- name, line, v.vec[0], expansions[n].maxargs, v.nvec - 1);
- /* for ordinary expansions, recursively expand the arguments */
- if(!(expansions[n].flags & EXP_MAGIC)) {
- for(m = 1; m < v.nvec; ++m) {
- dynstr_init(&d);
- byte_xasprintf(&argname, "<%s:%d arg #%d>", name, sline, m);
- parameter_output.quote = 0;
- parameter_output.sink = sink_dynstr(&d);
- cgi_expand_string(argname, v.vec[m],
- expansions, nexpansions,
- ¶meter_output, u);
- dynstr_terminate(&d);
- v.vec[m] = d.vec;
+ e = xmalloc(sizeof *e);
+ e->next = 0;
+ e->line = sline;
+ e->type = ELEMENT_EXPANSION;
+ e->name = v.vec[0];
+ e->nargs = v.nvec - 1;
+ e->args = &v.vec[1];
+ *tailp = e;
+ tailp = &e->next;
+ }
+ return head;
+}
+
+void cgi_expand_string(const char *name,
+ const char *template,
+ const struct cgi_expansion *expansions,
+ size_t nexpansions,
+ cgi_sink *output,
+ void *u) {
+ cgi_expand_parsed(name, cgi_parse_string(name, template),
+ expansions, nexpansions, output, u);
+}
+
+/** @brief Expand a list of arguments in place */
+static void cgi_expand_all_args(const char *name,
+ int line,
+ const struct cgi_expansion *expansions,
+ size_t nexpansions,
+ void *u,
+ char **args,
+ int nargs) {
+ int n;
+ struct dynstr d;
+ char *argname;
+ cgi_sink parameter_output;
+
+ for(n = 0; n < nargs; ++n) {
+ dynstr_init(&d);
+ byte_xasprintf(&argname, "<%s:%d arg #%d>", name, line,n);
+ parameter_output.quote = 0;
+ parameter_output.sink = sink_dynstr(&d);
+ cgi_expand_string(argname, args[n],
+ expansions, nexpansions,
+ ¶meter_output, u);
+ dynstr_terminate(&d);
+ args[n] = d.vec;
+ }
+}
+
+
+/** @brief Substitute macro arguments in place */
+static void cgi_substitute_args(const char *name,
+ struct cgi_element *head,
+ const struct cgi_macro *macro,
+ char **values) {
+ struct cgi_element *e;
+ int n;
+
+ for(e = head; e; e = e->next) {
+ if(e->type != ELEMENT_EXPANSION)
+ continue;
+ /* See if this is an argument name */
+ for(n = 0; n < macro->nargs; ++n)
+ if(!strcmp(e->name, macro->args[n]))
+ break;
+ if(n < macro->nargs) {
+ /* It is! */
+ if(e->nargs != 0)
+ fatal(0, "%s:%d: macro argument (%s) cannot take parameters",
+ name, e->line, e->name);
+ /* Replace it with the argument text */
+ e->type = ELEMENT_TEXT;
+ e->text = values[n];
+ continue;
+ }
+ /* It's not a macro argument. We must recurse into its arguments to
+ * substitute the macro arguments. */
+ /* TODO */
+ /* In order to do this we must parse it and our callers must expect the
+ * parsed form. We're not ready for this yet... */
+ }
+}
+
+static void cgi_expand_parsed(const char *name,
+ struct cgi_element *head,
+ const struct cgi_expansion *expansions,
+ size_t nexpansions,
+ cgi_sink *output,
+ void *u) {
+ int n;
+ const struct cgi_macro *macro;
+ struct cgi_element *e, *macro_head;
+
+ for(e = head; e; e = e->next) {
+ switch(e->type) {
+ case ELEMENT_TEXT:
+ output->sink->write(output->sink, e->text, strlen(e->text));
+ break;
+ case ELEMENT_EXPANSION:
+ if((n = table_find(expansions,
+ offsetof(struct cgi_expansion, name),
+ sizeof (struct cgi_expansion),
+ nexpansions,
+ e->name)) >= 0) {
+ /* We found a built-in */
+ if(e->nargs < expansions[n].minargs)
+ fatal(0, "%s:%d: insufficient arguments to @%s@ (min %d, got %d)",
+ name, e->line, e->name, expansions[n].minargs, e->nargs);
+ if(e->nargs > expansions[n].maxargs)
+ fatal(0, "%s:%d: too many arguments to @%s@ (max %d, got %d)",
+ name, e->line, e->name, expansions[n].maxargs, e->nargs);
+ /* for ordinary expansions, recursively expand the arguments */
+ if(!(expansions[n].flags & EXP_MAGIC))
+ cgi_expand_all_args(name, e->line, expansions, nexpansions, u,
+ e->args, e->nargs);
+ expansions[n].handler(e->nargs, e->args, output, u);
+ } else if(cgi_macros && (macro = hash_find(cgi_macros, e->name))) {
+ /* We found a macro */
+ if(e->nargs != macro->nargs)
+ fatal(0, "%s:%d: wrong number of arguments to @%s@ (need %d, got %d)",
+ name, e->line, e->name, macro->nargs, e->nargs);
+ /* Expand arguments */
+ cgi_expand_all_args(name, e->line, expansions, nexpansions, u,
+ e->args, e->nargs);
+ /* Parse the macro value. Doing this every time isn't very efficient,
+ * but NB that we mess with the result of the parse, so for the time
+ * being we do need to do it. */
+ macro_head = cgi_parse_string(e->name, macro->value);
+ /* Substitute in argument values */
+ cgi_substitute_args(name, macro_head, macro, e->args);
+ /* Expand the result */
+ cgi_expand_parsed(e->name,
+ macro_head,
+ expansions,
+ nexpansions,
+ output,
+ u);
+ } else {
+ /* Totally undefined */
+ fatal(0, "%s:%d: unknown expansion '%s'", name, e->line, e->name);
}
+ break;
}
- expansions[n].handler(v.nvec - 1, v.vec + 1, output, u);
}
}
}
}
+void cgi_define(const char *name,
+ int nargs,
+ char **args,
+ const char *value) {
+ struct cgi_macro m;
+
+ if(!cgi_macros)
+ cgi_macros = hash_new(sizeof(struct cgi_macro));
+ m.nargs = nargs;
+ m.args = args;
+ m.value = value;
+ hash_add(cgi_macros, name, &m, HASH_INSERT_OR_REPLACE);
+}
+
/*
Local Variables:
c-basic-offset:2