+/* -*-c-*-
+ *
+ * Common definitions for `runlisp'
+ *
+ * (c) 2020 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Runlisp, a tool for invoking Common Lisp scripts.
+ *
+ * Runlisp is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Runlisp is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Runlisp. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <assert.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unistd.h>
+
+#include "lib.h"
+
+/*----- Miscellany --------------------------------------------------------*/
+
+int str_lt(const char *a, size_t an, const char *b, size_t bn)
+{
+ if (an < bn) return (MEMCMP(a, <=, b, an));
+ else return (MEMCMP(a, <, b, bn));
+}
+
+/*----- Diagnostic utilities ----------------------------------------------*/
+
+const char *progname = "???";
+
+void set_progname(const char *prog)
+{
+ const char *p;
+
+ p = strrchr(prog, '/');
+ progname = p ? p + 1 : progname;
+}
+
+void vmoan(const char *msg, va_list ap)
+{
+ fprintf(stderr, "%s: ", progname);
+ vfprintf(stderr, msg, ap);
+ fputc('\n', stderr);
+}
+
+void moan(const char *msg, ...)
+ { va_list ap; va_start(ap, msg); vmoan(msg, ap); va_end(ap); }
+
+void lose(const char *msg, ...)
+ { va_list ap; va_start(ap, msg); vmoan(msg, ap); va_end(ap); exit(127); }
+
+/*----- Memory allocation -------------------------------------------------*/
+
+void *xmalloc(size_t n)
+{
+ void *p;
+
+ if (!n) return (0);
+ p = malloc(n); if (!p) lose("failed to allocate memory");
+ return (p);
+}
+
+void *xrealloc(void *p, size_t n)
+{
+ if (!n) { free(p); return (0); }
+ else if (!p) return (xmalloc(n));
+ p = realloc(p, n); if (!p) lose("failed to allocate memory");
+ return (p);
+}
+
+char *xstrndup(const char *p, size_t n)
+{
+ char *q = xmalloc(n + 1);
+
+ memcpy(q, p, n); q[n] = 0;
+ return (q);
+}
+
+char *xstrdup(const char *p) { return (xstrndup(p, strlen(p))); }
+
+/*----- Dynamic strings ---------------------------------------------------*/
+
+void dstr_init(struct dstr *d) { d->p = 0; d->len = d->sz = 0; }
+
+void dstr_reset(struct dstr *d) { d->len = 0; }
+
+void dstr_ensure(struct dstr *d, size_t n)
+{
+ size_t need = d->len + n, newsz;
+
+ if (need <= d->sz) return;
+ newsz = d->sz ? 2*d->sz : 16;
+ while (newsz < need) newsz *= 2;
+ d->p = xrealloc(d->p, newsz); d->sz = newsz;
+}
+
+void dstr_release(struct dstr *d) { free(d->p); }
+
+void dstr_putm(struct dstr *d, const void *p, size_t n)
+ { dstr_ensure(d, n); memcpy(d->p + d->len, p, n); d->len += n; }
+
+void dstr_puts(struct dstr *d, const char *p)
+{
+ size_t n = strlen(p);
+
+ dstr_ensure(d, n + 1);
+ memcpy(d->p + d->len, p, n + 1);
+ d->len += n;
+}
+
+void dstr_putc(struct dstr *d, int ch)
+ { dstr_ensure(d, 1); d->p[d->len++] = ch; }
+
+void dstr_putcn(struct dstr *d, int ch, size_t n)
+ { dstr_ensure(d, n); memset(d->p + d->len, ch, n); d->len += n; }
+
+void dstr_putz(struct dstr *d)
+ { dstr_ensure(d, 1); d->p[d->len] = 0; }
+
+void dstr_vputf(struct dstr *d, const char *p, va_list ap)
+{
+ va_list ap2;
+ size_t r;
+ int n;
+
+ r = d->sz - d->len;
+ va_copy(ap2, ap);
+ n = vsnprintf(d->p + d->len, r, p, ap2); assert(n >= 0);
+ va_end(ap2);
+ if (n >= r) {
+ dstr_ensure(d, n + 1); r = d->sz - d->len;
+ n = vsnprintf(d->p + d->len, r, p, ap); assert(n >= 0); assert(n < r);
+ }
+ d->len += n;
+}
+
+PRINTF_LIKE(2, 3) void dstr_putf(struct dstr *d, const char *p, ...)
+ { va_list ap; va_start(ap, p); dstr_vputf(d, p, ap); va_end(ap); }
+
+int dstr_readline(struct dstr *d, FILE *fp)
+{
+ size_t n;
+ int any = 0;
+
+ for (;;) {
+ dstr_ensure(d, 2);
+ if (!fgets(d->p + d->len, d->sz - d->len, fp)) break;
+ n = strlen(d->p + d->len); assert(n > 0); any = 1;
+ d->len += n;
+ if (d->p[d->len - 1] == '\n') { d->p[--d->len] = 0; break; }
+ }
+
+ if (!any) return (-1);
+ else return (0);
+}
+
+/*----- Dynamic vectors of strings ----------------------------------------*/
+
+void argv_init(struct argv *av)
+ { av->v = 0; av->o = av->n = av->sz = 0; }
+
+void argv_reset(struct argv *av) { av->n = 0; }
+
+void argv_ensure(struct argv *av, size_t n)
+{
+ size_t need = av->n + av->o + n, newsz;
+
+ if (need <= av->sz) return;
+ newsz = av->sz ? 2*av->sz : 8;
+ while (newsz < need) newsz *= 2;
+ av->v =
+ (const char **)xrealloc(av->v - av->o, newsz*sizeof(const char *)) +
+ av->o;
+ av->sz = newsz;
+}
+
+void argv_ensure_offset(struct argv *av, size_t n)
+{
+ size_t newoff;
+
+ /* Stupid version. We won't, in practice, be prepending lots of stuff, so
+ * avoid the extra bookkeeping involved in trying to make a double-ended
+ * extendable array asymptotically efficient.
+ */
+ if (av->o >= n) return;
+ newoff = 16;
+ while (newoff < n) newoff *= 2;
+ argv_ensure(av, newoff - av->o);
+ memmove(av->v + newoff - av->o, av->v, av->n*sizeof(const char *));
+ av->v += newoff - av->o; av->o = newoff;
+}
+
+void argv_release(struct argv *av) { free(av->v - av->o); }
+
+void argv_append(struct argv *av, const char *p)
+ { argv_ensure(av, 1); av->v[av->n++] = p; }
+
+void argv_appendz(struct argv *av)
+ { argv_ensure(av, 1); av->v[av->n] = 0; }
+
+void argv_appendn(struct argv *av, const char *const *v, size_t n)
+{
+ argv_ensure(av, n);
+ memcpy(av->v + av->n, v, n*sizeof(const char *));
+ av->n += n;
+}
+
+void argv_appendav(struct argv *av, const struct argv *bv)
+ { argv_appendn(av, bv->v, bv->n); }
+
+void argv_appendv(struct argv *av, va_list ap)
+{
+ const char *p;
+
+ for (;;)
+ { p = va_arg(ap, const char *); if (!p) break; argv_append(av, p); }
+}
+
+void argv_appendl(struct argv *av, ...)
+ { va_list ap; va_start(ap, av); argv_appendv(av, ap); va_end(ap); }
+
+void argv_prepend(struct argv *av, const char *p)
+ { argv_ensure_offset(av, 1); *--av->v = p; av->o--; av->n++; }
+
+void argv_prependn(struct argv *av, const char *const *v, size_t n)
+{
+ argv_ensure_offset(av, n);
+ av->o -= n; av->v -= n; av->n += n;
+ memcpy(av->v, v, n*sizeof(const char *));
+}
+
+void argv_prependav(struct argv *av, const struct argv *bv)
+ { argv_prependn(av, bv->v, bv->n); }
+
+void argv_prependv(struct argv *av, va_list ap)
+{
+ const char *p, **v;
+ size_t n = 0;
+
+ for (;;) {
+ p = va_arg(ap, const char *); if (!p) break;
+ argv_prepend(av, p); n++;
+ }
+ v = av->v;
+ while (n >= 2) {
+ p = v[0]; v[0] = v[n - 1]; v[n - 1] = p;
+ v++; n -= 2;
+ }
+}
+
+void argv_prependl(struct argv *av, ...)
+ { va_list ap; va_start(ap, av); argv_prependv(av, ap); va_end(ap); }
+
+/*----- Treaps ------------------------------------------------------------*/
+
+void treap_init(struct treap *t) { t->root = 0; }
+
+void *treap_lookup(const struct treap *t, const char *k, size_t kn)
+{
+ struct treap_node *n = t->root, *candidate = 0;
+
+ while (n) {
+ if (str_lt(k, kn, n->k, n->kn)) n = n->left;
+ else { candidate = n; n = n->right; }
+ }
+ if (!candidate || str_lt(candidate->k, candidate->kn, k, kn)) return (0);
+ return (candidate);
+}
+
+void *treap_probe(struct treap *t, const char *k, size_t kn,
+ struct treap_path *p)
+{
+ struct treap_node **nn = &t->root, *candidate = 0;
+ unsigned i = 0;
+
+ for (;;) {
+ assert(i < TREAP_PATHMAX); p->path[i++] = nn;
+ if (!*nn) break;
+ if (str_lt(k, kn, (*nn)->k, (*nn)->kn)) nn = &(*nn)->left;
+ else { candidate = *nn; nn = &(*nn)->right; }
+ }
+ p->nsteps = i;
+ if (!candidate || str_lt(candidate->k, candidate->kn, k, kn)) return (0);
+ return (candidate);
+}
+
+void treap_insert(struct treap *t, const struct treap_path *p,
+ struct treap_node *n, const char *k, size_t kn)
+{
+ size_t i = p->nsteps;
+ struct treap_node **nn, **uu, *u;
+ unsigned wt;
+
+ n->k = xstrndup(k, kn); n->kn = kn;
+ n->wt = wt = rand(); n->left = n->right = 0;
+ assert(i); nn = p->path[--i];
+ while (i--) {
+ uu = p->path[i]; u = *uu;
+ if (wt <= u->wt) break;
+ if (nn == &u->left) { u->left = n->right; n->right = u; }
+ else { u->right = n->left; n->left = u; }
+ nn = uu;
+ }
+ *nn = n;
+}
+
+void *treap_remove(struct treap *t, const char *k, size_t kn)
+{
+ struct treap_node **nn = &t->root, **candidate = 0, *n, *l, *r;
+
+ while (*nn) {
+ if (str_lt(k, kn, (*nn)->k, (*nn)->kn)) nn = &(*nn)->left;
+ else { candidate = nn; nn = &(*nn)->right; }
+ }
+ if (!candidate || str_lt((*candidate)->k, (*candidate)->kn, k, kn))
+ return (0);
+
+ n = *candidate; l = n->left; r = n->right;
+ for (;;) {
+ if (l && (!r || l->wt > r->wt)) { nn = &l->right; l = l->right; }
+ else if (r) { nn = &r->left; r = r->left; }
+ else break;
+ }
+ *nn = 0;
+ free(n->k);
+ return (n);
+}
+
+void treap_start_iter(struct treap *t, struct treap_iter *i)
+{
+ struct treap_node *n = t->root;
+ unsigned sp = 0;
+
+ while (n) {
+ assert(sp < TREAP_PATHMAX);
+ i->stack[sp++] = n; n = n->left;
+ }
+ i->sp = sp;
+}
+
+void *treap_next(struct treap_iter *i)
+{
+ struct treap_node *n, *o;
+ unsigned sp = i->sp;
+
+ if (!sp) return (0);
+ n = i->stack[--sp];
+ o = n->right;
+ while (o) {
+ assert(sp < TREAP_PATHMAX);
+ i->stack[sp++] = o; o = o->left;
+ }
+ i->sp = sp;
+ return (n);
+}
+
+static void check_node(struct treap_node *n, unsigned maxwt,
+ const char *klo, const char *khi)
+{
+ assert(n->wt <= maxwt);
+ if (klo) assert(STRCMP(n->k, >, klo));
+ if (khi) assert(STRCMP(n->k, <, khi));
+ if (n->left) check_node(n->left, n->wt, klo, n->k);
+ if (n->right) check_node(n->right, n->wt, n->k, khi);
+}
+
+void treap_check(struct treap *t)
+ { if (t->root) check_node(t->root, t->root->wt, 0, 0); }
+
+static void dump_node(struct treap_node *n, int ind)
+{
+ if (n->left) dump_node(n->left, ind + 1);
+ printf(";;%*s [%10u] `%s'\n", 2*ind, "", n->wt, n->k);
+ if (n->right) dump_node(n->right, ind + 1);
+}
+
+void treap_dump(struct treap *t) { if (t->root) dump_node(t->root, 0); }
+
+/*----- Configuration file parsing ----------------------------------------*/
+
+#ifndef DECL_ENVIRON
+ extern char **environ;
+#endif
+
+void config_init(struct config *conf)
+ { treap_init(&conf->sections); }
+
+struct config_section *config_find_section(struct config *conf, unsigned f,
+ const char *name)
+ { return (config_find_section_n(conf, f, name, strlen(name))); }
+
+struct config_section *config_find_section_n(struct config *conf, unsigned f,
+ const char *name, size_t sz)
+{
+ struct config_section *sect;
+ struct treap_path path;
+
+ if (!(f&CF_CREAT))
+ sect = treap_lookup(&conf->sections, name, sz);
+ else {
+ sect = treap_probe(&conf->sections, name, sz, &path);
+ if (!sect) {
+ sect = xmalloc(sizeof(*sect));
+ if (!conf->head) conf->tail = &conf->head;
+ sect->next = 0; *conf->tail = sect; conf->tail = §->next;
+ sect->parents = 0; sect->nparents = SIZE_MAX;
+ treap_init(§->vars); treap_init(§->cache);
+ treap_insert(&conf->sections, &path, §->_node, name, sz);
+ config_set_var_n(conf, sect, CF_LITERAL, "@NAME", 5, name, sz);
+ }
+ }
+ return (sect);
+}
+
+void config_set_fallback(struct config *conf, struct config_section *sect)
+{
+ if (sect->nparents == SIZE_MAX) sect->nparents = 0;
+ conf->fallback = sect;
+}
+
+void config_set_parent(struct config_section *sect,
+ struct config_section *parent)
+{
+ if (!parent)
+ sect->nparents = 0;
+ else {
+ sect->parents = xmalloc(sizeof(*sect->parents));
+ sect->parents[0] = parent; sect->nparents = 1;
+ }
+}
+
+void config_start_section_iter(struct config *conf,
+ struct config_section_iter *i)
+ { i->sect = conf->head; }
+
+struct config_section *config_next_section(struct config_section_iter *i)
+{
+ struct config_section *sect;
+
+ sect = i->sect;
+ if (sect) i->sect = sect->next;
+ return (sect);
+}
+
+static void set_config_section_parents(struct config *conf,
+ struct config_section *sect)
+{
+ struct config_section *parent;
+ struct config_var *var;
+ struct argv av = ARGV_INIT;
+ size_t i, n;
+ char *p, *q;
+
+ if (sect->nparents != SIZE_MAX) return;
+
+ var = treap_lookup(§->vars, "@PARENTS", 8);
+ if (!var) {
+ if (!conf->fallback)
+ sect->nparents = 0;
+ else {
+ sect->parents = xmalloc(sizeof(*sect->parents));
+ sect->nparents = 1;
+ sect->parents[0] = conf->fallback;
+ }
+ } else {
+ p = var->val;
+ for (;;) {
+ while (ISSPACE(*p)) p++;
+ if (!*p) break;
+ q = p; while (*q && *q != ',' && !ISSPACE(*q)) q++;
+ argv_append(&av, p); argv_append(&av, q);
+ p = q; if (*p == ',') p++;
+ }
+ sect->nparents = av.n/2;
+ sect->parents = xmalloc(sect->nparents*sizeof(sect->parents));
+ for (i = 0; i < av.n; i += 2) {
+ n = av.v[i + 1] - av.v[i];
+ parent = config_find_section_n(conf, 0, av.v[i], n);
+ if (!parent)
+ lose("%s:%u: unknown parent section `%.*s'",
+ var->file, var->line, (int)n, av.v[i]);
+ sect->parents[i/2] = parent;
+ }
+ argv_release(&av);
+ }
+}
+
+struct config_var *search_recursive(struct config *conf,
+ struct config_section *sect,
+ const char *name, size_t sz)
+{
+ struct config_cache_entry *cache;
+ struct treap_path path;
+ struct config_var *var, *v;
+ size_t i, j = j;
+
+ cache = treap_probe(§->cache, name, sz, &path);
+ if (!cache) {
+ cache = xmalloc(sizeof(*cache)); cache->f = CF_OPEN;
+ treap_insert(§->cache, &path, &cache->_node, name, sz);
+ } else if (cache->f&CF_OPEN)
+ lose("inheritance cycle through section `%s'",
+ CONFIG_SECTION_NAME(sect));
+ else
+ return (cache->var);
+
+ set_config_section_parents(conf, sect);
+
+ var = treap_lookup(§->vars, name, sz);
+ if (!var) {
+ for (i = 0; i < sect->nparents; i++) {
+ v = search_recursive(conf, sect->parents[i], name, sz);
+ if (!v);
+ else if (!var) { var = v; j = i; }
+ else if (var != v)
+ lose("section `%s' inherits variable `%s' ambiguously "
+ "via `%s' and `%s'",
+ CONFIG_SECTION_NAME(sect), CONFIG_VAR_NAME(var),
+ CONFIG_SECTION_NAME(sect->parents[j]),
+ CONFIG_SECTION_NAME(sect->parents[i]));
+ }
+ }
+
+ cache->var = var; cache->f &= ~CF_OPEN;
+ return (var);
+}
+
+struct config_var *config_find_var(struct config *conf,
+ struct config_section *sect,
+ unsigned f, const char *name)
+ { return (config_find_var_n(conf, sect, f, name, strlen(name))); }
+
+struct config_var *config_find_var_n(struct config *conf,
+ struct config_section *sect,
+ unsigned f, const char *name, size_t sz)
+{
+ struct config_var *var;
+ struct treap_path path;
+
+ if (f&CF_INHERIT)
+ var = search_recursive(conf, sect, name, sz);
+ else if (!(f&CF_CREAT))
+ var = treap_lookup(§->vars, name, sz);
+ else {
+ var = treap_probe(§->vars, name, sz, &path);
+ if (!var) {
+ var = xmalloc(sizeof(*var));
+ var->val = 0; var->file = 0; var->f = 0; var->line = 1;
+ treap_insert(§->vars, &path, &var->_node, name, sz);
+ }
+ }
+ return (var);
+}
+
+void config_start_var_iter(struct config_section *sect,
+ struct config_var_iter *i)
+ { treap_start_iter(§->vars, &i->i); }
+
+struct config_var *config_next_var(struct config_var_iter *i)
+ { return (treap_next(&i->i)); }
+
+void config_set_var(struct config *conf, struct config_section *sect,
+ unsigned f,
+ const char *name, const char *value)
+{
+ config_set_var_n(conf, sect, f,
+ name, strlen(name),
+ value, strlen(value));
+}
+
+void config_set_var_n(struct config *conf, struct config_section *sect,
+ unsigned f,
+ const char *name, size_t namelen,
+ const char *value, size_t valuelen)
+{
+ struct config_var *var =
+ config_find_var_n(conf, sect, CF_CREAT, name, namelen);
+
+ if (var->f&~f&CF_OVERRIDE) return;
+ free(var->val); var->val = xstrndup(value, valuelen); var->n = valuelen;
+ var->f = f;
+}
+
+int config_read_file(struct config *conf, const char *file, unsigned f)
+{
+ struct config_section *sect;
+ struct config_var *var;
+ struct dstr d = DSTR_INIT, dd = DSTR_INIT;
+ unsigned line = 0;
+ char *p, *q;
+ FILE *fp;
+
+ fp = fopen(file, "r");
+ if (!fp) {
+ if ((f&CF_NOENTOK) && errno == ENOENT) return (-1);
+ lose("failed to open configuration file `%s': %s",
+ file, strerror(errno));
+ }
+
+ sect = config_find_section(conf, CF_CREAT, "@CONFIG"); var = 0;
+
+ for (;;) {
+ dstr_reset(&d); if (dstr_readline(&d, fp)) break;
+ line++;
+
+ if (d.p[0] && !ISSPACE(d.p[0])) {
+ if (var) {
+ if (!(var->f&CF_OVERRIDE))
+ { var->val = xstrndup(dd.p, dd.len); var->n = dd.len; }
+ var = 0;
+ }
+ if (d.p[0] == ';')
+ ;
+ else if (d.p[0] == '[') {
+ p = d.p + 1; q = strchr(p, ']');
+ if (!q) lose("%s:%u: missing `]' in section header", file, line);
+ sect = config_find_section_n(conf, CF_CREAT, p, q - p);
+ q++; while (ISSPACE(*q)) q++;
+ if (*q) lose("%s:%u: trailing junk after `]' in section header",
+ file, line);
+ } else {
+ p = d.p;
+ while (*p && !ISSPACE(*p) && *p != '{' && *p != '}' && *p != '=')
+ p++;
+ var = config_find_var_n(conf, sect, CF_CREAT, d.p, p - d.p);
+ while (ISSPACE(*p)) p++;
+ if (*p != '=') lose("%s:%u: missing `=' in assignment", file, line);
+ p++; while (ISSPACE(*p)) p++;
+ if (!(var->f&CF_OVERRIDE)) {
+ free(var->val); var->val = 0; var->f = 0;
+ free(var->file); var->file = xstrdup(file); var->line = line;
+ }
+ dstr_reset(&dd); dstr_puts(&dd, p);
+ }
+ } else {
+ p = d.p; while (ISSPACE(*p)) p++;
+ if (*p) {
+ if (!var)
+ lose("%s:%u: continuation line, but no variable", file, line);
+ if (dd.len) dstr_putc(&dd, ' ');
+ dstr_puts(&dd, p);
+ }
+ }
+ }
+
+ if (var && !(var->f&CF_OVERRIDE))
+ { var->val = xstrndup(dd.p, dd.len); var->n = dd.len; }
+
+ dstr_release(&d); dstr_release(&dd);
+ if (fclose(fp))
+ lose("error reading configuration file `%s': %s", file, strerror(errno));
+ return (0);
+}
+
+void config_read_env(struct config *conf, struct config_section *sect)
+{
+ const char *p, *v;
+ size_t i;
+
+ for (i = 0; (p = environ[i]) != 0; i++) {
+ v = strchr(p, '='); if (!v) continue;
+ config_set_var_n(conf, sect, CF_LITERAL, p, v - p, v + 1, strlen(v + 1));
+ }
+}
+
+/*----- Substitution and quoting ------------------------------------------*/
+
+struct subst {
+ struct config *config;
+ struct config_section *home, *fallback;
+ struct argv *av;
+ struct dstr *d;
+};
+
+static const char *scan_name(const char *p, const char *l)
+{
+ while (p < l &&
+ (ISALNUM(*p) || *p == '-' || *p == '_' || *p == '.' || *p == '/' ||
+ *p == '*' || *p == '+' || *p == '%' || *p == '@'))
+ p++;
+ return (p);
+}
+
+static void filter_string(const char *p, const char *l, struct subst *sb,
+ unsigned qfilt)
+{
+ size_t r, n;
+
+ if (!qfilt)
+ dstr_putm(sb->d, p, l - p);
+ else for (;;) {
+ r = l - p; n = strcspn(p, "\"\\");
+ if (n > r) n = r;
+ dstr_putm(sb->d, p, n);
+ if (n >= r) break;
+ dstr_putcn(sb->d, '\\', qfilt); dstr_putc(sb->d, p[n]);
+ p += n + 1;
+ }
+}
+
+static const char *retrieve_varspec(const char *p, const char *l,
+ struct subst *sb,
+ struct config_var **var_out)
+{
+ struct config_section *sect = sb->home;
+ const char *t;
+
+ t = scan_name(p, l);
+ if (t < l && *t == ':') {
+ sect = config_find_section_n(sb->config, 0, p, t - p);
+ p = t + 1; t = scan_name(p, l);
+ }
+
+ if (!sect) *var_out = 0;
+ else *var_out = config_find_var_n(sb->config, sect, CF_INHERIT, p, t - p);
+ return (t);
+}
+
+#define SF_SPLIT 0x0001u
+#define SF_QUOT 0x0002u
+#define SF_SUBST 0x0004u
+#define SF_SUBEXPR 0x0008u
+#define SF_SPANMASK 0x00ffu
+#define SF_WORD 0x0100u
+#define SF_SKIP 0x0200u
+#define SF_LITERAL 0x0400u
+
+static const char *subst(const char *p, const char *l, struct subst *sb,
+ const char *file, unsigned line,
+ unsigned qfilt, unsigned f)
+{
+ struct config_var *var;
+ const char *q0, *q1, *t;
+ unsigned subqfilt, ff;
+ size_t n;
+
+#define ESCAPE "\\"
+#define SUBST "$"
+#define WORDSEP " \f\r\n\t\v'\""
+#define QUOT "\""
+#define DELIM "|}"
+
+ static const char *const delimtab[] =
+ { ESCAPE,
+ ESCAPE WORDSEP,
+ 0,
+ ESCAPE QUOT,
+ ESCAPE SUBST,
+ ESCAPE SUBST WORDSEP,
+ 0,
+ ESCAPE SUBST QUOT,
+ ESCAPE DELIM,
+ ESCAPE DELIM WORDSEP,
+ 0,
+ ESCAPE DELIM QUOT,
+ ESCAPE DELIM SUBST,
+ ESCAPE DELIM SUBST WORDSEP,
+ 0,
+ ESCAPE DELIM SUBST QUOT };
+
+#undef COMMON
+#undef WORDSEP
+#undef SQUOT
+#undef DELIM
+
+ if (!file) file = "<internal>";
+
+ if (f&SF_LITERAL) {
+ filter_string(p, l, sb, qfilt);
+ f |= SF_WORD;
+ goto done;
+ }
+
+ while (p < l) {
+
+ if ((f&(SF_SPLIT | SF_QUOT)) == SF_SPLIT && ISSPACE(*p)) {
+ if (f&SF_WORD) {
+ if (!(f&SF_SKIP)) {
+ argv_append(sb->av, xstrndup(sb->d->p, sb->d->len));
+ dstr_reset(sb->d);
+ }
+ f &= ~SF_WORD;
+ }
+ do p++; while (p < l && ISSPACE(*p));
+
+ } else if (*p == '\\') {
+ p++;
+ if (p >= l) lose("%s:%u: unfinished `\\' escape", file, line);
+ if (!(f&SF_SKIP)) {
+ if (qfilt && (*p == '"' || *p == '\\'))
+ dstr_putcn(sb->d, '\\', qfilt);
+ dstr_putc(sb->d, *p);
+ }
+ p++;
+
+ } else if ((f&SF_SPLIT) && *p == '"') {
+ f ^= SF_QUOT; f |= SF_WORD; p++;
+
+ } else if ((f&(SF_SPLIT | SF_QUOT)) == SF_SPLIT && *p == '\'') {
+ t = strchr(p, '\''); if (!t) lose("%s:%u: missing `''", file, line);
+ if (!(f&SF_SKIP)) filter_string(p, t, sb, qfilt);
+ p = t + 1; f |= SF_WORD;
+
+ } else if ((f&SF_SUBEXPR) && (*p == '|' || *p == '}')) {
+ break;
+
+ } else if ((f&SF_SUBST) && *p == '$') {
+ p++; if (p >= l) lose("%s:%u: incomplete substitution", file, line);
+ ff = f&~(SF_QUOT | (f&SF_WORD ? SF_SPLIT : 0));
+ switch (*p) {
+
+ case '?':
+ p = retrieve_varspec(p + 1, l, sb, &var);
+ if (p > l || *p != '{') lose("%s:%u: expected `{'", file, line);
+ p++;
+ ff |= SF_SUBEXPR;
+ p = subst(p, l, sb, file, line, qfilt,
+ ff | (var ? 0 : SF_SKIP));
+ if (p < l && *p == '|')
+ p = subst(p + 1, l, sb, file, line, qfilt,
+ ff | (var ? SF_SKIP : 0));
+ if (p >= l || *p != '}') lose("%s:%u: missing `}'", file, line);
+ p++;
+ break;
+
+ case '{':
+ q0 = p + 1; p = retrieve_varspec(q0, l, sb, &var); q1 = p;
+ subqfilt = qfilt;
+ while (p < l) {
+ if (*p != '|') break;
+ p++; t = scan_name(p, l);
+ if (t - p == 1 && *p == 'q') subqfilt = 2*subqfilt + 1;
+ else
+ lose("%s:%u: unknown filter `%.*s'",
+ file, line, (int)(t - p), p);
+ p = t;
+ }
+ if (!(f&SF_SKIP) && var) {
+ if (var->f&CF_EXPAND)
+ lose("%s:%u: recursive expansion of variable `%.*s'",
+ file, line, (int)(q1 - q0), q0);
+ var->f |= CF_EXPAND;
+ subst(var->val, var->val + var->n, sb,
+ var->file, var->line, subqfilt,
+ ff | (var->f&CF_LITERAL ? SF_LITERAL : 0));
+ var->f &= ~CF_EXPAND;
+ }
+ if (p < l && *p == '?')
+ p = subst(p + 1, l, sb, file, line, subqfilt,
+ ff | SF_SUBEXPR | (var ? SF_SKIP : 0));
+ else if (!var && !(f&SF_SKIP))
+ lose("%s:%u: unknown variable `%.*s'",
+ file, line, (int)(q1 - q0), q0);
+ if (p >= l || *p != '}') lose("%s:%u: missing `}'", file, line);
+ p++;
+ break;
+
+ default:
+ lose("%s:%u: unexpected substitution `%c'", file, line, *p);
+ }
+ if (p < l && !(~f&~(SF_WORD | SF_SPLIT)) && !ISSPACE(*p) &&
+ !((f&SF_SUBEXPR) && (*p == '|' || *p == '}')))
+ lose("%s:%u: surprising word boundary "
+ "after splicing substitution",
+ file, line);
+ }
+
+ else {
+ n = strcspn(p, delimtab[f&SF_SPANMASK]);
+ if (n > l - p) n = l - p;
+ if (!(f&SF_SKIP)) filter_string(p, p + n, sb, qfilt);
+ p += n; f |= SF_WORD;
+ }
+ }
+
+done:
+ if (f&SF_QUOT) lose("%s:%u: missing `\"'", file, line);
+ if ((f&(SF_WORD | SF_SPLIT | SF_SKIP)) == (SF_SPLIT | SF_WORD)) {
+ argv_append(sb->av, xstrndup(sb->d->p, sb->d->len));
+ dstr_reset(sb->d);
+ }
+
+ return (p);
+}
+
+void config_subst_string(struct config *config, struct config_section *home,
+ const char *what, const char *p, struct dstr *d)
+{
+ struct subst sb;
+
+ sb.config = config; sb.home = home; sb.d = d;
+ subst(p, p + strlen(p), &sb, what, 0, 0, SF_SUBST);
+ dstr_putz(d);
+}
+
+char *config_subst_string_alloc(struct config *config,
+ struct config_section *home,
+ const char *what, const char *p)
+{
+ struct dstr d = DSTR_INIT;
+ char *q;
+
+ config_subst_string(config, home, what, p, &d);
+ q = xstrndup(d.p, d.len); dstr_release(&d); return (q);
+}
+
+void config_subst_var(struct config *config, struct config_section *home,
+ struct config_var *var, struct dstr *d)
+{
+ struct subst sb;
+
+ sb.config = config; sb.home = home; sb.d = d;
+ var->f |= CF_EXPAND;
+ subst(var->val, var->val + var->n, &sb, var->file, var->line, 0,
+ SF_SUBST | (var->f&CF_LITERAL ? SF_LITERAL : 0));
+ var->f &= ~CF_EXPAND;
+ dstr_putz(d);
+}
+
+char *config_subst_var_alloc(struct config *config,
+ struct config_section *home,
+ struct config_var *var)
+{
+ struct dstr d = DSTR_INIT;
+ char *q;
+
+ config_subst_var(config, home, var, &d);
+ q = xstrndup(d.p, d.len); dstr_release(&d); return (q);
+}
+
+void config_subst_split_var(struct config *config,
+ struct config_section *home,
+ struct config_var *var, struct argv *av)
+{
+ struct dstr d = DSTR_INIT;
+ struct subst sb;
+
+ sb.config = config; sb.home = home; sb.av = av; sb.d = &d;
+ var->f |= CF_EXPAND;
+ subst(var->val, var->val + var->n, &sb, var->file, var->line, 0,
+ SF_SUBST | SF_SPLIT | (var->f&CF_LITERAL ? SF_LITERAL : 0));
+ var->f &= ~CF_EXPAND;
+ dstr_release(&d);
+}
+
+/*----- That's all, folks -------------------------------------------------*/