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}