experimental GDC dependencies scanner (turned off by default)
[k8jam.git] / src / expand.c
blobe374bb912df89c7000c8bd63e923fb70c98deddd
1 /* coded by Ketmar // Vampire Avalon (psyc://ketmar.no-ip.org/~Ketmar)
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 * expand.c - expand a buffer, given variable values
20 * External routines:
22 * var_expand() - variable-expand input string into list of strings
24 #include <alloca.h>
25 #include <ctype.h>
26 #include <stdlib.h>
27 #include <string.h>
29 #include <stdio.h>
30 #include <unistd.h>
32 #include "jam.h"
33 #include "lists.h"
34 #include "expand.h"
35 #include "newstr.h"
36 #include "dstrings.h"
38 #include "pathsys.h"
39 #include "variable.h"
43 * need_screen() - does the given char need to be screened?
45 static inline int need_screen (int ch) {
46 ch &= 0xff;
47 if (!ch) return 0;
48 if (ch <= '*' || ch == '`' || ch == 127 ||
49 (ch >= ';' && ch <= '<') ||
50 (ch >= '>' && ch <= '?') ||
51 (ch >= '[' && ch <= ']') ||
52 (ch >= '{' && ch <= '}')) return 1;
53 return 0;
57 static inline void shell_quote_set (char set[256]) {
58 for (int f = 0; f < 256; ++f) set[f] = need_screen(f);
62 static const char *skip_varaccess (const char *s, const char *e);
65 * skip_varaccess() - skip variable access
67 * 's' points to '$(...)'.
68 * return first char after '$(...)' or NULL on error
70 static const char *skip_varaccess_intr (const char *s, const char *e) {
71 int state = 0; /* 0: in varname; 1: in []; 2: in ':' */
72 s += (*s == '$' ? 2 : 1); /* skip '(' */
73 if (s >= e || s[0] == ')' || s[0] == ':' || s[0] == '[') return NULL; /* bad luck */
74 while (s < e) {
75 if (s[0] == ')') return s+1; /* that's all folks */
76 /* indexing? */
77 if (s[0] == '[' && state < 2) {
78 if (state == 1 || s+1 >= e) return NULL; /* double indexing is no-no */
79 state = 1;
80 ++s; /* skip '[' */
81 while (s < e) {
82 if (s[0] == '$' && s+1 < e && s[1] == '(') {
83 if ((s = skip_varaccess(s, e)) == NULL) return NULL;
84 } else {
85 ++s;
86 if (s[-1] == ']') break;
89 if (s >= e) return NULL; /* bad luck */
90 continue;
92 /* postfix modifiers? */
93 if (s[0] == ':') {
94 state = 2;
95 /* skip selectors */
96 for (;;) {
97 for (++s; s < e && s[0] >= 'A' && s[0] <= 'Z'; ++s) ;
98 if (s >= e) return NULL; /* bad luck */
99 if (s < e && s[0] == '=') {
100 /* assign */
101 char qch = 0;
102 if (++s >= e) return NULL; /* bad luck */
103 /* here we can have quoted string or var access */
104 if (*s == '"' || *s == '\'') qch = *s++;
105 while (s < e) {
106 if (!qch && s[0] == ')') return s+1; /* that's all folks */
107 if (qch != '\'' && s[0] == '$' && s+1 < e && s[1] == '(') {
108 /* variable access */
109 if ((s = skip_varaccess(s, e)) == NULL) return NULL;
110 continue;
112 if (!qch && s[0] == ':') break; /* another modifier */
113 if (qch && s[0] == qch) {
114 /* quoted string terminator */
115 if (qch == '\'' && s+1 < e && s[1] == '\'') {
116 /* "''" */
117 s += 2;
118 } else {
119 ++s; /* skip closing quote */
120 qch = 0;
121 break;
123 continue;
125 if (qch != '\'' && s+1 < e && s[0] == '\\') {
126 /* quoted char */
127 s += 2;
128 continue;
130 /* ordinary char */
131 ++s;
133 if (qch) return NULL; /* unclosed quoting */
135 if (s >= e || s[0] < 'A' || s[0] > 'Z') break;
136 /* we have another selector, do it all again */
138 continue;
140 /* var access? */
141 if (s[0] == '$' && s+1 < e && s[1] == '(') {
142 /* this is another var access, do recursive skip */
143 if ((s = skip_varaccess(s, e)) == NULL) return NULL; /* something goes wrong along the way */
144 continue;
146 /* var name */
147 if (state != 0) return NULL; /* bad luck: varname char in 'not varname' state */
148 ++s;
150 /* we should never come here if there were no errors */
151 return NULL;
155 static const char *skip_varaccess (const char *s, const char *e) {
156 const char *res;
157 if (DEBUG_VAREXP) printf(" skip_varaccess: '%.*s'\n", (int)(e-s), s);
158 res = skip_varaccess_intr(s, e);
159 if (DEBUG_VAREXP) {
160 if (res == NULL) printf(" FATAL: invalid var access!\n");
161 else printf(" skip_varaccess rest: '%.*s'\n", (int)(e-res), res);
163 return res;
167 /* this function used to parse indicies in "[]" */
168 /* returns either NULL on error or pointer to terminator char (with trailing spaces skipped) */
169 static const char *parse_number (int *num, const char *s, const char *e, LOL *lol) {
170 int neg = 0, n = 0;
171 // if (DEBUG_VAREXP) printf("parse_number: '%.*s'\n", (int)(e-s), s);
172 for (; s < e && isspace(*s); ++s) ; /* skip leading spaces */
173 if (s < e && s[0] == '-') { neg = 1; ++s; }
174 if (s >= e) return NULL; /* bad luck */
175 //if (DEBUG_VAREXP) printf(" 1:parse_number: '%.*s'\n", (int)(e-s), s);
176 /* this must be either variable access or number */
177 if (isdigit(*s)) {
178 /* number */
179 for (; s < e && isdigit(*s); ++s) n = n*10+s[0]-'0';
180 //if (DEBUG_VAREXP) printf(" 2:parse_number: '%.*s'\n", (int)(e-s), s);
181 } else if (s[0] == '$' && s+1 < e && s[1] == '(') {
182 /* variable access */
183 LIST *vv;
184 const char *ve = skip_varaccess(s, e);
185 if (ve == NULL) return NULL; /* something is wrong */
186 //if (DEBUG_VAREXP) printf(" 3:parse_number: '%.*s'\n", (int)(ve-s), s);
187 vv = var_expand(s, ve, lol, 0);
188 s = ve;
189 if (vv != L0 && vv->string[0]) {
190 /* convert expanded value to number */
191 /* note that bad number means 'item zero' here, which is always nothing */
192 const char *t;
193 for (t = vv->string; *t && isspace(*t); ++t) ;
194 if (*t == '-') { neg = !neg; ++t; } /* we can have preceding '-', so use '!' */
195 for (; *t && isdigit(*t); ++t) n = n*10+t[0]-'0';
196 for (; *t && isspace(*t); ++t) ;
197 if (*t) n = 0; /* this is not a number, so reset index */
199 list_free(vv);
200 } else {
201 return NULL; /* bad luck, have to have something here */
203 for (; s < e && isspace(*s); ++s) ; /* skip trailing spaces */
204 if (s >= e) return NULL; /* bad luck */
205 //if (DEBUG_VAREXP) printf(" 4:parse_number: '%.*s'\n", (int)(e-s), s);
206 if (neg) n = -n;
207 *num = n;
208 return s;
212 static int digit (int c, int base) {
213 if (c == EOF) return -1;
214 if (c >= 'a' && c <= 'z') c -= 32;
215 if (c < '0' || (c > '9' && c < 'A') || c > 'Z') return -1;
216 if ((c -= '0') > 9) c -= 7;
217 if (c >= base) return -1;
218 return c;
223 * parse_modifier() - parse one modifier
225 * 's' can point either to modifier char or to ':'
226 * 'rep' can be NULL
227 * 'mc' can be NULL, if it is not NULL and '*mc' != 0: expand only this modifier
228 * if 'wasrep' is not NULL, set it to !0 if we have '=' here (and store everything after '=' in 'rep')
229 * set 'mc' to modifier char
231 static const char *parse_modifier (const char *s, const char *e, LOL *lol, char *mc, dstring_t *rep, int *wasrep) {
232 int mod = (mc != NULL ? *mc : 0);
233 if (mc != NULL) *mc = 0;
234 if (wasrep != NULL) *wasrep = 0;
235 if (rep != NULL) dstr_clear(rep);
236 while (s < e && *s == ':') ++s;
237 if (s < e) {
238 if (mod && *s != mod) rep = NULL; /* don't expand this */
239 if (mc != NULL) *mc = *s;
240 if (*s != '=') ++s;
241 if (s < e && *s == '=') {
242 ++s;
243 if (wasrep != NULL) *wasrep = 1;
244 /* now parse replacement string; note that we can have variables in it */
245 /* do it easy way: collect string and pass it to var_expand() */
246 /* note that quoting works only for the whole string, not for string parts */
247 if (s < e) {
248 if (*s == '\'') {
249 /* single-quoted string; the easiest thing */
250 for (++s; s < e; ++s) {
251 if (*s == '\'') {
252 ++s;
253 if (s >= e || *s != '\'') break;
255 dstr_push_char(rep, *s);
257 } else {
258 int need_expand = 0;
259 char qch = (*s == '"' ? '"' : 0);
260 if (qch) ++s;
261 while (s < e) {
262 if (!qch && s[0] == ':') break;
263 if (s[0] == '$' && s+1 < e && s[1] == '(') {
264 /* variable access */
265 const char *ve = skip_varaccess(s, e);
266 if (ve == NULL) { printf("FATAL: invalid var access: '%.*s'\n", (int)(e-s), s); exit(42); }
267 dstr_push_memrange(rep, s, ve);
268 need_expand = 1;
269 s = ve;
270 continue;
272 if (s[0] == '\\') {
273 /* screened char */
274 if (qch && s+1 < e) {
275 int n;
276 ++s;
277 switch (*s++) {
278 case 'a': dstr_push_char(rep, '\a'); break;
279 case 'b': dstr_push_char(rep, '\b'); break;
280 case 'e': dstr_push_char(rep, '\x1b'); break;
281 case 'f': dstr_push_char(rep, '\f'); break;
282 case 'n': dstr_push_char(rep, '\n'); break;
283 case 'r': dstr_push_char(rep, '\r'); break;
284 case 't': dstr_push_char(rep, '\t'); break;
285 case 'v': dstr_push_char(rep, '\v'); break;
286 case 'x':
287 if (s >= e) { printf("FATAL: invalid hex escape!\n"); exit(42); }
288 n = digit(*s++, 16); /* first digit */
289 if (n < 0) { printf("FATAL: invalid hex escape!\n"); exit(42); }
290 if (s < e) {
291 /* second digit */
292 int d = digit(*s++, 16);
293 if (d < 0) { printf("FATAL: invalid hex escape!\n"); exit(42); }
294 n = n*16+d;
296 if (n == 0) { printf("FATAL: invalid hex escape!\n"); exit(42); }
297 dstr_push_char(rep, n);
298 break;
299 /*TODO: add '\uXXXX'?*/
300 default:
301 if (isalnum(s[-1])) { printf("FATAL: invalid escape: '%c'!\n", s[-1]); exit(42); }
302 dstr_push_char(rep, s[-1]);
303 break;
305 } else {
306 if (s+1 < e) ++s;
307 dstr_push_char(rep, *s);
308 ++s;
310 continue;
312 /* end of quoted string? */
313 if (qch && s[0] == qch) {
314 qch = 0;
315 ++s;
316 break;
318 /* normal char */
319 dstr_push_char(rep, *s);
320 ++s;
322 /* now expand collected string */
323 if (rep != NULL && need_expand) {
324 LIST *l = var_expand(dstr_cstr(rep), NULL, lol, 0);
325 dstr_clear(rep);
326 if (l != L0) dstr_set_cstr(rep, l->string);
327 list_free(l);
333 return s;
337 static int find_join (const char *s, const char *e, LOL *lol, dstring_t *rep) {
338 while (s < e) {
339 char mc = 'J';
340 s = parse_modifier(s, e, lol, &mc, rep, NULL);
341 if (s == NULL) abort(); /* the thing that should not be */
342 if (mc == 'J') {
343 if (strcmp(dstr_cstr(rep), "|space|") == 0) dstr_set_cstr(rep, " ");
344 else if (strcmp(dstr_cstr(rep), "|tab|") == 0) dstr_set_cstr(rep, "\t");
345 return 1; /* got 'join', wow! */
348 return 0;
352 static int find_empty (const char *s, const char *e, LOL *lol, dstring_t *rep) {
353 while (s < e) {
354 char mc = 'E';
355 s = parse_modifier(s, e, lol, &mc, rep, NULL);
356 if (s == NULL) abort(); /* the thing that should not be */
357 if (mc == 'E') return 1; /* got 'empty', wow! */
359 return 0;
363 static inline void dstr_cpy (dstring_t *dest, dstring_t *src) { dstr_set_buf(dest, dstr_cstr(src), dstr_len(src)); }
366 /* returns boolean: success */
367 /*TODO: check overflow */
368 static inline int s2i (int *val, const char *s) {
369 if (s != NULL) {
370 int n = 0, neg = 0;
371 while (*s && isspace(*s)) ++s;
372 if (*s == '-') { neg = 1; ++s; } else if (*s == '+') ++s;
373 for (; *s && isdigit(*s); ++s) n = n*10+s[0]-'0';
374 while (*s && isspace(*s)) ++s;
375 if (neg) n = -n;
376 if (val != NULL) *val = n;
377 return (*s == 0);
378 } else {
379 if (val != NULL) *val = 0;
380 return 0;
385 /*FIXME: this is full of hacks using internal dstring structures! add necessary API to dstrings and remove hacks!*/
386 static void do_mod_cw (dstring_t *str, int was_wc, int marg_w, int marg_c) {
387 int len = dstr_len(str);
388 if ((was_wc&0x01) && abs(marg_c) < len) {
389 /* apply 'C' */
390 if (marg_c < 0) {
391 /* remove left part */
392 marg_c = -marg_c;
393 memmove(str->str, str->str+str->len-marg_c, marg_c+1); /* take terminating zero too */
394 } /* removing right part don't need any hackery */
395 dstr_chop(str, marg_c);
396 len = marg_c;
398 if ((was_wc&0x02) && abs(marg_w) > len) {
399 /* apply 'W' */
400 dstr_reserve(str, abs(marg_w)+1); /* reserve room for terminating zero too */
401 if (marg_w > 0) {
402 /* add trailing spaces */
403 memset(str->str+len, ' ', marg_w-len);
404 } else {
405 /* add leading spaces */
406 marg_w = -marg_w;
407 memmove(str->str+(marg_w-len), str->str, len+1);
408 memset(str->str, ' ', marg_w-len);
410 str->len = marg_w;
411 str->str[marg_w] = 0; /* add terminating zero */
416 /* modifiers "ULQ" applies after all others */
417 static void process_selectors (dstring_t *res, const char *s, const char *e, LOL *lol, const char *vval) {
418 /* no recursive calls to process_selectors() allowed, so we can make all vars static */
419 int doupdown = 0; /* <0: down; >0: up */
420 char qchar = 0; /* quoting char; 0: don't quote */
421 int was_wc = 0; /* bit 0: have ':C=', bit 1: have ':W=', bit 2: have ':W' */
422 int marg_w = 0, marg_c = 0; /* args for ':W' and ':C' */
423 int pnp_inited = 0; /* 1: inited, no ':x' (w/o assign) was hit; 2: ':x' (w/o assign) was hit */
424 static PATHNAME pnp;
425 static dstring_t s_grist, s_root, s_dir, s_base, s_suffix, s_member; /* initialized if pnp_inited != 0 */
426 static dstring_t mval;
427 static char qset[256]; /* chars to quote; initialized if qchar != 0 */
428 dstr_init(&mval);
429 while (s < e) {
430 char mc = 0;
431 int wasass = 0;
432 /* some optimizations */
433 while (s < e && *s == ':') ++s;
434 if (s >= e) break;
435 if (s[0] == 'E' || s[0] == 'J') {
436 /* skip this ones */
437 s = parse_modifier(s, e, lol, NULL, NULL, NULL);
438 if (s == NULL) abort(); /* the thing that should not be */
439 continue;
441 s = parse_modifier(s, e, lol, &mc, &mval, &wasass);
442 if (DEBUG_VAREXP) printf("mod: '%c'; len=%d; s=<%s>\n", mc, dstr_len(&mval), dstr_cstr(&mval));
443 if (s == NULL) abort(); /* the thing that should not be */
445 int mvlen = (wasass ? dstr_len(&mval) : 0);
446 const char *mv = (wasass ? dstr_cstr(&mval) : "");
447 if (mc == 'U') { doupdown = 1; continue; }
448 if (mc == 'L') { doupdown = -1; continue; }
449 if (mc == 'Q') {
450 qchar = (mvlen > 0 ? mv[0] : '\\');
451 if (mvlen < 2) {
452 shell_quote_set(qset);
453 } else {
454 /* first char is quoting char, others are chars to be quoted */
455 memset(qset, 0, 256);
456 for (++mv; *mv; ++mv) qset[(unsigned char)(mv[0])] = 1;
458 continue;
460 if (mc == 'W') {
461 if (wasass) {
462 if (s2i(&marg_w, mv)) was_wc |= 0x02; else was_wc &= ~0x02;
463 } else {
464 was_wc |= 0x04;
466 continue;
468 if (mc == 'C') {
469 if (wasass) {
470 if (s2i(&marg_c, mv)) was_wc |= 0x01; else was_wc &= ~0x01;
472 continue;
474 if (strchr("GRDBSM", mc) == NULL) { printf("FATAL: invalid selector: '%c'\n", mc); exit(42); }
475 /* parse value if it is necessary */
476 if (!pnp_inited && strchr("GRDBSM", mc) != NULL) {
477 pnp_inited = 1;
478 path_parse(vval, &pnp);
479 dstr_init_buf(&s_grist, pnp.f_grist.ptr, pnp.f_grist.len);
480 dstr_init_buf(&s_root, pnp.f_root.ptr, pnp.f_root.len);
481 dstr_init_buf(&s_dir, pnp.f_dir.ptr, pnp.f_dir.len);
482 dstr_init_buf(&s_base, pnp.f_base.ptr, pnp.f_base.len);
483 dstr_init_buf(&s_suffix, pnp.f_suffix.ptr, pnp.f_suffix.len);
484 dstr_init_buf(&s_member, pnp.f_member.ptr, pnp.f_member.len);
486 /* first ':x' (w/o assign) hit -- clear all parts */
487 if (!wasass && pnp_inited == 1) {
488 pnp_inited = 2;
489 pnp.f_grist.len = pnp.f_root.len = pnp.f_dir.len = pnp.f_base.len = pnp.f_suffix.len = pnp.f_member.len = 0;
491 switch (mc) {
492 case 'G':
493 pnp.f_grist.len = 1;
494 if (wasass) dstr_cpy(&s_grist, &mval);
495 break;
496 case 'R':
497 pnp.f_root.len = 1;
498 if (wasass) dstr_cpy(&s_root, &mval);
499 break;
500 case 'D':
501 pnp.f_dir.len = 1;
502 if (wasass) dstr_cpy(&s_dir, &mval);
503 break;
504 case 'B':
505 pnp.f_base.len = 1;
506 if (wasass) dstr_cpy(&s_base, &mval);
507 break;
508 case 'S':
509 pnp.f_suffix.len = 1;
510 if (wasass) dstr_cpy(&s_suffix, &mval);
511 break;
512 case 'M':
513 pnp.f_member.len = 1;
514 if (wasass) dstr_cpy(&s_member, &mval);
515 break;
517 case 'P':
518 if (wasass) { printf("FATAL: invalid selector assign: 'P'\n"); exit(42); }
519 //if (edits->parent) path_parent(&pathname);
520 pnp.f_grist.len = pnp.f_root.len = pnp.f_dir.len = 1;
521 pnp.f_base.len = pnp.f_suffix.len = pnp.f_member.len = 0;
522 break;
524 default: printf("FATAL: invalid selector: '%c'\n", mc); exit(42);
528 /* rebuild string */
529 if (pnp_inited) {
530 /* at least one of part selectors was hit */
531 /* all parts that should be included have len > 0 */
532 static char fname[MAXJPATH];
533 /* grist */
534 if (pnp.f_grist.len > 0) { pnp.f_grist.ptr = dstr_cstr(&s_grist); pnp.f_grist.len = dstr_len(&s_grist); }
535 if (pnp.f_root.len > 0) { pnp.f_root.ptr = dstr_cstr(&s_root); pnp.f_root.len = dstr_len(&s_root); }
536 if (pnp.f_dir.len > 0) { pnp.f_dir.ptr = dstr_cstr(&s_dir); pnp.f_dir.len = dstr_len(&s_dir); }
537 if (pnp.f_base.len > 0) { pnp.f_base.ptr = dstr_cstr(&s_base); pnp.f_base.len = dstr_len(&s_base); }
538 if (pnp.f_suffix.len > 0) { pnp.f_suffix.ptr = dstr_cstr(&s_suffix); pnp.f_suffix.len = dstr_len(&s_suffix); }
539 if (pnp.f_member.len > 0) { pnp.f_member.ptr = dstr_cstr(&s_member); pnp.f_member.len = dstr_len(&s_member); }
540 path_build(fname, &pnp);
541 dstr_done(&s_grist);
542 dstr_done(&s_root);
543 dstr_done(&s_dir);
544 dstr_done(&s_base);
545 dstr_done(&s_suffix);
546 dstr_done(&s_member);
547 dstr_set_cstr(&mval, fname);
548 } else {
549 dstr_set_cstr(&mval, vval);
551 /* do case conversion */
552 if (doupdown < 0) for (char *p = dstr_cstr(&mval); *p; ++p) *p = tolower(*p);
553 if (doupdown > 0) for (char *p = dstr_cstr(&mval); *p; ++p) *p = toupper(*p);
554 /* do quoting */
555 if (qchar) {
556 dstring_t tval, *ts = res;
557 const unsigned char *t = (const unsigned char *)(dstr_cstr(&mval));
558 for (; *t; ++t) if (qset[*t]) break;
559 if (was_wc&0x7) {
560 /* we need to expand/cut string, use temprorary storage */
561 ts = &tval;
562 dstr_init(ts);
564 /* copy 'ok' chars */
565 dstr_push_memrange(ts, dstr_cstr(&mval), t);
566 if (*t) {
567 /* need to do quoting */
568 for (; *t; ++t) {
569 if (qset[*t]) dstr_push_char(ts, qchar);
570 dstr_push_char(ts, *t);
573 /* apply width modifiers */
574 if (was_wc&0x7) {
575 do_mod_cw(ts, was_wc, marg_w, marg_c);
576 if (was_wc&0x04) {
577 snprintf(qset, sizeof(qset), "%d", dstr_len(ts));
578 dstr_set_cstr(&mval, qset);
579 dstr_push_buf(res, dstr_cstr(&mval), dstr_len(&mval));
580 } else {
581 dstr_push_buf(res, dstr_cstr(ts), dstr_len(ts));
583 dstr_done(ts);
585 } else {
586 /* apply width modifiers */
587 if (was_wc&0x7) {
588 do_mod_cw(&mval, was_wc, marg_w, marg_c);
589 if (was_wc&0x04) {
590 snprintf(qset, sizeof(qset), "%d", dstr_len(&mval));
591 dstr_set_cstr(&mval, qset);
594 dstr_push_buf(res, dstr_cstr(&mval), dstr_len(&mval));
596 dstr_done(&mval);
601 * get everything before first varref to string (pref)
602 * get variable value (lval)
603 * recursively expand everything after varref if anything (lrest)
604 * lnew = L0;
605 * foreach (l in lval) {
606 * str = pref;
607 * str += l->string;
608 * if (lrest) {
609 * foreach (r in lrest) {
610 * str1 = str+r->string;
611 * lnew = list_new(lnew, str1, 0);
613 * } else {
614 * lnew = list_new(lnew, str, 0);
617 * list_free(lrest)
618 * list_free(lval)
619 * return lnew;
624 * var_expand() - variable-expand input string into list of strings
626 * Would just copy input to output, performing variable expansion,
627 * except that since variables can contain multiple values the result
628 * of variable expansion may contain multiple values (a list). Properly
629 * performs "product" operations that occur in "$(var1)xxx$(var2)" or
630 * even "$($(var2))".
632 * Returns a newly created list.
634 * `end` can be NULL, it means: `in` is cstr.
636 LIST *var_expand (const char *in, const char *end, LOL *lol, int cancopyin) {
637 LIST *res;
638 const char *inp = in;
639 if (end == NULL) end = in+strlen(in);
640 if (DEBUG_VAREXP) printf("***expand '%.*s'\n", (int)(end-in), in);
641 /* this gets alot of cases: $(<), $(>), $(1)...$(9) */
642 if (end-in == 4 && in[0] == '$' && in[1] == '(' && in[3] == ')') {
643 switch (in[2]) {
644 case '0': return L0;
645 case '<': return list_copy(L0, lol_get(lol, 0));
646 case '>': return list_copy(L0, lol_get(lol, 1));
647 default: if (isdigit(in[2])) return list_copy(L0, lol_get(lol, in[2]-'1'));
650 /* another aliases */
651 if (end-in == 5 && memcmp(in, "$(in)", 5) == 0) return list_copy(L0, lol_get(lol, 1));
652 if (end-in == 6 && memcmp(in, "$(out)", 6) == 0) return list_copy(L0, lol_get(lol, 0));
653 /* if there is no '$(' -- just copy */
654 while (in < end-1) {
655 if (in[0] == '$' && in[1] == '(') goto expand;
656 ++in;
658 /* no variables expanded - just add copy of input string to list */
659 /* cancopyin is an optimization: if the input was already a list */
660 /* item, we can use the copystr() to put it on the new list, */
661 /* otherwise, we use the slower newstr() */
662 if (cancopyin) {
663 res = list_new(L0, inp, 1);
664 } else {
665 /*FIXME: make this faster!*/
666 /*FIXME: we should always have something at end[0]*/
667 if (*end) {
668 intptr_t len = end-inp;
669 if (len > 0) {
670 /* non-empty string */
671 if (len < 512) {
672 /* small string: use alloca() */
673 char *s = alloca(len+1);
674 memcpy(s, inp, len);
675 s[len] = 0;
676 res = list_new(L0, s, 0);
677 } else {
678 /* big string: use malloc() */
679 char *s = malloc(len+1);
680 memcpy(s, inp, len);
681 s[len] = 0;
682 res = list_new(L0, s, 0);
683 free(s);
685 } else {
686 /* empty string */
687 res = list_new(L0, "", 0);
689 } else {
690 /* string ends with zero char */
691 res = list_new(L0, inp, 0);
694 if (DEBUG_VAREXP) { printf(" **expand_res (%d): ", list_length(res)); list_print_ex(stdout, res, LPFLAG_NO_TRSPACE); printf("\n"); }
695 return res;
696 expand: ;
697 dstring_t prefix, varspec;
698 int prefix_len; /* length of prefix -- for chopping */
699 LIST *var_value, *rest_list;
700 const char *vnstart;
701 const char *rest; /* this will point just after the "$(...)" */
702 int free_var_value = 0;
704 * Input so far (ignore blanks):
706 * stuff-in-outbuf $(variable) remainder
707 * ^ ^
708 * in end
709 * Output so far: nothing
711 dstr_init_memrange(&prefix, inp, in);
712 prefix_len = dstr_len(&prefix);
713 if ((rest = skip_varaccess(in, end)) == NULL) {
714 printf("FATAL: invalid var access: '%.*s'\n", (int)(end-in), in);
715 exit(42);
717 if (DEBUG_VAREXP) printf(" rest: '%.*s'\n", (int)(end-rest), rest);
718 --rest; /* don't take last ')' */
719 vnstart = (in += 2); /* skip $ and '(' */
720 dstr_init(&varspec);
722 * check if we have any $(...) before rest or ':' or '['.
723 * if not, we can just use var_get() to get var value, else
724 * we have to do complex things.
726 while (in < rest) {
727 if (in[0] == ':' || in[0] == '[') break;
728 dstr_push_char(&varspec, *in++);
729 if (in[-1] == '$' && in < rest && in[0] == '(') break;
731 /* here 'in' either >= 'rest' or points to '(' after '$' or points to ':' or points to '[' */
732 if (in < rest && in[0] == '(') {
733 LIST *vname;
734 /* we have to extract everything before ':' or '[' and expand it, then use var_get() to get resulting value */
735 /* skip all var accessing till ':', '[' or EOL */
736 --in; /* return to '$' */
737 /* collect complex varname */
738 for (;;) {
739 const char *e = skip_varaccess(in, rest);
740 if (e == NULL) {
741 printf("FATAL: invalid var access: '%.*s'\n", (int)(rest-vnstart), vnstart);
742 exit(42);
744 in = e;
745 while (in < rest) {
746 if (in[0] == ':' || in[0] == '[') break;
747 if (in[0] == '$' && in+1 < rest && in[1] == '(') break;
748 ++in;
750 /* here 'in' either >= 'rest' or points or '$(' or to terminator */
751 if (in >= rest || in[0] != '$') break;
753 if (DEBUG_VAREXP) printf(" varname_expand: '%.*s'\n", (int)(in-vnstart), vnstart);
754 vname = var_expand(vnstart, in, lol, 0);
755 if (!vname) {
756 printf("FATAL: trying to dereference NULL variable: '%.*s'\n", (int)(in-vnstart), vnstart);
757 exit(42);
759 if (DEBUG_VAREXP) printf(" varname_expand result: '%s'\n", vname->string);
760 dstr_set_cstr(&varspec, vname->string);
761 list_free(vname);
763 /* get variable value, check for special vars */
764 if (DEBUG_VAREXP) printf(" var_get: '%s'\n", dstr_cstr(&varspec));
766 const char *vn = dstr_cstr(&varspec);
767 if (!vn[1] && isdigit(vn[0])) var_value = (vn[0] == '0' ? L0 : lol_get(lol, vn[0]-'1'));
768 else if ((!vn[1] && vn[0] == '<') || strcmp(vn, "out") == 0) var_value = lol_get(lol, 0);
769 else if ((!vn[1] && vn[0] == '>') || strcmp(vn, "in") == 0) var_value = lol_get(lol, 1);
770 else var_value = var_get(dstr_cstr(&varspec));
773 * here 'in' either >= 'rest' or points to ':' or points to '['
774 * prefix is literal prefix
775 * var_value is variable value (NOT COPIED!)
777 * do indexing first and then selectors
779 if (DEBUG_VAREXP) { printf(" var_value (%d): ", list_length(var_value)); list_print_ex(stdout, var_value, LPFLAG_NO_TRSPACE); printf("\n"); }
780 /* optimization: if this is simple var access, just return it's value */
781 if (in >= end && dstr_len(&prefix) == 0) {
782 dstr_done(&varspec);
783 dstr_done(&prefix);
784 res = list_copy(L0, var_value);
785 if (DEBUG_VAREXP) { printf(" *(s)expand_res (%d): ", list_length(res)); list_print_ex(stdout, res, LPFLAG_NO_TRSPACE); printf("\n"); }
786 return res;
788 if (in < rest && in[0] == '[') {
789 /* process indexing */
790 /* here we have special rules for variable expansion: '-' and ',' are terminators, leading and trailing spaces ignored */
791 LIST *nval = L0;
792 int vvlen = list_length(var_value);
793 ++in; /* skip '[' */
794 while (in < rest && in[0] != ']') {
795 int idx = 0;
796 for (; in < rest && *in && isspace(*in); ++in) ; /* skip leading spaces */
797 if (in < rest && in[0] == ',') { ++in; continue; } /* skip excessive commas */
798 /* go till terminator */
799 if (in >= rest) { printf("FATAL: invalid index (0): '%.*s'\n", (int)(rest-vnstart), vnstart); exit(42); }
800 if (in[0] == ']') break;
801 /* this must be number */
802 if ((in = parse_number(&idx, in, rest, lol)) == NULL) { printf("FATAL: invalid index (1): '%.*s'\n", (int)(rest-vnstart), vnstart); exit(42); }
803 if (idx < 0 && (idx += vvlen+1) < 0) idx = 0;
804 if (in[0] == '-') {
805 /* this is range */
806 int iend;
807 ++in; /* skip '-' */
808 for (; in < rest && *in && isspace(*in); ++in) ; /* skip leading spaces */
809 if (in >= rest) { printf("FATAL: invalid index (2): '%.*s'\n", (int)(rest-vnstart), vnstart); exit(42); }
810 if (in[0] == ',' || in[0] == ']') {
811 iend = vvlen;
812 } else {
813 if ((in = parse_number(&iend, in, rest, lol)) == NULL) { printf("FATAL: invalid index (3): '%.*s'\n", (int)(rest-vnstart), vnstart); exit(42); }
814 if (iend < 0 && (iend += vvlen+1) < 0) iend = 0;
816 if (iend > 0) {
817 if (idx < 0) idx = 1;
818 if (iend > vvlen) iend = vvlen;
819 if (idx <= vvlen && iend >= idx) {
820 LIST *i;
821 iend -= idx; /* convert to length-1 */
822 for (i = var_value; --idx > 0; i = i->next) ;
823 for (; iend-- >= 0; i = i->next) nval = list_new(nval, i->string, 1);
826 } else {
827 /* single index */
828 if (idx >= 1 && idx <= vvlen) {
829 LIST *i;
830 for (i = var_value; --idx > 0; i = i->next) ;
831 nval = list_new(nval, i->string, 1);
834 if (in < rest && in[0] == ']') break; /* done */
835 if (in >= rest || in[0] != ',') { printf("FATAL: invalid index (4): '%.*s'\n", (int)(rest-vnstart), vnstart); exit(42); }
836 ++in; /* skip ',' */
838 if (in >= rest || in[0] != ']') { printf("FATAL: invalid index (5): '%.*s'\n", (int)(rest-vnstart), vnstart); exit(42); }
839 ++in; /* skip ']' */
840 free_var_value = 1;
841 var_value = nval;
843 /* 'rest' points to ')' */
844 if (rest+1 < end) {
845 /* we have something after the var, expand it */
846 rest_list = var_expand(rest+1, end, lol, 0);
847 if (DEBUG_VAREXP) { printf(" rest_list: (%d): ", list_length(rest_list)); list_print_ex(stdout, rest_list, LPFLAG_NO_TRSPACE); printf("\n"); }
848 } else {
849 /* this is the end my only friend */
850 rest_list = L0;
852 /* if we have ':E' modifier, apply it */
853 if (var_value == L0 && in < rest && in[0] == ':' && find_empty(in, rest, lol, &varspec)) {
854 if (DEBUG_VAREXP) printf(" E: '%s'\n", dstr_cstr(&varspec));
855 free_var_value = 1;
856 var_value = list_new(L0, dstr_cstr(&varspec), 0);
858 /* if we have ':J' modifier, join all values */
859 if (var_value != L0 && var_value->next && in < rest && in[0] == ':' && find_join(in, rest, lol, &varspec)) {
860 /* join var_value, processing selectors by the way */
861 dstring_t nval;
862 dstr_init(&nval);
863 if (DEBUG_VAREXP) printf(" JOIN: '%s'\n", dstr_cstr(&varspec));
864 for (LIST *cv = var_value; cv != NULL; cv = cv->next) {
865 /*int olen = dstr_len(&nval);*/
866 if (in < rest && in[0] == ':') {
867 process_selectors(&nval, in, rest, lol, cv->string);
868 } else {
869 dstr_push_cstr(&nval, cv->string);
871 /* skip empty strings */
872 if (cv->next != NULL /*&& dstr_len(&nval) > olen*/) dstr_push_cstr(&nval, dstr_cstr(&varspec));
874 if (free_var_value) list_free(var_value);
875 free_var_value = 1;
876 var_value = list_new(L0, dstr_cstr(&nval), 0);
877 dstr_done(&nval);
879 /* assemble output */
880 res = L0;
881 /* for each item in var_value list */
882 for (LIST *cv = var_value; cv != NULL; cv = cv->next) {
883 /* we have to process selectors seperately for each item */
884 if (in < rest && in[0] == ':') {
885 process_selectors(&prefix, in, rest, lol, cv->string);
886 } else {
887 dstr_push_cstr(&prefix, cv->string);
889 /* here 'prefix' actually contains "prefix"+'current_value' */
890 /* have something in rest_list? */
891 if (rest_list != L0) {
892 /* yes, combine current prefix with each rest_list item */
893 int ol = dstr_len(&prefix);
894 for (LIST *rl = rest_list; rl != NULL; rl = rl->next) {
895 dstr_chop(&prefix, ol);
896 dstr_push_cstr(&prefix, rl->string);
897 res = list_new(res, dstr_cstr(&prefix), 0);
899 } else {
900 /* just add prefix */
901 res = list_new(res, dstr_cstr(&prefix), 0);
903 /* restore prefix and process next item */
904 dstr_chop(&prefix, prefix_len);
906 list_free(rest_list);
907 if (free_var_value) list_free(var_value);
908 dstr_done(&varspec);
909 dstr_done(&prefix);
910 if (DEBUG_VAREXP) { printf(" *expand_res (%d): ", list_length(res)); list_print_ex(stdout, res, LPFLAG_NO_TRSPACE); printf("\n"); }
911 return res;