pathsys: cosmetix
[k8jam.git] / src / expand.c
blobf645f34a4d70cea5121f0b6318034894835697e1
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 int need_expand = 0;
249 if (*s == '\'') {
250 /* single-quoted string; the easiest thing */
251 for (++s; s < e; ++s) {
252 if (*s == '\'') {
253 ++s;
254 if (s >= e || *s != '\'') break;
256 if (rep != NULL) dstr_push_char(rep, *s++);
258 } else {
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 if (rep != NULL) 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': if (rep != NULL) dstr_push_char(rep, '\a'); break;
279 case 'b': if (rep != NULL) dstr_push_char(rep, '\b'); break;
280 case 'e': if (rep != NULL) dstr_push_char(rep, '\x1b'); break;
281 case 'f': if (rep != NULL) dstr_push_char(rep, '\f'); break;
282 case 'n': if (rep != NULL) dstr_push_char(rep, '\n'); break;
283 case 'r': if (rep != NULL) dstr_push_char(rep, '\r'); break;
284 case 't': if (rep != NULL) dstr_push_char(rep, '\t'); break;
285 case 'v': if (rep != NULL) 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 if (rep != NULL) 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 if (rep != NULL) dstr_push_char(rep, s[-1]);
303 break;
305 } else {
306 if (s+1 < e) ++s;
307 if (rep != NULL) 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 if (rep != NULL) dstr_push_char(rep, *s);
320 ++s;
323 /* now expand collected string */
324 if (rep != NULL && need_expand) {
325 LIST *l = var_expand(dstr_cstr(rep), NULL, lol, 0);
326 dstr_clear(rep);
327 if (l != L0) dstr_set_cstr(rep, l->string);
328 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 (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;