1#include "shinobi.h"
2#include "internal.h"
3#include "posix.h"
4
5#include <glob.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9
10/*
11 * eval does the second pass over the built ast
12 * seed builtins like CC and so on
13 * apply assignment semantics
14 * execute conditionals and flatten the chosen branch
15 */
16
17static char *
18runshellassign(const char *cmd)
19{
20 FILE *fp;
21 char buf[4096];
22 char *out;
23 size_t len, cap, nread, i;
24
25 fp = popen(cmd, "r");
26 if (!fp)
27 return xstrdup("");
28
29 cap = 64;
30 len = 0;
31 out = xmalloc(cap);
32 out[0] = 0;
33
34 while ((nread = fread(buf, 1, sizeof(buf), fp)) > 0) {
35 if (len + nread + 1 > cap) {
36 while (cap < len + nread + 1)
37 cap *= 2;
38 out = xrealloc(out, cap);
39 }
40 memcpy(out + len, buf, nread);
41 len += nread;
42 }
43 out[len] = 0;
44 pclose(fp);
45 if (len > 0 && out[len - 1] == '\n') {
46 len--;
47 if (len > 0 && out[len - 1] == '\r')
48 len--;
49 out[len] = 0;
50 } else if (len > 0 && out[len - 1] == '\r') {
51 len--;
52 out[len] = 0;
53 }
54
55 for (i = 0; i < len; i++) {
56 if (out[i] == '\n' || out[i] == '\r')
57 out[i] = ' ';
58 }
59 return out;
60}
61
62static char *
63escapedollars(const char *s)
64{
65 size_t i, n, ndollar;
66 char *out;
67 size_t j;
68
69 n = strlen(s);
70 ndollar = 0;
71 for (i = 0; i < n; i++) {
72 if (s[i] == '$')
73 ndollar++;
74 }
75 out = xmalloc(n + ndollar + 1);
76 j = 0;
77 for (i = 0; i < n; i++) {
78 if (s[i] == '$')
79 out[j++] = '$';
80 out[j++] = s[i];
81 }
82 out[j] = 0;
83 return out;
84}
85
86static void
87removeexportname(struct StrList *list, const char *name)
88{
89 size_t i;
90
91 for (i = 0; i < list->n; i++) {
92 if (strcmp(list->v[i], name) != 0)
93 continue;
94 free(list->v[i]);
95 memmove(&list->v[i], &list->v[i + 1], (list->n - i - 1) * sizeof(list->v[0]));
96 list->n--;
97 return;
98 }
99}
100
101static int
102assignexported(const struct EvalCtx *ctx, const struct AssignNode *in)
103{
104 if (in->exported > 0)
105 return 1;
106 if (in->exported < 0)
107 return 0;
108 if (hasword(&ctx->unexports, in->lhs))
109 return 0;
110 if (hasword(&ctx->exports, in->lhs))
111 return 1;
112 return ctx->export_all;
113}
114
115void
116evalassign(struct EvalCtx *ctx, const struct AssignNode *in)
117{
118 struct Env *env;
119 struct Var *v;
120 char *lhs, *rhs, *joined;
121 enum Origin o;
122 int exported;
123
124 env = ctx->env;
125 o = in->origin ? in->origin : ORIGIN_FILE;
126 exported = assignexported(ctx, in);
127 lhs = expandstr(ctx, in->lhs);
128 switch (in->op) {
129 case ASSIGN_EQ:
130 envsetvar(env, lhs, xstrdup(in->rhs), 0, o, exported);
131 break;
132 case ASSIGN_DCOLON_EQ:
133 if (ctx->mode == MODE_POSIX_2008) {
134 dielikemake(ctx->cur_path, ctx->cur_line, "'::=' is not valid in POSIX 2008", 0);
135 ctx->errors++;
136 break;
137 }
138 envsetvar(env, lhs, expandstr(ctx, in->rhs), 1, o, exported);
139 break;
140 case ASSIGN_COLON_EQ:
141 /* := is a gnu extension. posix uses ::= for simple expansion.
142 * bsd := is different again, equivalent to posix :::= */
143 if (ctx->mode == MODE_POSIX_2024) {
144 dielikemake(ctx->cur_path, ctx->cur_line, "':=' is not valid in POSIX 2024, use '::='", 0);
145 ctx->errors++;
146 break;
147 }
148 if (ctx->mode == MODE_POSIX_2008) {
149 dielikemake(ctx->cur_path, ctx->cur_line, "':=' is not valid in POSIX 2008", 0);
150 ctx->errors++;
151 break;
152 }
153 envsetvar(env, lhs, expandstr(ctx, in->rhs), 1, o, exported);
154 break;
155 case ASSIGN_COLON3_EQ:
156 if (ctx->mode == MODE_POSIX_2008) {
157 dielikemake(ctx->cur_path, ctx->cur_line, "':::=' is not valid in POSIX 2008", 0);
158 ctx->errors++;
159 break;
160 }
161 /* expand now, escape the result so re-expansion on use gives back the same value */
162 rhs = expandstr(ctx, in->rhs);
163 joined = escapedollars(rhs);
164 free(rhs);
165 envsetvar(env, lhs, joined, 0, o, exported);
166 break;
167 case ASSIGN_QMARK_EQ:
168 if (ctx->mode == MODE_POSIX_2008) {
169 dielikemake(ctx->cur_path, ctx->cur_line, "'?=' is not valid in POSIX 2008", 0);
170 ctx->errors++;
171 break;
172 }
173 if (!findvar(env, lhs))
174 envsetvar(env, lhs, xstrdup(in->rhs), 0, o, exported);
175 break;
176 case ASSIGN_PLUS_EQ:
177 if (ctx->mode == MODE_POSIX_2008) {
178 dielikemake(ctx->cur_path, ctx->cur_line, "'+=' is not valid in POSIX 2008", 0);
179 ctx->errors++;
180 break;
181 }
182 v = findvar(env, lhs);
183 if (!v) {
184 envsetvar(env, lhs, xstrdup(in->rhs), 0, o, in->exported);
185 break;
186 }
187 if ((int)o < (int)v->origin)
188 break;
189 rhs = v->simple ? expandstr(ctx, in->rhs) : xstrdup(in->rhs);
190 joined = cat3(v->val, " ", rhs);
191 free(rhs);
192 free(v->val);
193 v->val = joined;
194 v->origin = o;
195 if (exported)
196 v->exported = 1;
197 break;
198 case ASSIGN_BANG_EQ:
199 if (ctx->mode == MODE_POSIX_2008) {
200 dielikemake(ctx->cur_path, ctx->cur_line, "'!=' is not valid in POSIX 2008", 0);
201 ctx->errors++;
202 break;
203 }
204 rhs = expandstr(ctx, in->rhs);
205 joined = runshellassign(rhs);
206 free(rhs);
207 envsetvar(env, lhs, joined, 1, o, exported);
208 break;
209 }
210 free(lhs);
211}
212
213static void
214evalexport(struct EvalCtx *ctx, const struct ExportNode *exp)
215{
216 struct StrList names;
217 struct Var *v;
218 size_t i, j;
219
220 if (exp->all) {
221 size_t j;
222
223 ctx->export_all = exp->exported;
224 for (i = 0; i < ctx->env->n; i++)
225 ctx->env->v[i].exported = exp->exported;
226 for (j = 0; j < ctx->out->nvars; j++)
227 ctx->out->vars[j].exported = exp->exported;
228 for (j = 0; j < ctx->out->ntvars; j++)
229 ctx->out->tvars[j].exported = exp->exported;
230 return;
231 }
232 memset(&names, 0, sizeof(names));
233 for (i = 0; i < exp->names.n; i++) {
234 char *s;
235
236 s = expandstr(ctx, exp->names.v[i]);
237 splitwords(&names, s, strlen(s));
238 free(s);
239 }
240 for (i = 0; i < names.n; i++) {
241 if (exp->exported) {
242 removeexportname(&ctx->unexports, names.v[i]);
243 if (!hasword(&ctx->exports, names.v[i]))
244 addstr(&ctx->exports, names.v[i]);
245 } else {
246 removeexportname(&ctx->exports, names.v[i]);
247 if (!hasword(&ctx->unexports, names.v[i]))
248 addstr(&ctx->unexports, names.v[i]);
249 }
250 v = findvar(ctx->env, names.v[i]);
251 if (v)
252 v->exported = exp->exported;
253 }
254 for (j = 0; j < ctx->out->nvars; j++) {
255 if (hasword(&ctx->exports, ctx->out->vars[j].lhs))
256 ctx->out->vars[j].exported = 1;
257 if (hasword(&ctx->unexports, ctx->out->vars[j].lhs))
258 ctx->out->vars[j].exported = 0;
259 }
260 for (j = 0; j < ctx->out->ntvars; j++) {
261 if (hasword(&ctx->exports, ctx->out->tvars[j].lhs))
262 ctx->out->tvars[j].exported = 1;
263 if (hasword(&ctx->unexports, ctx->out->tvars[j].lhs))
264 ctx->out->tvars[j].exported = 0;
265 }
266 freestrs(&names);
267}
268
269static int
270testcond(struct EvalCtx *ctx, const struct CondNode *cond)
271{
272 char *a, *b, *name;
273 struct Var *v;
274 int ok;
275
276 if (cond->kind == COND_IFDEF || cond->kind == COND_IFNDEF) {
277 name = expandstr(ctx, cond->arg1);
278 v = findvar(ctx->env, name);
279 ok = v != 0;
280 free(name);
281 if (cond->kind == COND_IFNDEF)
282 ok = !ok;
283 return ok;
284 }
285
286 a = cond->arg1 ? expandstr(ctx, cond->arg1) : xstrdup("");
287 b = cond->arg2 ? expandstr(ctx, cond->arg2) : xstrdup("");
288 ok = strcmp(a, b) == 0;
289 free(a);
290 free(b);
291 if (cond->kind == COND_IFNEQ)
292 ok = !ok;
293 return ok;
294}
295
296static void
297copywords(struct StrList *out, const struct StrList *in, struct EvalCtx *ctx)
298{
299 size_t i;
300
301 memset(out, 0, sizeof(*out));
302 for (i = 0; i < in->n; i++) {
303 char *s;
304
305 s = expandstr(ctx, in->v[i]);
306 splitwords(out, s, strlen(s));
307 free(s);
308 }
309}
310
311static void
312addrulesetassign(struct AssignNode **vec, size_t *n, const struct Node *src, struct EvalCtx *ctx)
313{
314 struct AssignNode *dst;
315
316 *vec = xrealloc(*vec, (*n + 1) * sizeof((*vec)[0]));
317 dst = &(*vec)[(*n)++];
318 memset(dst, 0, sizeof(*dst));
319 dst->lhs = xstrdup(src->data.assign.lhs);
320 dst->op = src->data.assign.op;
321 dst->origin = src->data.assign.origin;
322 dst->exported = assignexported(ctx, &src->data.assign);
323 dst->tspec = src->data.assign.tspec;
324 if (src->data.assign.op == ASSIGN_DCOLON_EQ ||
325 src->data.assign.op == ASSIGN_COLON_EQ ||
326 src->data.assign.op == ASSIGN_COLON3_EQ) {
327 char *rhs;
328
329 rhs = expandstr(ctx, src->data.assign.rhs);
330 dst->rhs = escapedollars(rhs);
331 free(rhs);
332 } else if (src->data.assign.op == ASSIGN_BANG_EQ) {
333 dst->rhs = expandstr(ctx, src->data.assign.rhs);
334 } else {
335 dst->rhs = xstrdup(src->data.assign.rhs);
336 }
337 copywords(&dst->targets, &src->data.assign.targets, ctx);
338}
339
340static int
341issufruletargets(const struct RuleNode *rule)
342{
343 size_t i;
344 char *a, *b;
345
346 if (rule->targets.n == 0)
347 return 0;
348 for (i = 0; i < rule->targets.n; i++) {
349 if (issinglesuf(rule->targets.v[i], &a)) {
350 free(a);
351 continue;
352 }
353 if (issuf(rule->targets.v[i], &a, &b)) {
354 free(a);
355 free(b);
356 continue;
357 }
358 return 0;
359 }
360 return 1;
361}
362
363static void
364addrulesetrule(struct RuleSet *out, const struct RuleNode *src, struct EvalCtx *ctx)
365{
366 struct RuleNode *dst;
367
368 out->rules = xrealloc(out->rules, (out->nrules + 1) * sizeof(out->rules[0]));
369 dst = &out->rules[out->nrules++];
370 memset(dst, 0, sizeof(*dst));
371 dst->dcolon = src->dcolon;
372 copywords(&dst->targets, &src->targets, ctx);
373 if (src->target_pattern)
374 dst->target_pattern = expandstr(ctx, src->target_pattern);
375 copywords(&dst->prereqs, &src->prereqs, ctx);
376 copywords(&dst->order_only, &src->order_only, ctx);
377 addrecipes(&dst->recipes, &src->recipes);
378}
379
380static int
381hasglobmeta(const char *s)
382{
383 for (; *s; s++) {
384 if (*s == '*' || *s == '?' || *s == '[')
385 return 1;
386 }
387 return 0;
388}
389
390static int
391evalinclude(struct EvalCtx *ctx, const struct IncludeNode *inc)
392{
393 struct StrList paths;
394 char *exp;
395 size_t i;
396
397 struct RuleNode *makerule;
398
399 makerule = 0;
400
401 exp = expandstr(ctx, inc->path);
402 memset(&paths, 0, sizeof(paths));
403 splitwords(&paths, exp, strlen(exp));
404 free(exp);
405 for (i = 0; i < paths.n; i++) {
406 size_t j, nmatch;
407 char *single;
408 const char *word;
409 glob_t g;
410 int grc;
411
412 memset(&g, 0, sizeof(g));
413 single = 0;
414 word = paths.v[i];
415 nmatch = 0;
416 grc = 0;
417 /* glob in include paths is a gnu extension */
418 if (ctx->mode == MODE_GNU && hasglobmeta(word)) {
419 grc = glob(word, 0, 0, &g);
420 nmatch = (grc == 0 && g.gl_pathc > 0) ? g.gl_pathc : 0;
421 } else {
422 nmatch = 1;
423 single = xstrdup(word);
424 }
425
426 if (nmatch == 0) {
427 if (!inc->optional)
428 fprintf(stderr, "%s:%d: %s: No such file or directory\n",
429 ctx->cur_path, ctx->cur_line, word);
430 globfree(&g);
431 continue;
432 }
433
434 for (j = 0; j < nmatch; j++) {
435 char *src;
436 const char *path;
437
438 path = single ? single : g.gl_pathv[j];
439 src = readfile(path);
440 if (!src) {
441 size_t r, k;
442
443 makerule = 0;
444 for (r = 0; r < ctx->out->nrules && !makerule; r++) {
445 struct RuleNode *rule = &ctx->out->rules[r];
446
447 for (k = 0; k < rule->targets.n; k++) {
448 if (strcmp(rule->targets.v[k], path) == 0) {
449 makerule = rule;
450 break;
451 }
452 }
453 }
454 if (makerule) {
455 for (k = 0; k < makerule->recipes.n; k++) {
456 char *cmd;
457 int rc;
458
459 cmd = expandstr(ctx, makerule->recipes.v[k].body);
460 rc = system(cmd);
461 free(cmd);
462 if (rc != 0)
463 break;
464 }
465 src = readfile(path);
466 }
467 }
468 if (!src) {
469 if (!inc->optional)
470 fprintf(stderr, "%s:%d: %s: No such file or directory\n",
471 ctx->cur_path, ctx->cur_line, path);
472 continue;
473 }
474 if (evalsnippet(ctx, path, src) < 0) {
475 free(src);
476 if (single)
477 free(single);
478 globfree(&g);
479 freestrs(&paths);
480 return -1;
481 }
482 free(src);
483 }
484 if (single)
485 free(single);
486 globfree(&g);
487 }
488 freestrs(&paths);
489 return 0;
490}
491
492static int
493isgnutarget(const char *s)
494{
495 /*gnu only special targets that we actually handle*/
496 static const char *const gnutargets[] = {
497 ".EXPORT_ALL_VARIABLES",
498 0
499 };
500 size_t i;
501
502 for (i = 0; gnutargets[i]; i++) {
503 if (strcmp(s, gnutargets[i]) == 0)
504 return 1;
505 }
506 return 0;
507}
508
509static int
510evalnodes(const struct NodeList *in, struct RuleSet *out, struct EvalCtx *ctx)
511{
512 size_t i;
513 struct SpecialTargets targets;
514
515 initspecialtargets(&targets);
516
517 for (i = 0; i < in->n; i++) {
518 const struct Node *src;
519
520 src = &in->v[i];
521 ctx->cur_line = src->loc.line0;
522 switch (src->kind) {
523 case NODE_BLANK:
524 break;
525 case NODE_RAW: {
526 char *exp;
527
528 exp = expandstr(ctx, src->data.raw.text);
529 free(exp);
530 break;
531 }
532 case NODE_COMMENT:
533 break;
534 case NODE_INCLUDE:
535 if (evalinclude(ctx, &src->data.include) < 0)
536 return -1;
537 break;
538 case NODE_COND:
539 if (ctx->mode != MODE_GNU) {
540 dielikemake(ctx->cur_path, ctx->cur_line, "conditionals are only valid in GNU", 0);
541 ctx->errors++;
542 break;
543 }
544 if (testcond(ctx, &src->data.cond)) {
545 if (evalnodes(&src->data.cond.thenpart, out, ctx) < 0)
546 return -1;
547 } else {
548 if (evalnodes(&src->data.cond.elsepart, out, ctx) < 0)
549 return -1;
550 }
551 continue;
552 case NODE_ASSIGN:
553 if (ctx->mode != MODE_GNU && src->data.assign.define_block) {
554 dielikemake(ctx->cur_path, ctx->cur_line, "'define'/'endef' are only valid in GNU", 0);
555 ctx->errors++;
556 break;
557 }
558 if (ctx->mode != MODE_GNU && src->data.assign.exported != 0) {
559 dielikemake(ctx->cur_path, ctx->cur_line, "'export'/'unexport' are only valid in GNU", 0);
560 ctx->errors++;
561 break;
562 }
563 updatespecialassign(&targets, src->data.assign.lhs, src->data.assign.rhs);
564 if (src->data.assign.tspec) {
565 addrulesetassign(&out->tvars, &out->ntvars, src, ctx);
566 } else {
567 addrulesetassign(&out->vars, &out->nvars, src, ctx);
568 evalassign(ctx, &src->data.assign);
569 }
570 break;
571 case NODE_EXPORT:
572 if (ctx->mode != MODE_GNU) {
573 dielikemake(ctx->cur_path, ctx->cur_line, "'export'/'unexport' are only valid in GNU", 0);
574 ctx->errors++;
575 break;
576 }
577 evalexport(ctx, &src->data.export);
578 break;
579 case NODE_RULE: {
580 struct StrList exptargets;
581 struct RuleNode tmprule;
582
583 /* expand targets before special target checks so that some pattern like
584 * "$X.POSIX:" with X undefined correctly resolves to just ".POSIX:" and
585 * set targets.posix before suffix rule prereq warnings fire
586 * (this is for gnu/features/suffixrules/t009). */
587 memset(&exptargets, 0, sizeof(exptargets));
588 copywords(&exptargets, &src->data.rule.targets, ctx);
589 tmprule = src->data.rule;
590 tmprule.targets = exptargets;
591 if (ctx->mode != MODE_GNU && tmprule.dcolon) {
592 dielikemake(ctx->cur_path, ctx->cur_line,
593 "double-colon rules are only valid in GNU", 0);
594 ctx->errors++;
595 freestrs(&exptargets);
596 break;
597 }
598 /* ignore gnu only targets if not in gnu mode */
599 if (ctx->mode != MODE_GNU &&
600 tmprule.targets.n == 1 &&
601 isgnutarget(tmprule.targets.v[0])) {
602 freestrs(&exptargets);
603 break;
604 }
605 if (tmprule.targets.n == 1 &&
606 strcmp(tmprule.targets.v[0], ".EXPORT_ALL_VARIABLES") == 0) {
607 ctx->export_all = 1;
608 freestrs(&exptargets);
609 break;
610 }
611 if (tmprule.targets.n == 1 &&
612 strcmp(tmprule.targets.v[0], ".DEFAULT") == 0) {
613 freerecipes(&out->defaultrule);
614 addrecipes(&out->defaultrule, &src->data.rule.recipes);
615 freestrs(&exptargets);
616 break;
617 }
618 if (handlespecialrule(&targets, &tmprule)) {
619 freestrs(&exptargets);
620 break;
621 }
622 if (!targets.posix &&
623 src->data.rule.prereqs.n > 0 &&
624 !issufrule(&tmprule) &&
625 issufruletargets(&tmprule))
626 warnlikemake(ctx->cur_path, ctx->cur_line,
627 "ignoring prerequisites on suffix rule definition");
628 freestrs(&exptargets);
629 addrulesetrule(out, &src->data.rule, ctx);
630 break;
631 }
632 }
633 }
634 if (out)
635 addwords(&out->phony, &targets.phony);
636 if (out)
637 out->export_all = ctx->export_all || targets.export_all;
638 if (out)
639 out->posix = targets.posix;
640 freestrs(&targets.phony);
641 return 0;
642}
643
644int
645evalsnippet(struct EvalCtx *ctx, const char *path, const char *src)
646{
647 struct Ast ast;
648 const char *saved_path;
649 int rc;
650
651 if (parse(path, src, &ast, ctx->mode) < 0)
652 return -1;
653 saved_path = ctx->cur_path;
654 ctx->cur_path = path;
655 rc = evalnodes((const struct NodeList *)&ast, ctx->out, ctx);
656 ctx->cur_path = saved_path;
657 freeast(&ast);
658 return rc;
659}
660
661int
662eval(const char *path, const struct Ast *ast, const struct Ast *pre, int envoverride, enum ShinMode mode, struct RuleSet *out)
663{
664 struct Env env;
665 struct EvalCtx ctx;
666 int rc;
667
668 memset(out, 0, sizeof(*out));
669 out->envoverride = envoverride;
670 memset(&env, 0, sizeof(env));
671 memset(&ctx, 0, sizeof(ctx));
672 seedenv(&env, 0, out->envoverride, mode);
673 ctx.env = &env;
674 ctx.out = out;
675 ctx.cur_path = path;
676 ctx.mode = mode;
677 if (pre) {
678 rc = evalnodes((const struct NodeList *)pre, out, &ctx);
679 if (rc < 0) {
680 freeenv(&env);
681 return rc;
682 }
683 }
684 rc = evalnodes((const struct NodeList *)ast, out, &ctx);
685 freeenv(&env);
686 freestrs(&ctx.exports);
687 freestrs(&ctx.unexports);
688 if (rc == 0 && ctx.errors)
689 return -1;
690 return rc;
691}
692
693static void
694freeassigns(struct AssignNode *v, size_t n)
695{
696 size_t i;
697
698 for (i = 0; i < n; i++) {
699 free(v[i].lhs);
700 free(v[i].rhs);
701 freestrs(&v[i].targets);
702 }
703 free(v);
704}
705
706static void
707freerules(struct RuleNode *v, size_t n)
708{
709 size_t i;
710
711 for (i = 0; i < n; i++) {
712 freestrs(&v[i].targets);
713 free(v[i].target_pattern);
714 freestrs(&v[i].prereqs);
715 freestrs(&v[i].order_only);
716 freerecipes(&v[i].recipes);
717 }
718 free(v);
719}
720
721void
722freeruleset(struct RuleSet *ruleset)
723{
724 if (!ruleset)
725 return;
726 freeassigns(ruleset->vars, ruleset->nvars);
727 freeassigns(ruleset->tvars, ruleset->ntvars);
728 freerules(ruleset->rules, ruleset->nrules);
729 freerecipes(&ruleset->defaultrule);
730 freestrs(&ruleset->phony);
731 memset(ruleset, 0, sizeof(*ruleset));
732}
733
734/*
735 * strings in ast nodes are arena-owned, only free the v arrays and
736 * the submake strings (xmalloc'd separately by parsesubmake)
737 */
738static void
739freenodes(struct NodeList *list)
740{
741 size_t i, j;
742
743 for (i = 0; i < list->n; i++) {
744 switch (list->v[i].kind) {
745 case NODE_COMMENT:
746 case NODE_RAW:
747 case NODE_INCLUDE:
748 break;
749 case NODE_EXPORT:
750 free(list->v[i].data.export.names.v);
751 break;
752 case NODE_ASSIGN:
753 free(list->v[i].data.assign.targets.v);
754 break;
755 case NODE_RULE:
756 free(list->v[i].data.rule.targets.v);
757 free(list->v[i].data.rule.prereqs.v);
758 free(list->v[i].data.rule.order_only.v);
759 for (j = 0; j < list->v[i].data.rule.recipes.n; j++)
760 freesubmake(&list->v[i].data.rule.recipes.v[j].sm);
761 free(list->v[i].data.rule.recipes.v);
762 break;
763 case NODE_COND:
764 freenodes(&list->v[i].data.cond.thenpart);
765 freenodes(&list->v[i].data.cond.elsepart);
766 break;
767 case NODE_BLANK:
768 break;
769 }
770 }
771 free(list->v);
772 list->v = 0;
773 list->n = 0;
774 list->cap = 0;
775}
776
777void
778freeast(struct Ast *ast)
779{
780 freenodes((struct NodeList *)ast);
781 arena_free(&ast->arena);
782}