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
22 * var_expand() - variable-expand input string into list of strings
43 * need_screen() - does the given char need to be screened?
45 static inline int need_screen (int ch
) {
48 if (ch
<= '*' || ch
== '`' || ch
== 127 ||
49 (ch
>= ';' && ch
<= '<') ||
50 (ch
>= '>' && ch
<= '?') ||
51 (ch
>= '[' && ch
<= ']') ||
52 (ch
>= '{' && ch
<= '}')) return 1;
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 */
75 if (s
[0] == ')') return s
+1; /* that's all folks */
77 if (s
[0] == '[' && state
< 2) {
78 if (state
== 1 || s
+1 >= e
) return NULL
; /* double indexing is no-no */
82 if (s
[0] == '$' && s
+1 < e
&& s
[1] == '(') {
83 if ((s
= skip_varaccess(s
, e
)) == NULL
) return NULL
;
86 if (s
[-1] == ']') break;
89 if (s
>= e
) return NULL
; /* bad luck */
92 /* postfix modifiers? */
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] == '=') {
102 if (++s
>= e
) return NULL
; /* bad luck */
103 /* here we can have quoted string or var access */
104 if (*s
== '"' || *s
== '\'') qch
= *s
++;
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
;
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] == '\'') {
119 ++s
; /* skip closing quote */
125 if (qch
!= '\'' && s
+1 < e
&& s
[0] == '\\') {
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 */
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 */
147 if (state
!= 0) return NULL
; /* bad luck: varname char in 'not varname' state */
150 /* we should never come here if there were no errors */
155 static const char *skip_varaccess (const char *s
, const char *e
) {
157 if (DEBUG_VAREXP
) printf(" skip_varaccess: '%.*s'\n", (int)(e
-s
), s
);
158 res
= skip_varaccess_intr(s
, e
);
160 if (res
== NULL
) printf(" FATAL: invalid var access!\n");
161 else printf(" skip_varaccess rest: '%.*s'\n", (int)(e
-res
), 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
) {
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 */
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 */
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);
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 */
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 */
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);
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;
223 * parse_modifier() - parse one modifier
225 * 's' can point either to modifier char or to ':'
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
;
238 if (mod
&& *s
!= mod
) rep
= NULL
; /* don't expand this */
239 if (mc
!= NULL
) *mc
= *s
;
241 if (s
< e
&& *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 */
249 /* single-quoted string; the easiest thing */
250 for (++s
; s
< e
; ++s
) {
253 if (s
>= e
|| *s
!= '\'') break;
255 dstr_push_char(rep
, *s
);
259 char qch
= (*s
== '"' ? '"' : 0);
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
);
274 if (qch
&& s
+1 < e
) {
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;
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); }
292 int d
= digit(*s
++, 16);
293 if (d
< 0) { printf("FATAL: invalid hex escape!\n"); exit(42); }
296 if (n
== 0) { printf("FATAL: invalid hex escape!\n"); exit(42); }
297 dstr_push_char(rep
, n
);
299 /*TODO: add '\uXXXX'?*/
301 if (isalnum(s
[-1])) { printf("FATAL: invalid escape: '%c'!\n", s
[-1]); exit(42); }
302 dstr_push_char(rep
, s
[-1]);
307 dstr_push_char(rep
, *s
);
312 /* end of quoted string? */
313 if (qch
&& s
[0] == qch
) {
319 dstr_push_char(rep
, *s
);
322 /* now expand collected string */
323 if (rep
!= NULL
&& need_expand
) {
324 LIST
*l
= var_expand(dstr_cstr(rep
), NULL
, lol
, 0);
326 if (l
!= L0
) dstr_set_cstr(rep
, l
->string
);
337 static int find_join (const char *s
, const char *e
, LOL
*lol
, dstring_t
*rep
) {
340 s
= parse_modifier(s
, e
, lol
, &mc
, rep
, NULL
);
341 if (s
== NULL
) abort(); /* the thing that should not be */
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! */
352 static int find_empty (const char *s
, const char *e
, LOL
*lol
, dstring_t
*rep
) {
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! */
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
) {
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
;
376 if (val
!= NULL
) *val
= n
;
379 if (val
!= NULL
) *val
= 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
) {
391 /* remove left part */
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
);
398 if ((was_wc
&0x02) && abs(marg_w
) > len
) {
400 dstr_reserve(str
, abs(marg_w
)+1); /* reserve room for terminating zero too */
402 /* add trailing spaces */
403 memset(str
->str
+len
, ' ', marg_w
-len
);
405 /* add leading spaces */
407 memmove(str
->str
+(marg_w
-len
), str
->str
, len
+1);
408 memset(str
->str
, ' ', marg_w
-len
);
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 */
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 */
432 /* some optimizations */
433 while (s
< e
&& *s
== ':') ++s
;
435 if (s
[0] == 'E' || s
[0] == 'J') {
437 s
= parse_modifier(s
, e
, lol
, NULL
, NULL
, NULL
);
438 if (s
== NULL
) abort(); /* the thing that should not be */
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; }
450 qchar
= (mvlen
> 0 ? mv
[0] : '\\');
452 shell_quote_set(qset
);
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;
462 if (s2i(&marg_w
, mv
)) was_wc
|= 0x02; else was_wc
&= ~0x02;
470 if (s2i(&marg_c
, mv
)) was_wc
|= 0x01; else was_wc
&= ~0x01;
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
) {
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) {
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;
494 if (wasass
) dstr_cpy(&s_grist
, &mval
);
498 if (wasass
) dstr_cpy(&s_root
, &mval
);
502 if (wasass
) dstr_cpy(&s_dir
, &mval
);
506 if (wasass
) dstr_cpy(&s_base
, &mval
);
509 pnp
.f_suffix
.len
= 1;
510 if (wasass
) dstr_cpy(&s_suffix
, &mval
);
513 pnp
.f_member
.len
= 1;
514 if (wasass
) dstr_cpy(&s_member
, &mval
);
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;
524 default: printf("FATAL: invalid selector: '%c'\n", mc
); exit(42);
530 /* at least one of part selectors was hit */
531 /* all parts that should be included have len > 0 */
532 static char fname
[MAXJPATH
];
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
);
545 dstr_done(&s_suffix
);
546 dstr_done(&s_member
);
547 dstr_set_cstr(&mval
, fname
);
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
);
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;
560 /* we need to expand/cut string, use temprorary storage */
564 /* copy 'ok' chars */
565 dstr_push_memrange(ts
, dstr_cstr(&mval
), t
);
567 /* need to do quoting */
569 if (qset
[*t
]) dstr_push_char(ts
, qchar
);
570 dstr_push_char(ts
, *t
);
573 /* apply width modifiers */
575 do_mod_cw(ts
, was_wc
, marg_w
, marg_c
);
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
));
581 dstr_push_buf(res
, dstr_cstr(ts
), dstr_len(ts
));
586 /* apply width modifiers */
588 do_mod_cw(&mval
, was_wc
, marg_w
, marg_c
);
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
));
601 * get everything before first varref to string (pref)
602 * get variable value (lval)
603 * recursively expand everything after varref if anything (lrest)
605 * foreach (l in lval) {
609 * foreach (r in lrest) {
610 * str1 = str+r->string;
611 * lnew = list_new(lnew, str1, 0);
614 * lnew = list_new(lnew, str, 0);
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
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
) {
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] == ')') {
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 */
655 if (in
[0] == '$' && in
[1] == '(') goto expand
;
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() */
663 res
= list_new(L0
, inp
, 1);
665 /*FIXME: make this faster!*/
666 /*FIXME: we should always have something at end[0]*/
668 intptr_t len
= end
-inp
;
670 /* non-empty string */
672 /* small string: use alloca() */
673 char *s
= alloca(len
+1);
676 res
= list_new(L0
, s
, 0);
678 /* big string: use malloc() */
679 char *s
= malloc(len
+1);
682 res
= list_new(L0
, s
, 0);
687 res
= list_new(L0
, "", 0);
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"); }
697 dstring_t prefix
, varspec
;
698 int prefix_len
; /* length of prefix -- for chopping */
699 LIST
*var_value
, *rest_list
;
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
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
);
717 if (DEBUG_VAREXP
) printf(" rest: '%.*s'\n", (int)(end
-rest
), rest
);
718 --rest
; /* don't take last ')' */
719 vnstart
= (in
+= 2); /* skip $ and '(' */
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.
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] == '(') {
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 */
739 const char *e
= skip_varaccess(in
, rest
);
741 printf("FATAL: invalid var access: '%.*s'\n", (int)(rest
-vnstart
), vnstart
);
746 if (in
[0] == ':' || in
[0] == '[') break;
747 if (in
[0] == '$' && in
+1 < rest
&& in
[1] == '(') break;
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);
756 printf("FATAL: trying to dereference NULL variable: '%.*s'\n", (int)(in
-vnstart
), vnstart
);
759 if (DEBUG_VAREXP
) printf(" varname_expand result: '%s'\n", vname
->string
);
760 dstr_set_cstr(&varspec
, vname
->string
);
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) {
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"); }
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 */
792 int vvlen
= list_length(var_value
);
794 while (in
< rest
&& in
[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;
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] == ']') {
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;
817 if (idx
< 0) idx
= 1;
818 if (iend
> vvlen
) iend
= vvlen
;
819 if (idx
<= vvlen
&& iend
>= idx
) {
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);
828 if (idx
>= 1 && idx
<= vvlen
) {
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); }
838 if (in
>= rest
|| in
[0] != ']') { printf("FATAL: invalid index (5): '%.*s'\n", (int)(rest
-vnstart
), vnstart
); exit(42); }
843 /* 'rest' points to ')' */
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"); }
849 /* this is the end my only friend */
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
));
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 */
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
);
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
);
876 var_value
= list_new(L0
, dstr_cstr(&nval
), 0);
879 /* assemble output */
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
);
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);
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
);
910 if (DEBUG_VAREXP
) { printf(" *expand_res (%d): ", list_length(res
)); list_print_ex(stdout
, res
, LPFLAG_NO_TRSPACE
); printf("\n"); }