master xplshn/aruu / cmd / dev / ninqu / sexpr.c
  1#include "ninqu.h"
  2
  3#include <ctype.h>
  4#include <stdio.h>
  5#include <stdlib.h>
  6#include <string.h>
  7
  8/* track nesting so $(EXTRA_LIBS:$(BASESTEM)) does not stop at the
  9 * inner close paren. shared by kv_expand and subst_tmpl */
 10const char *
 11find_dollar_close(const char *inner)
 12{
 13  const char *scan  = inner;
 14  int         depth = 1;
 15
 16  while (*scan) {
 17    if (scan[0] == '$' && scan[1] == '(') {
 18      depth++;
 19      scan += 2;
 20      continue;
 21    }
 22    if (*scan == ')') {
 23      if (--depth == 0)
 24        return scan;
 25    }
 26    scan++;
 27  }
 28  return NULL;
 29}
 30
 31struct SNode *
 32snode_atom(const char *s, int line)
 33{
 34  struct SNode *n = ecalloc(1, sizeof *n);
 35  n->kind         = S_ATOM;
 36  n->atom         = estrdup(s);
 37  n->line         = line;
 38  return n;
 39}
 40
 41struct SNode *
 42snode_list(int line)
 43{
 44  struct SNode *n = ecalloc(1, sizeof *n);
 45  n->kind         = S_LIST;
 46  n->line         = line;
 47  return n;
 48}
 49
 50void
 51snode_push(struct SNode *list, struct SNode *kid)
 52{
 53  if (list->nkids == 0) {
 54    list->kids = emalloc(8 * sizeof *list->kids);
 55  } else {
 56    int cap = 8;
 57    while (cap < list->nkids)
 58      cap *= 2;
 59    if (list->nkids == cap)
 60      list->kids = erealloc(list->kids, (size_t)cap * 2 * sizeof *list->kids);
 61  }
 62  list->kids[list->nkids++] = kid;
 63}
 64
 65void
 66snode_free(struct SNode *n)
 67{
 68  int i;
 69  if (!n)
 70    return;
 71  if (n->kind == S_LIST) {
 72    for (i = 0; i < n->nkids; i++)
 73      snode_free(n->kids[i]);
 74    free(n->kids);
 75  }
 76  free(n->atom);
 77  free(n);
 78}
 79
 80/* deep copy. a template body is parsed from a file that gets freed
 81 * once load_manifest returns, so the stored copy cannot point back
 82 * into that tree */
 83struct SNode *
 84snode_clone(struct SNode *n)
 85{
 86  struct SNode *out;
 87  int           i;
 88
 89  if (!n)
 90    return NULL;
 91  if (n->kind == S_ATOM)
 92    return snode_atom(n->atom, n->line);
 93  out = snode_list(n->line);
 94  for (i = 0; i < n->nkids; i++)
 95    snode_push(out, snode_clone(n->kids[i]));
 96  return out;
 97}
 98
 99/* replace each $(PARAM) with its argument text. any other $(...) is
100 * copied verbatim for the later kv_expand pass, so a template body
101 * can reference $(BASESTEM) the same way a hand-written rule does */
102char *
103subst_tmpl(const char *s, struct StrList *params, char **vals)
104{
105  size_t      cap = strlen(s) * 2 + 64, len = 0;
106  char       *out = emalloc(cap);
107  const char *p   = s;
108
109  while (*p) {
110    if (p[0] == '$' && p[1] == '(') {
111      const char *inner = p + 2;
112      const char *close = find_dollar_close(inner);
113
114      if (close) {
115        char   name[256];
116        size_t nl = (size_t)(close - inner);
117        size_t tokl;
118        int    pi;
119
120        if (nl >= sizeof name)
121          nl = sizeof name - 1;
122        memcpy(name, inner, nl);
123        name[nl] = '\0';
124
125        for (pi = 0; pi < params->n; pi++)
126          if (strcmp(params->v[pi], name) == 0)
127            break;
128
129        tokl = (pi < params->n) ? strlen(vals[pi]) : (size_t)(close - p) + 1;
130        while (len + tokl + 1 >= cap) {
131          cap *= 2;
132          out = erealloc(out, cap);
133        }
134        memcpy(out + len, pi < params->n ? vals[pi] : p, tokl);
135        len += tokl;
136        p = close + 1;
137        continue;
138      }
139    }
140    if (len + 2 >= cap) {
141      cap *= 2;
142      out = erealloc(out, cap);
143    }
144    out[len++] = *p++;
145  }
146  out[len] = '\0';
147  return out;
148}
149
150void
151snode_subst(struct SNode *n, struct StrList *params, char **vals)
152{
153  int i;
154  if (!n)
155    return;
156  if (n->kind == S_ATOM) {
157    char *out = subst_tmpl(n->atom, params, vals);
158    free(n->atom);
159    n->atom = out;
160    return;
161  }
162  for (i = 0; i < n->nkids; i++)
163    snode_subst(n->kids[i], params, vals);
164}
165
166/* swallow a balanced $(...) inside a bare atom */
167void
168lex_swallow_dollar(struct SLex *lx, char *buf, size_t *blen, size_t bsz)
169{
170  int depth = 1;
171
172  if (*blen + 2 < bsz) {
173    buf[(*blen)++] = '$';
174    buf[(*blen)++] = '(';
175  }
176  lx->pos += 2;
177  while (depth > 0) {
178    int e = lx->src[lx->pos];
179    if (e == '\0')
180      eprintf("manifest: unterminated $(...) on line %d\n", lx->line);
181    if (e == '\n')
182      lx->line++;
183    if (e == '$' && lx->src[lx->pos + 1] == '(') {
184      depth++;
185      if (*blen + 2 < bsz) {
186        buf[(*blen)++] = e;
187        buf[(*blen)++] = lx->src[lx->pos + 1];
188      }
189      lx->pos += 2;
190      continue;
191    }
192    if (*blen < bsz - 1)
193      buf[(*blen)++] = e;
194    if (e == ')')
195      depth--;
196    lx->pos++;
197  }
198}
199
200/* \" \\ \n \t \r \$ are recognized. anything else keeps the backslash
201 * so shell escapes like \( survive */
202void
203lex_string(struct SLex *lx, char *buf, size_t bsz)
204{
205  int    line = lx->line;
206  size_t blen = 0;
207
208  lx->pos++;
209  while (lx->src[lx->pos] && lx->src[lx->pos] != '"') {
210    int e = lx->src[lx->pos];
211
212    if (e == '\\' && lx->src[lx->pos + 1]) {
213      int esc = lx->src[lx->pos + 1];
214      switch (esc) {
215        case 'n':
216          e = '\n';
217          break;
218        case 't':
219          e = '\t';
220          break;
221        case 'r':
222          e = '\r';
223          break;
224        case '"':
225        case '\\':
226        case '$':
227          e = esc;
228          break;
229        default:
230          if (blen < bsz - 1)
231            buf[blen++] = '\\';
232          e = esc;
233          break;
234      }
235      lx->pos += 2;
236    } else {
237      lx->pos++;
238    }
239    if (e == '\n')
240      lx->line++;
241    if (blen < bsz - 1)
242      buf[blen++] = (char)e;
243  }
244  if (lx->src[lx->pos] != '"')
245    eprintf("manifest: unterminated string on line %d\n", line);
246  lx->pos++;
247  buf[blen] = '\0';
248}
249
250struct SNode *
251snode_parse(struct SLex *lx)
252{
253  char           buf[8192];
254  size_t         blen;
255  int            line;
256  struct SNode  *root  = snode_list(lx->line);
257  struct SNode **stack = emalloc(16 * sizeof *stack);
258  int            sp = 0, scap = 16;
259
260  stack[sp++] = root;
261
262  for (;;) {
263    int c = lx->src[lx->pos];
264
265    if (c == '\0')
266      break;
267    if (c == '\n') {
268      lx->line++;
269      lx->pos++;
270      continue;
271    }
272    if (isspace((unsigned char)c)) {
273      lx->pos++;
274      continue;
275    }
276    if (c == ';') {
277      while (lx->src[lx->pos] && lx->src[lx->pos] != '\n')
278        lx->pos++;
279      continue;
280    }
281    if (c == '(') {
282      struct SNode *lst = snode_list(lx->line);
283      snode_push(stack[sp - 1], lst);
284      if (sp >= scap) {
285        scap *= 2;
286        stack = erealloc(stack, (size_t)scap * sizeof *stack);
287      }
288      stack[sp++] = lst;
289      lx->pos++;
290      continue;
291    }
292    if (c == ')') {
293      if (sp <= 1)
294        eprintf("manifest: stray ')' on line %d\n", lx->line);
295      sp--;
296      lx->pos++;
297      continue;
298    }
299    if (c == '"') {
300      line = lx->line;
301      lex_string(lx, buf, sizeof buf);
302      snode_push(stack[sp - 1], snode_atom(buf, line));
303      continue;
304    }
305
306    /* bare atom */
307    line = lx->line;
308    blen = 0;
309    while (lx->src[lx->pos]) {
310      int d = lx->src[lx->pos];
311      if (d == '(' || d == ')' || d == '"' || d == ';' || isspace((unsigned char)d))
312        break;
313      if (d == '$' && lx->src[lx->pos + 1] == '(') {
314        lex_swallow_dollar(lx, buf, &blen, sizeof buf);
315        continue;
316      }
317      if (blen < sizeof buf - 1)
318        buf[blen++] = d;
319      lx->pos++;
320    }
321    buf[blen] = '\0';
322    snode_push(stack[sp - 1], snode_atom(buf, line));
323  }
324
325  if (sp != 1)
326    eprintf("manifest: %d unclosed '(' at end of file\n", sp - 1);
327
328  free(stack);
329  return root;
330}
331
332int
333s_is_list(struct SNode *n)
334{
335  return n && n->kind == S_LIST;
336}
337
338const char *
339s_head(struct SNode *n)
340{
341  if (!s_is_list(n) || n->nkids == 0 || n->kids[0]->kind != S_ATOM)
342    return NULL;
343  return n->kids[0]->atom;
344}