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