mirror of
git://git.code.sf.net/p/zsh/code
synced 2024-11-19 13:33:52 +01:00
69b4b8bdde
Separate justification width of parameters from base/precision.
3058 lines
76 KiB
C
3058 lines
76 KiB
C
/*
|
|
* subst.c - various substitutions
|
|
*
|
|
* This file is part of zsh, the Z shell.
|
|
*
|
|
* Copyright (c) 1992-1997 Paul Falstad
|
|
* All rights reserved.
|
|
*
|
|
* Permission is hereby granted, without written agreement and without
|
|
* license or royalty fees, to use, copy, modify, and distribute this
|
|
* software and to distribute modified versions of this software for any
|
|
* purpose, provided that the above copyright notice and the following
|
|
* two paragraphs appear in all copies of this software.
|
|
*
|
|
* In no event shall Paul Falstad or the Zsh Development Group be liable
|
|
* to any party for direct, indirect, special, incidental, or consequential
|
|
* damages arising out of the use of this software and its documentation,
|
|
* even if Paul Falstad and the Zsh Development Group have been advised of
|
|
* the possibility of such damage.
|
|
*
|
|
* Paul Falstad and the Zsh Development Group specifically disclaim any
|
|
* warranties, including, but not limited to, the implied warranties of
|
|
* merchantability and fitness for a particular purpose. The software
|
|
* provided hereunder is on an "as is" basis, and Paul Falstad and the
|
|
* Zsh Development Group have no obligation to provide maintenance,
|
|
* support, updates, enhancements, or modifications.
|
|
*
|
|
*/
|
|
|
|
#include "zsh.mdh"
|
|
#include "subst.pro"
|
|
|
|
/**/
|
|
char nulstring[] = {Nularg, '\0'};
|
|
|
|
/* Do substitutions before fork. These are:
|
|
* - Process substitution: <(...), >(...), =(...)
|
|
* - Parameter substitution
|
|
* - Command substitution
|
|
* Followed by
|
|
* - Quote removal
|
|
* - Brace expansion
|
|
* - Tilde and equals substitution
|
|
*
|
|
* PF_* flags are defined in zsh.h
|
|
*/
|
|
|
|
/**/
|
|
mod_export void
|
|
prefork(LinkList list, int flags)
|
|
{
|
|
LinkNode node, stop = 0;
|
|
int keep = 0, asssub = (flags & PF_TYPESET) && isset(KSHTYPESET);
|
|
|
|
queue_signals();
|
|
for (node = firstnode(list); node; incnode(node)) {
|
|
char *str, c;
|
|
|
|
str = (char *)getdata(node);
|
|
if (((c = *str) == Inang || c == Outang || c == Equals) &&
|
|
str[1] == Inpar) {
|
|
if (c == Inang || c == Outang)
|
|
setdata(node, (void *) getproc(str)); /* <(...) or >(...) */
|
|
else
|
|
setdata(node, (void *) getoutputfile(str)); /* =(...) */
|
|
if (!getdata(node)) {
|
|
unqueue_signals();
|
|
return;
|
|
}
|
|
} else {
|
|
if (isset(SHFILEEXPANSION))
|
|
filesub((char **)getaddrdata(node),
|
|
flags & (PF_TYPESET|PF_ASSIGN));
|
|
if (!(node = stringsubst(list, node, flags & PF_SINGLE, asssub))) {
|
|
unqueue_signals();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
for (node = firstnode(list); node; incnode(node)) {
|
|
if (node == stop)
|
|
keep = 0;
|
|
if (*(char *)getdata(node)) {
|
|
remnulargs(getdata(node));
|
|
if (unset(IGNOREBRACES) && !(flags & PF_SINGLE)) {
|
|
if (!keep)
|
|
stop = nextnode(node);
|
|
while (hasbraces(getdata(node))) {
|
|
keep = 1;
|
|
xpandbraces(list, &node);
|
|
}
|
|
}
|
|
if (unset(SHFILEEXPANSION))
|
|
filesub((char **)getaddrdata(node),
|
|
flags & (PF_TYPESET|PF_ASSIGN));
|
|
} else if (!(flags & PF_SINGLE) && !keep)
|
|
uremnode(list, node);
|
|
if (errflag) {
|
|
unqueue_signals();
|
|
return;
|
|
}
|
|
}
|
|
unqueue_signals();
|
|
}
|
|
|
|
/**/
|
|
static LinkNode
|
|
stringsubst(LinkList list, LinkNode node, int ssub, int asssub)
|
|
{
|
|
int qt;
|
|
char *str3 = (char *)getdata(node);
|
|
char *str = str3, c;
|
|
|
|
while (!errflag && (c = *str)) {
|
|
if ((qt = c == Qstring) || c == String) {
|
|
if ((c = str[1]) == Inpar) {
|
|
if (!qt)
|
|
mult_isarr = 1;
|
|
str++;
|
|
goto comsub;
|
|
} else if (c == Inbrack) {
|
|
/* $[...] */
|
|
char *str2 = str;
|
|
str2++;
|
|
if (skipparens(Inbrack, Outbrack, &str2)) {
|
|
zerr("closing bracket missing", NULL, 0);
|
|
return NULL;
|
|
}
|
|
str2[-1] = *str = '\0';
|
|
str = arithsubst(str + 2, &str3, str2);
|
|
setdata(node, (void *) str3);
|
|
continue;
|
|
} else if (c == Snull) {
|
|
str = getkeystring(str, NULL, 4, NULL);
|
|
continue;
|
|
} else {
|
|
node = paramsubst(list, node, &str, qt, ssub);
|
|
if (errflag || !node)
|
|
return NULL;
|
|
str3 = (char *)getdata(node);
|
|
continue;
|
|
}
|
|
} else if ((qt = c == Qtick) || (c == Tick ? (mult_isarr = 1) : 0))
|
|
comsub: {
|
|
LinkList pl;
|
|
char *s, *str2 = str;
|
|
char endchar;
|
|
int l1, l2;
|
|
|
|
if (c == Inpar) {
|
|
endchar = Outpar;
|
|
str[-1] = '\0';
|
|
#ifdef DEBUG
|
|
if (skipparens(Inpar, Outpar, &str))
|
|
dputs("BUG: parse error in command substitution");
|
|
#else
|
|
skipparens(Inpar, Outpar, &str);
|
|
#endif
|
|
str--;
|
|
} else {
|
|
endchar = c;
|
|
*str = '\0';
|
|
|
|
while (*++str != endchar)
|
|
DPUTS(!*str, "BUG: parse error in command substitution");
|
|
}
|
|
*str++ = '\0';
|
|
if (endchar == Outpar && str2[1] == '(' && str[-2] == ')') {
|
|
/* Math substitution of the form $((...)) */
|
|
str[-2] = '\0';
|
|
str = arithsubst(str2 + 2, &str3, str);
|
|
setdata(node, (void *) str3);
|
|
continue;
|
|
}
|
|
|
|
/* It is a command substitution, which will be parsed again *
|
|
* by the lexer, so we untokenize it first, but we cannot use *
|
|
* untokenize() since in the case of `...` some Bnulls should *
|
|
* be left unchanged. Note that the lexer doesn't tokenize *
|
|
* the body of a command substitution so if there are some *
|
|
* tokens here they are from a ${(e)~...} substitution. */
|
|
for (str = str2; (c = *++str); )
|
|
if (itok(c) && c != Nularg &&
|
|
!(endchar != Outpar && c == Bnull &&
|
|
(str[1] == '$' || str[1] == '\\' || str[1] == '`' ||
|
|
(qt && str[1] == '"'))))
|
|
*str = ztokens[c - Pound];
|
|
str++;
|
|
if (!(pl = getoutput(str2 + 1, qt || ssub))) {
|
|
zerr("parse error in command substitution", NULL, 0);
|
|
return NULL;
|
|
}
|
|
if (endchar == Outpar)
|
|
str2--;
|
|
if (!(s = (char *) ugetnode(pl))) {
|
|
str = strcpy(str2, str);
|
|
continue;
|
|
}
|
|
if (!qt && ssub && isset(GLOBSUBST))
|
|
shtokenize(s);
|
|
l1 = str2 - str3;
|
|
l2 = strlen(s);
|
|
if (nonempty(pl)) {
|
|
LinkNode n = lastnode(pl);
|
|
str2 = (char *) hcalloc(l1 + l2 + 1);
|
|
strcpy(str2, str3);
|
|
strcpy(str2 + l1, s);
|
|
setdata(node, str2);
|
|
insertlinklist(pl, node, list);
|
|
s = (char *) getdata(node = n);
|
|
l1 = 0;
|
|
l2 = strlen(s);
|
|
}
|
|
str2 = (char *) hcalloc(l1 + l2 + strlen(str) + 1);
|
|
if (l1)
|
|
strcpy(str2, str3);
|
|
strcpy(str2 + l1, s);
|
|
str = strcpy(str2 + l1 + l2, str);
|
|
str3 = str2;
|
|
setdata(node, str3);
|
|
continue;
|
|
} else if (asssub && ((c == '=') || c == Equals) && str != str3) {
|
|
/*
|
|
* We are in a normal argument which looks like an assignment
|
|
* and is to be treated like one, with no word splitting.
|
|
*/
|
|
ssub = 1;
|
|
}
|
|
str++;
|
|
}
|
|
return errflag ? NULL : node;
|
|
}
|
|
|
|
/*
|
|
* Simplified version of the prefork/singsub processing where
|
|
* we only do substitutions appropriate to quoting. Currently
|
|
* this means only the expansions in $'....'. This is used
|
|
* for the end tag for here documents. As we are not doing
|
|
* `...` expansions, we just use those for quoting. However,
|
|
* they stay in the text. This is weird, but that's not
|
|
* my fault.
|
|
*
|
|
* The remnulargs() makes this consistent with the other forms
|
|
* of substitution, indicating that quotes have been fully
|
|
* processed.
|
|
*/
|
|
|
|
/**/
|
|
void
|
|
quotesubst(char *str)
|
|
{
|
|
char *s = str;
|
|
|
|
while (*s) {
|
|
if (*s == String && s[1] == Snull) {
|
|
s = getkeystring(s, NULL, 4, NULL);
|
|
} else {
|
|
s++;
|
|
}
|
|
}
|
|
remnulargs(str);
|
|
}
|
|
|
|
/**/
|
|
mod_export void
|
|
globlist(LinkList list, int nountok)
|
|
{
|
|
LinkNode node, next;
|
|
|
|
badcshglob = 0;
|
|
for (node = firstnode(list); !errflag && node; node = next) {
|
|
next = nextnode(node);
|
|
zglob(list, node, nountok);
|
|
}
|
|
if (badcshglob == 1)
|
|
zerr("no match", NULL, 0);
|
|
}
|
|
|
|
/* perform substitution on a single word */
|
|
|
|
/**/
|
|
mod_export void
|
|
singsub(char **s)
|
|
{
|
|
int omi = mult_isarr;
|
|
local_list1(foo);
|
|
|
|
init_list1(foo, *s);
|
|
|
|
prefork(&foo, PF_SINGLE);
|
|
mult_isarr = omi;
|
|
if (errflag)
|
|
return;
|
|
*s = (char *) ugetnode(&foo);
|
|
DPUTS(nonempty(&foo), "BUG: singsub() produced more than one word!");
|
|
}
|
|
|
|
/* Perform substitution on a single word. Unlike with singsub, the *
|
|
* result can have more than one word. A single word result is stored *
|
|
* in *s and *isarr is set to zero; otherwise *isarr is set to 1 and *
|
|
* the result is stored in *a. If `a' is zero a multiple word result is *
|
|
* joined using sep or the IFS parameter if sep is zero and the result *
|
|
* is returned in *s. The return value is true iff the expansion *
|
|
* resulted in an empty list. *
|
|
* The mult_isarr variable is used by paramsubst() to tell if it yields *
|
|
* an array. */
|
|
|
|
/**/
|
|
static int mult_isarr;
|
|
|
|
/**/
|
|
static int
|
|
multsub(char **s, char ***a, int *isarr, UNUSED(char *sep))
|
|
{
|
|
int l, omi = mult_isarr;
|
|
char **r, **p;
|
|
local_list1(foo);
|
|
|
|
mult_isarr = 0;
|
|
init_list1(foo, *s);
|
|
prefork(&foo, 0);
|
|
if (errflag) {
|
|
if (isarr)
|
|
*isarr = 0;
|
|
mult_isarr = omi;
|
|
return 0;
|
|
}
|
|
if ((l = countlinknodes(&foo))) {
|
|
p = r = hcalloc((l + 1) * sizeof(char*));
|
|
while (nonempty(&foo))
|
|
*p++ = (char *)ugetnode(&foo);
|
|
*p = NULL;
|
|
/*
|
|
* This is the most obscure way of deciding whether a value is
|
|
* an array it would be possible to imagine. It seems to result
|
|
* partly because we don't pass down the qt and ssub flags from
|
|
* paramsubst() through prefork() properly, partly because we
|
|
* don't tidy up to get back the return type from multsub we
|
|
* need properly. The crux of neatening this up is to get rid
|
|
* of the following test.
|
|
*/
|
|
if (a && mult_isarr) {
|
|
*a = r;
|
|
*isarr = SCANPM_MATCHMANY;
|
|
mult_isarr = omi;
|
|
return 0;
|
|
}
|
|
*s = sepjoin(r, NULL, 1);
|
|
mult_isarr = omi;
|
|
if (isarr)
|
|
*isarr = 0;
|
|
return 0;
|
|
}
|
|
if (l)
|
|
*s = (char *) ugetnode(&foo);
|
|
else
|
|
*s = dupstring("");
|
|
if (isarr)
|
|
*isarr = 0;
|
|
mult_isarr = omi;
|
|
return !l;
|
|
}
|
|
|
|
/*
|
|
* ~, = subs: assign & PF_TYPESET => typeset or magic equals
|
|
* assign & PF_ASSIGN => normal assignment
|
|
*/
|
|
|
|
/**/
|
|
mod_export void
|
|
filesub(char **namptr, int assign)
|
|
{
|
|
char *eql = NULL, *sub = NULL, *str, *ptr;
|
|
int len;
|
|
|
|
filesubstr(namptr, assign);
|
|
|
|
if (!assign)
|
|
return;
|
|
|
|
if (assign & PF_TYPESET) {
|
|
if ((*namptr)[1] && (eql = sub = strchr(*namptr + 1, Equals))) {
|
|
str = sub + 1;
|
|
if ((sub[1] == Tilde || sub[1] == Equals) && filesubstr(&str, assign)) {
|
|
sub[1] = '\0';
|
|
*namptr = dyncat(*namptr, str);
|
|
}
|
|
} else
|
|
return;
|
|
}
|
|
|
|
ptr = *namptr;
|
|
while ((sub = strchr(ptr, ':'))) {
|
|
str = sub + 1;
|
|
len = sub - *namptr;
|
|
if (sub > eql &&
|
|
(sub[1] == Tilde || sub[1] == Equals) &&
|
|
filesubstr(&str, assign)) {
|
|
sub[1] = '\0';
|
|
*namptr = dyncat(*namptr, str);
|
|
}
|
|
ptr = *namptr + len + 1;
|
|
}
|
|
}
|
|
|
|
/**/
|
|
mod_export int
|
|
filesubstr(char **namptr, int assign)
|
|
{
|
|
#define isend(c) ( !(c) || (c)=='/' || (c)==Inpar || (assign && (c)==':') )
|
|
#define isend2(c) ( !(c) || (c)==Inpar || (assign && (c)==':') )
|
|
char *str = *namptr;
|
|
|
|
if (*str == Tilde && str[1] != '=' && str[1] != Equals) {
|
|
char *ptr;
|
|
int val;
|
|
|
|
val = zstrtol(str + 1, &ptr, 10);
|
|
if (isend(str[1])) { /* ~ */
|
|
*namptr = dyncat(home, str + 1);
|
|
return 1;
|
|
} else if (str[1] == '+' && isend(str[2])) { /* ~+ */
|
|
*namptr = dyncat(pwd, str + 2);
|
|
return 1;
|
|
} else if (str[1] == '-' && isend(str[2])) { /* ~- */
|
|
char *tmp;
|
|
*namptr = dyncat((tmp = oldpwd) ? tmp : pwd, str + 2);
|
|
return 1;
|
|
} else if (!inblank(str[1]) && isend(*ptr) &&
|
|
(!idigit(str[1]) || (ptr - str < 4))) {
|
|
char *ds;
|
|
|
|
if (val < 0)
|
|
val = -val;
|
|
ds = dstackent(str[1], val);
|
|
if (!ds)
|
|
return 0;
|
|
*namptr = dyncat(ds, ptr);
|
|
return 1;
|
|
} else if (iuser(str[1])) { /* ~foo */
|
|
char *ptr, *hom, save;
|
|
|
|
for (ptr = ++str; *ptr && iuser(*ptr); ptr++);
|
|
save = *ptr;
|
|
if (!isend(save))
|
|
return 0;
|
|
*ptr = 0;
|
|
if (!(hom = getnameddir(str))) {
|
|
if (isset(NOMATCH))
|
|
zerr("no such user or named directory: %s", str, 0);
|
|
*ptr = save;
|
|
return 0;
|
|
}
|
|
*ptr = save;
|
|
*namptr = dyncat(hom, ptr);
|
|
return 1;
|
|
}
|
|
} else if (*str == Equals && isset(EQUALS) && str[1]) { /* =foo */
|
|
char sav, *pp, *cnam;
|
|
|
|
for (pp = str + 1; !isend2(*pp); pp++);
|
|
sav = *pp;
|
|
*pp = 0;
|
|
if (!(cnam = findcmd(str + 1, 1))) {
|
|
if (isset(NOMATCH))
|
|
zerr("%s not found", str + 1, 0);
|
|
return 0;
|
|
}
|
|
*namptr = dupstring(cnam);
|
|
if (sav) {
|
|
*pp = sav;
|
|
*namptr = dyncat(*namptr, pp);
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
#undef isend
|
|
#undef isend2
|
|
}
|
|
|
|
/**/
|
|
static char *
|
|
strcatsub(char **d, char *pb, char *pe, char *src, int l, char *s, int glbsub,
|
|
int copied)
|
|
{
|
|
char *dest;
|
|
int pl = pe - pb;
|
|
|
|
if (!pl && (!s || !*s)) {
|
|
*d = dest = (copied ? src : dupstring(src));
|
|
if (glbsub)
|
|
shtokenize(dest);
|
|
} else {
|
|
*d = dest = hcalloc(pl + l + (s ? strlen(s) : 0) + 1);
|
|
strncpy(dest, pb, pl);
|
|
dest += pl;
|
|
strcpy(dest, src);
|
|
if (glbsub)
|
|
shtokenize(dest);
|
|
dest += l;
|
|
if (s)
|
|
strcpy(dest, s);
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
typedef int (*CompareFn) _((const void *, const void *));
|
|
|
|
/**/
|
|
int
|
|
strpcmp(const void *a, const void *b)
|
|
{
|
|
#ifdef HAVE_STRCOLL
|
|
return strcoll(*(char **)a, *(char **)b);
|
|
#else
|
|
return strcmp(*(char **)a, *(char **)b);
|
|
#endif
|
|
}
|
|
|
|
/**/
|
|
int
|
|
invstrpcmp(const void *a, const void *b)
|
|
{
|
|
#ifdef HAVE_STRCOLL
|
|
return -strcoll(*(char **)a, *(char **)b);
|
|
#else
|
|
return -strcmp(*(char **)a, *(char **)b);
|
|
#endif
|
|
}
|
|
|
|
/**/
|
|
int
|
|
cstrpcmp(const void *a, const void *b)
|
|
{
|
|
#ifdef HAVE_STRCOLL
|
|
VARARR(char, c, strlen(*(char **) a) + 1);
|
|
VARARR(char, d, strlen(*(char **) b) + 1);
|
|
char *s, *t;
|
|
int cmp;
|
|
|
|
for (s = *(char **) a, t = c; (*t++ = tulower(*s++)););
|
|
for (s = *(char **) b, t = d; (*t++ = tulower(*s++)););
|
|
|
|
cmp = strcoll(c, d);
|
|
|
|
return cmp;
|
|
#else
|
|
char *c = *(char **)a, *d = *(char **)b;
|
|
|
|
for (; *c && tulower(*c) == tulower(*d); c++, d++);
|
|
|
|
return (int)STOUC(tulower(*c)) - (int)STOUC(tulower(*d));
|
|
#endif
|
|
}
|
|
|
|
/**/
|
|
int
|
|
invcstrpcmp(const void *a, const void *b)
|
|
{
|
|
#ifdef HAVE_STRCOLL
|
|
VARARR(char, c, strlen(*(char **) a) + 1);
|
|
VARARR(char, d, strlen(*(char **) b) + 1);
|
|
char *s, *t;
|
|
int cmp;
|
|
|
|
for (s = *(char **) a, t = c; (*t++ = tulower(*s++)););
|
|
for (s = *(char **) b, t = d; (*t++ = tulower(*s++)););
|
|
|
|
cmp = strcoll(c, d);
|
|
|
|
return -cmp;
|
|
#else
|
|
char *c = *(char **)a, *d = *(char **)b;
|
|
|
|
for (; *c && tulower(*c) == tulower(*d); c++, d++);
|
|
|
|
return (int)STOUC(tulower(*d)) - (int)STOUC(tulower(*c));
|
|
#endif
|
|
}
|
|
|
|
/**/
|
|
int
|
|
nstrpcmp(const void *a, const void *b)
|
|
{
|
|
char *c = *(char **)a, *d = *(char **)b;
|
|
int cmp;
|
|
|
|
#ifdef HAVE_STRCOLL
|
|
cmp = strcoll(c, d);
|
|
#endif
|
|
for (; *c == *d && *c; c++, d++);
|
|
#ifndef HAVE_STRCOLL
|
|
cmp = (int)STOUC(*c) - (int)STOUC(*d);
|
|
#endif
|
|
if (idigit(*c) || idigit(*d)) {
|
|
for (; c > *(char **)a && idigit(c[-1]); c--, d--);
|
|
if (idigit(*c) && idigit(*d)) {
|
|
while (*c == '0')
|
|
c++;
|
|
while (*d == '0')
|
|
d++;
|
|
for (; idigit(*c) && *c == *d; c++, d++);
|
|
if (idigit(*c) || idigit(*d)) {
|
|
cmp = (int)STOUC(*c) - (int)STOUC(*d);
|
|
while (idigit(*c) && idigit(*d))
|
|
c++, d++;
|
|
if (idigit(*c) && !idigit(*d))
|
|
return 1;
|
|
if (idigit(*d) && !idigit(*c))
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
return cmp;
|
|
}
|
|
|
|
/**/
|
|
int
|
|
invnstrpcmp(const void *a, const void *b)
|
|
{
|
|
return -nstrpcmp(a, b);
|
|
}
|
|
|
|
/**/
|
|
int
|
|
instrpcmp(const void *a, const void *b)
|
|
{
|
|
VARARR(char, c, strlen(*(char **) a) + 1);
|
|
VARARR(char, d, strlen(*(char **) b) + 1);
|
|
char **e = (char **)&c;
|
|
char **f = (char **)&d;
|
|
char *s, *t;
|
|
|
|
for (s = *(char **) a, t = c; (*t++ = tulower(*s++)););
|
|
for (s = *(char **) b, t = d; (*t++ = tulower(*s++)););
|
|
|
|
return nstrpcmp(&e, &f);
|
|
}
|
|
|
|
/**/
|
|
int
|
|
invinstrpcmp(const void *a, const void *b)
|
|
{
|
|
return -instrpcmp(a, b);
|
|
}
|
|
|
|
/**/
|
|
static char *
|
|
dopadding(char *str, int prenum, int postnum, char *preone, char *postone, char *premul, char *postmul)
|
|
{
|
|
char def[3], *ret, *t, *r;
|
|
int ls, ls2, lpreone, lpostone, lpremul, lpostmul, lr, f, m, c, cc;
|
|
|
|
def[0] = *ifs ? *ifs : ' ';
|
|
def[1] = *ifs == Meta ? ifs[1] ^ 32 : '\0';
|
|
def[2] = '\0';
|
|
if (preone && !*preone)
|
|
preone = def;
|
|
if (postone && !*postone)
|
|
postone = def;
|
|
if (!premul || !*premul)
|
|
premul = def;
|
|
if (!postmul || !*postmul)
|
|
postmul = def;
|
|
|
|
ls = strlen(str);
|
|
lpreone = preone ? strlen(preone) : 0;
|
|
lpostone = postone ? strlen(postone) : 0;
|
|
lpremul = strlen(premul);
|
|
lpostmul = strlen(postmul);
|
|
|
|
lr = prenum + postnum;
|
|
|
|
if (lr == ls)
|
|
return str;
|
|
|
|
r = ret = (char *)zhalloc(lr + 1);
|
|
|
|
if (prenum) {
|
|
if (postnum) {
|
|
ls2 = ls / 2;
|
|
|
|
f = prenum - ls2;
|
|
if (f <= 0)
|
|
for (str -= f, c = prenum; c--; *r++ = *str++);
|
|
else {
|
|
if (f <= lpreone)
|
|
for (c = f, t = preone + lpreone - f; c--; *r++ = *t++);
|
|
else {
|
|
f -= lpreone;
|
|
if ((m = f % lpremul))
|
|
for (c = m, t = premul + lpremul - m; c--; *r++ = *t++);
|
|
for (cc = f / lpremul; cc--;)
|
|
for (c = lpremul, t = premul; c--; *r++ = *t++);
|
|
for (c = lpreone; c--; *r++ = *preone++);
|
|
}
|
|
for (c = ls2; c--; *r++ = *str++);
|
|
}
|
|
ls2 = ls - ls2;
|
|
f = postnum - ls2;
|
|
if (f <= 0)
|
|
for (c = postnum; c--; *r++ = *str++);
|
|
else {
|
|
for (c = ls2; c--; *r++ = *str++);
|
|
if (f <= lpostone)
|
|
for (c = f; c--; *r++ = *postone++);
|
|
else {
|
|
f -= lpostone;
|
|
for (c = lpostone; c--; *r++ = *postone++);
|
|
for (cc = f / lpostmul; cc--;)
|
|
for (c = lpostmul, t = postmul; c--; *r++ = *t++);
|
|
if ((m = f % lpostmul))
|
|
for (; m--; *r++ = *postmul++);
|
|
}
|
|
}
|
|
} else {
|
|
f = prenum - ls;
|
|
if (f <= 0)
|
|
for (c = prenum, str -= f; c--; *r++ = *str++);
|
|
else {
|
|
if (f <= lpreone)
|
|
for (c = f, t = preone + lpreone - f; c--; *r++ = *t++);
|
|
else {
|
|
f -= lpreone;
|
|
if ((m = f % lpremul))
|
|
for (c = m, t = premul + lpremul - m; c--; *r++ = *t++);
|
|
for (cc = f / lpremul; cc--;)
|
|
for (c = lpremul, t = premul; c--; *r++ = *t++);
|
|
for (c = lpreone; c--; *r++ = *preone++);
|
|
}
|
|
for (c = ls; c--; *r++ = *str++);
|
|
}
|
|
}
|
|
} else if (postnum) {
|
|
f = postnum - ls;
|
|
if (f <= 0)
|
|
for (c = postnum; c--; *r++ = *str++);
|
|
else {
|
|
for (c = ls; c--; *r++ = *str++);
|
|
if (f <= lpostone)
|
|
for (c = f; c--; *r++ = *postone++);
|
|
else {
|
|
f -= lpostone;
|
|
for (c = lpostone; c--; *r++ = *postone++);
|
|
for (cc = f / lpostmul; cc--;)
|
|
for (c = lpostmul, t = postmul; c--; *r++ = *t++);
|
|
if ((m = f % lpostmul))
|
|
for (; m--; *r++ = *postmul++);
|
|
}
|
|
}
|
|
}
|
|
*r = '\0';
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**/
|
|
char *
|
|
get_strarg(char *s)
|
|
{
|
|
char t = *s++;
|
|
|
|
if (!t)
|
|
return s - 1;
|
|
|
|
switch (t) {
|
|
case '(':
|
|
t = ')';
|
|
break;
|
|
case '[':
|
|
t = ']';
|
|
break;
|
|
case '{':
|
|
t = '}';
|
|
break;
|
|
case '<':
|
|
t = '>';
|
|
break;
|
|
case Inpar:
|
|
t = Outpar;
|
|
break;
|
|
case Inang:
|
|
t = Outang;
|
|
break;
|
|
case Inbrace:
|
|
t = Outbrace;
|
|
break;
|
|
case Inbrack:
|
|
t = Outbrack;
|
|
break;
|
|
}
|
|
|
|
while (*s && *s != t)
|
|
s++;
|
|
|
|
return s;
|
|
}
|
|
|
|
/**/
|
|
static int
|
|
get_intarg(char **s)
|
|
{
|
|
char *t = get_strarg(*s + 1);
|
|
char *p, sav;
|
|
zlong ret;
|
|
|
|
if (!*t)
|
|
return -1;
|
|
sav = *t;
|
|
*t = '\0';
|
|
p = dupstring(*s + 2);
|
|
*s = t;
|
|
*t = sav;
|
|
if (parsestr(p))
|
|
return -1;
|
|
singsub(&p);
|
|
if (errflag)
|
|
return -1;
|
|
ret = mathevali(p);
|
|
if (errflag)
|
|
return -1;
|
|
if (ret < 0)
|
|
ret = -ret;
|
|
return ret < 0 ? -ret : ret;
|
|
}
|
|
|
|
/* Parsing for the (e) flag. */
|
|
|
|
static int
|
|
subst_parse_str(char **sp, int single, int err)
|
|
{
|
|
char *s;
|
|
|
|
*sp = s = dupstring(*sp);
|
|
|
|
if (!(err ? parsestr(s) : parsestrnoerr(s))) {
|
|
if (!single) {
|
|
int qt = 0;
|
|
|
|
for (; *s; s++)
|
|
if (!qt) {
|
|
if (*s == Qstring)
|
|
*s = String;
|
|
else if (*s == Qtick)
|
|
*s = Tick;
|
|
} else if (*s == Dnull)
|
|
qt = !qt;
|
|
}
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* parameter substitution */
|
|
|
|
#define isstring(c) ((c) == '$' || (char)(c) == String || (char)(c) == Qstring)
|
|
#define isbrack(c) ((c) == '[' || (char)(c) == Inbrack)
|
|
|
|
/*
|
|
* Given a linked list l with node n, perform parameter substitution
|
|
* starting from *str. Return the node with the substitutuion performed
|
|
* or NULL if it failed.
|
|
*
|
|
* If qt is true, the `$' was quoted. TODO: why can't we just look
|
|
* to see if the first character was String or Qstring?
|
|
*
|
|
* If ssub is true, we are being called via singsubst(), which means
|
|
* the result will be a single word. TODO: can we generate the
|
|
* single word at the end? TODO: if not, or maybe in any case,
|
|
* can we pass down the ssub flag from prefork with the other flags
|
|
* instead of pushing it into different arguments? (How exactly
|
|
* to qt and ssub differ? Are both necessary, if so is there some
|
|
* better way of separating the two?)
|
|
*/
|
|
|
|
/**/
|
|
LinkNode
|
|
paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
|
|
{
|
|
char *aptr = *str, c, cc;
|
|
char *s = aptr, *fstr, *idbeg, *idend, *ostr = (char *) getdata(n);
|
|
int colf; /* != 0 means we found a colon after the name */
|
|
/*
|
|
* There are far too many flags. They need to be grouped
|
|
* together into some structure which ties them to where they
|
|
* came from.
|
|
*
|
|
* Some flags have a an obscure relationship to their effect which
|
|
* depends on incrementing them to particular values in particular
|
|
* ways.
|
|
*/
|
|
/*
|
|
* Whether the value is an array (in aval) or not (in val). There's
|
|
* a movement from storing the value in the stuff read from the
|
|
* parameter (the value v) to storing them in val and aval.
|
|
* However, sometimes you find v reappearing temporarily.
|
|
*
|
|
* The values -1 and 2 are special to isarr. It looks like 2 is
|
|
* some kind of an internal flag to do with whether the array's been
|
|
* copied, in which case I don't know why we don't use the copied
|
|
* flag, but they do both occur close together so they presumably
|
|
* have different effects. The value -1 is isued to force us to
|
|
* keep an empty array. It's tested in the YUK chunk (I mean the
|
|
* one explicitly marked as such).
|
|
*/
|
|
int isarr = 0;
|
|
/*
|
|
* This is just the setting of the option except we need to
|
|
* take account of ^ and ^^.
|
|
*/
|
|
int plan9 = isset(RCEXPANDPARAM);
|
|
/*
|
|
* Likwise, but with ~ and ~~. Also, we turn it off later
|
|
* on if qt is passed down.
|
|
*/
|
|
int globsubst = isset(GLOBSUBST);
|
|
/*
|
|
* Indicates ${#pm}, massaged by whichlen which is set by
|
|
* the (c), (w), and (W) flags to indicate how we take the length.
|
|
*/
|
|
int getlen = 0;
|
|
int whichlen = 0;
|
|
/*
|
|
* Indicates ${+pm}: a simple boolean for once.
|
|
*/
|
|
int chkset = 0;
|
|
/*
|
|
* Indicates we have tried to get a value in v but that was
|
|
* unset. I don't quite understand why (v == NULL) isn't
|
|
* good enough, but there are places where we seem to need
|
|
* to second guess whether a value is a real value or not.
|
|
*/
|
|
int vunset = 0;
|
|
/*
|
|
* Indicates (t) flag, i.e. print out types. The code for
|
|
* this actually isn't too horrifically inbred compared with
|
|
* that for (P).
|
|
*/
|
|
int wantt = 0;
|
|
/*
|
|
* Indicates spliting a string into an array. There aren't
|
|
* actually that many special cases for this --- which may
|
|
* be why it doesn't work properly; we split in some cases
|
|
* where we shouldn't, in particular on the multsubs for
|
|
* handling embedded values for ${...=...} and the like.
|
|
*/
|
|
int spbreak = isset(SHWORDSPLIT) && !ssub && !qt;
|
|
/* Scalar and array value, see isarr above */
|
|
char *val = NULL, **aval = NULL;
|
|
/*
|
|
* Padding based on setting in parameter rather than substitution
|
|
* flags. This is only used locally.
|
|
*/
|
|
unsigned int fwidth = 0;
|
|
/*
|
|
* vbuf and v are both used to retrieve parameter values; this
|
|
* is a kludge, we pass down vbuf and it may or may not return v.
|
|
*/
|
|
struct value vbuf;
|
|
Value v = NULL;
|
|
/*
|
|
* This expressive name refers to the set of flags which
|
|
* is applied to matching for #, %, / and their doubled variants:
|
|
* (M), (R), (B), (E), (N), (S).
|
|
*/
|
|
int flags = 0;
|
|
/* Value from (I) flag, used for ditto. */
|
|
int flnum = 0;
|
|
/*
|
|
* sortit is an obscure combination of the settings for (o), (O),
|
|
* (i) and (n). casind is (i) and numord is (n); these are
|
|
* separate so we can have fun doing the obscure combinatorics later.
|
|
* indord is the (a) flag, which for consistency doesn't get
|
|
* combined into sortit.
|
|
*/
|
|
int sortit = 0, casind = 0, numord = 0, indord = 0;
|
|
/* (u): straightforward. */
|
|
int unique = 0;
|
|
/* combination of (L), (U) and (C) flags. */
|
|
int casmod = 0;
|
|
/*
|
|
* quotemod says we are doing either (q) (positive), (Q) (negative)
|
|
* or not (0). quotetype counts the q's for the first case.
|
|
* quoterr is simply (X) but gets passed around a lot because the
|
|
* combination (eX) needs it.
|
|
*/
|
|
int quotemod = 0, quotetype = 0, quoteerr = 0;
|
|
/*
|
|
* (V) flag: fairly straightforward, except that as with so
|
|
* many flags it's not easy to decide where to put it in the order.
|
|
*/
|
|
int visiblemod = 0;
|
|
/*
|
|
* The (z) flag, nothing to do with SH_WORD_SPLIT which is tied
|
|
* spbreak, see above; fairly straighforward in use but c.f.
|
|
* the comment for visiblemod.
|
|
*/
|
|
int shsplit = 0;
|
|
/*
|
|
* The separator from (j) and (s) respectively, or (F) and (f)
|
|
* respectively (hardwired to "\n" in that case). Slightly
|
|
* confusingly also used for ${#pm}, thought that's at least
|
|
* documented in the manual
|
|
*/
|
|
char *sep = NULL, *spsep = NULL;
|
|
/*
|
|
* Padding strings. The left and right padding strings which
|
|
* are repeated, then the ones which only occur once, for
|
|
* the (l) and (r) flags.
|
|
*/
|
|
char *premul = NULL, *postmul = NULL, *preone = NULL, *postone = NULL;
|
|
/* Replacement string for /orig/repl and //orig/repl */
|
|
char *replstr = NULL;
|
|
/* The numbers for (l) and (r) */
|
|
zlong prenum = 0, postnum = 0;
|
|
/*
|
|
* Whether the value has been copied. Optimisation: if we
|
|
* are modifying an expression, we only need to copy it the
|
|
* first time, and if we don't modify it we can just use the
|
|
* value from the parameter or input.
|
|
*/
|
|
int copied = 0;
|
|
/*
|
|
* The (A) flag for array assignment, with consequences for
|
|
* splitting and joining; (AA) gives arrasg == 2 for associative
|
|
* arrays.
|
|
*/
|
|
int arrasg = 0;
|
|
/*
|
|
* The (e) flag. As we need to do extra work not quite
|
|
* at the end, the effect of this is kludged in in several places.
|
|
*/
|
|
int eval = 0;
|
|
/*
|
|
* The (P) flag. This interacts a bit obscurely with whether
|
|
* or not we are dealing with a sub expression (subexp).
|
|
*/
|
|
int aspar = 0;
|
|
/*
|
|
* The (%) flag, c.f. visiblemod again.
|
|
*/
|
|
int presc = 0;
|
|
/*
|
|
* The (@) flag; interacts obscurely with qt and isarr.
|
|
* This is one of the things that decides whether multsub
|
|
* will produce an array, but in an extremely indirect fashion.
|
|
*/
|
|
int nojoin = 0;
|
|
/*
|
|
* != 0 means ${...}, otherwise $... What works without braces
|
|
* is largely a historical artefact (everything works with braces,
|
|
* I sincerely hope).
|
|
*/
|
|
char inbrace = 0;
|
|
/*
|
|
* Use for the (k) flag. Goes down into the parameter code,
|
|
* sometimes.
|
|
*/
|
|
char hkeys = 0;
|
|
/*
|
|
* Used for the (v) flag, ditto. Not quite sure why they're
|
|
* separate, but the tradition seems to be that things only
|
|
* get combined when that makes the result more obscure rather
|
|
* than less.
|
|
*/
|
|
char hvals = 0;
|
|
/*
|
|
* Whether we had to evaluate a subexpression, i.e. an
|
|
* internal ${...} or $(...) or plain $pm. We almost don't
|
|
* need to remember this (which would be neater), but the (P)
|
|
* flag means the subexp and !subexp code is obscurely combined,
|
|
* and the argument passing to fetchvalue has another kludge.
|
|
*/
|
|
int subexp;
|
|
|
|
*s++ = '\0';
|
|
/*
|
|
* Nothing to do unless the character following the $ is
|
|
* something we recognise.
|
|
*
|
|
* Shouldn't this be a table or something? We test for all
|
|
* these later on, too.
|
|
*/
|
|
if (!ialnum(c = *s) && c != '#' && c != Pound && c != '-' &&
|
|
c != '!' && c != '$' && c != String && c != Qstring &&
|
|
c != '?' && c != Quest && c != '_' &&
|
|
c != '*' && c != Star && c != '@' && c != '{' &&
|
|
c != Inbrace && c != '=' && c != Equals && c != Hat &&
|
|
c != '^' && c != '~' && c != Tilde && c != '+') {
|
|
s[-1] = '$';
|
|
*str = s;
|
|
return n;
|
|
}
|
|
DPUTS(c == '{', "BUG: inbrace == '{' in paramsubst()");
|
|
/*
|
|
* Extra processing if there is an opening brace: mostly
|
|
* flags in parentheses, but also one ksh hack.
|
|
*/
|
|
if (c == Inbrace) {
|
|
inbrace = 1;
|
|
s++;
|
|
/*
|
|
* In ksh emulation a leading `!' is a special flag working
|
|
* sort of like our (k).
|
|
* TODO: this is one of very few cases tied directly to
|
|
* the emulation mode rather than an option. Since ksh
|
|
* doesn't have parameter flags it might be neater to
|
|
* handle this with the ^, =, ~ stuff, below.
|
|
*/
|
|
if ((c = *s) == '!' && s[1] != Outbrace && emulation == EMULATE_KSH) {
|
|
hkeys = SCANPM_WANTKEYS;
|
|
s++;
|
|
} else if (c == '(' || c == Inpar) {
|
|
char *t, sav;
|
|
int tt = 0;
|
|
zlong num;
|
|
/*
|
|
* The (p) flag is (uniquely) only remembered within
|
|
* this block. It says we do print-style handling
|
|
* on the values for flags, but only on those.
|
|
* This explains the ghastly macro, but why can't it
|
|
* be a function? UNTOK_AND_ESCAPE is defined
|
|
* so that the argument must be an lvalue.
|
|
*/
|
|
int escapes = 0;
|
|
int klen;
|
|
#define UNTOK(C) (itok(C) ? ztokens[(C) - Pound] : (C))
|
|
#define UNTOK_AND_ESCAPE(X) {\
|
|
untokenize(X = dupstring(s + 1));\
|
|
if (escapes) {\
|
|
X = getkeystring(X, &klen, 3, NULL);\
|
|
X = metafy(X, klen, META_HREALLOC);\
|
|
}\
|
|
}
|
|
|
|
for (s++; (c = *s) != ')' && c != Outpar; s++, tt = 0) {
|
|
switch (c) {
|
|
case ')':
|
|
case Outpar:
|
|
break;
|
|
case 'A':
|
|
++arrasg;
|
|
break;
|
|
case '@':
|
|
nojoin = 1;
|
|
break;
|
|
case 'M':
|
|
flags |= SUB_MATCH;
|
|
break;
|
|
case 'R':
|
|
flags |= SUB_REST;
|
|
break;
|
|
case 'B':
|
|
flags |= SUB_BIND;
|
|
break;
|
|
case 'E':
|
|
flags |= SUB_EIND;
|
|
break;
|
|
case 'N':
|
|
flags |= SUB_LEN;
|
|
break;
|
|
case 'S':
|
|
flags |= SUB_SUBSTR;
|
|
break;
|
|
case 'I':
|
|
flnum = get_intarg(&s);
|
|
if (flnum < 0)
|
|
goto flagerr;
|
|
break;
|
|
|
|
case 'L':
|
|
casmod = 2;
|
|
break;
|
|
case 'U':
|
|
casmod = 1;
|
|
break;
|
|
case 'C':
|
|
casmod = 3;
|
|
break;
|
|
|
|
case 'o':
|
|
sortit = 1;
|
|
break;
|
|
case 'O':
|
|
sortit = 2;
|
|
break;
|
|
case 'i':
|
|
casind = 1;
|
|
break;
|
|
case 'n':
|
|
numord = 1;
|
|
break;
|
|
case 'a':
|
|
indord = 1;
|
|
break;
|
|
|
|
case 'V':
|
|
visiblemod++;
|
|
break;
|
|
|
|
case 'q':
|
|
quotemod++, quotetype++;
|
|
break;
|
|
case 'Q':
|
|
quotemod--;
|
|
break;
|
|
case 'X':
|
|
quoteerr = 1;
|
|
break;
|
|
|
|
case 'e':
|
|
eval = 1;
|
|
break;
|
|
case 'P':
|
|
aspar = 1;
|
|
break;
|
|
|
|
case 'c':
|
|
whichlen = 1;
|
|
break;
|
|
case 'w':
|
|
whichlen = 2;
|
|
break;
|
|
case 'W':
|
|
whichlen = 3;
|
|
break;
|
|
|
|
case 'f':
|
|
spsep = "\n";
|
|
break;
|
|
case 'F':
|
|
sep = "\n";
|
|
break;
|
|
|
|
case 's':
|
|
tt = 1;
|
|
/* fall through */
|
|
case 'j':
|
|
t = get_strarg(++s);
|
|
if (*t) {
|
|
sav = *t;
|
|
*t = '\0';
|
|
if (tt)
|
|
UNTOK_AND_ESCAPE(spsep)
|
|
else
|
|
UNTOK_AND_ESCAPE(sep)
|
|
*t = sav;
|
|
s = t;
|
|
} else
|
|
goto flagerr;
|
|
break;
|
|
|
|
case 'l':
|
|
tt = 1;
|
|
/* fall through */
|
|
case 'r':
|
|
sav = s[1];
|
|
num = get_intarg(&s);
|
|
if (num < 0)
|
|
goto flagerr;
|
|
if (tt)
|
|
prenum = num;
|
|
else
|
|
postnum = num;
|
|
if (UNTOK(s[1]) != UNTOK(sav))
|
|
break;
|
|
t = get_strarg(++s);
|
|
if (!*t)
|
|
goto flagerr;
|
|
sav = *t;
|
|
*t = '\0';
|
|
if (tt)
|
|
UNTOK_AND_ESCAPE(premul)
|
|
else
|
|
UNTOK_AND_ESCAPE(postmul)
|
|
*t = sav;
|
|
sav = *s;
|
|
s = t + 1;
|
|
if (UNTOK(*s) != UNTOK(sav)) {
|
|
s--;
|
|
break;
|
|
}
|
|
t = get_strarg(s);
|
|
if (!*t)
|
|
goto flagerr;
|
|
sav = *t;
|
|
*t = '\0';
|
|
if (tt)
|
|
UNTOK_AND_ESCAPE(preone)
|
|
else
|
|
UNTOK_AND_ESCAPE(postone)
|
|
*t = sav;
|
|
s = t;
|
|
break;
|
|
|
|
case 'p':
|
|
escapes = 1;
|
|
break;
|
|
|
|
case 'k':
|
|
hkeys = SCANPM_WANTKEYS;
|
|
break;
|
|
case 'v':
|
|
hvals = SCANPM_WANTVALS;
|
|
break;
|
|
|
|
case 't':
|
|
wantt = 1;
|
|
break;
|
|
|
|
case '%':
|
|
presc++;
|
|
break;
|
|
|
|
case 'z':
|
|
shsplit = 1;
|
|
break;
|
|
|
|
case 'u':
|
|
unique = 1;
|
|
break;
|
|
|
|
default:
|
|
flagerr:
|
|
zerr("error in flags", NULL, 0);
|
|
return NULL;
|
|
}
|
|
}
|
|
s++;
|
|
}
|
|
}
|
|
/* Sort is done by indexing on sortit-1:
|
|
* bit 1: ascending (o)/descending (O)
|
|
* bit 2: case sensitive/independent (i)
|
|
* bit 3: strict order/numeric (n)
|
|
* unless indord (a) is set set, in which case only test for
|
|
* descending by assuming only (O) is possible (not verified).
|
|
*/
|
|
if (sortit)
|
|
sortit += (casind << 1) + (numord << 2);
|
|
|
|
/*
|
|
* premul, postmul specify the padding character to be used
|
|
* multiple times with the (l) and (r) flags respectively.
|
|
*/
|
|
if (!premul)
|
|
premul = " ";
|
|
if (!postmul)
|
|
postmul = " ";
|
|
|
|
/*
|
|
* Look for special unparenthesised flags.
|
|
* TODO: could make these able to appear inside parentheses, too,
|
|
* i.e. ${(^)...} etc.
|
|
*/
|
|
for (;;) {
|
|
if ((c = *s) == '^' || c == Hat) {
|
|
/* RC_EXPAND_PARAM on or off (doubled )*/
|
|
if ((c = *++s) == '^' || c == Hat) {
|
|
plan9 = 0;
|
|
s++;
|
|
} else
|
|
plan9 = 1;
|
|
} else if ((c = *s) == '=' || c == Equals) {
|
|
/* SH_WORD_SPLIT on or off (doubled). spbreak = 2 means force */
|
|
if ((c = *++s) == '=' || c == Equals) {
|
|
spbreak = 0;
|
|
s++;
|
|
} else
|
|
spbreak = 2;
|
|
} else if ((c == '#' || c == Pound) &&
|
|
(iident(cc = s[1])
|
|
|| cc == '*' || cc == Star || cc == '@'
|
|
|| cc == '-' || (cc == ':' && s[2] == '-')
|
|
|| (isstring(cc) && (s[2] == Inbrace || s[2] == Inpar)))) {
|
|
getlen = 1 + whichlen, s++;
|
|
/*
|
|
* Return the length of the parameter.
|
|
* getlen can be more than 1 to indicate characters (2),
|
|
* words ignoring multiple delimiters (3), words taking
|
|
* account of multiple delimiters. delimiter is in
|
|
* spsep, NULL means $IFS.
|
|
*/
|
|
} else if (c == '~' || c == Tilde) {
|
|
/* GLOB_SUBST on or off (doubled) */
|
|
if ((c = *++s) == '~' || c == Tilde) {
|
|
globsubst = 0;
|
|
s++;
|
|
} else
|
|
globsubst = 1;
|
|
} else if (c == '+') {
|
|
/*
|
|
* Return whether indicated parameter is set.
|
|
* Try to handle this when parameter is named
|
|
* by (P) (second part of test).
|
|
*/
|
|
if (iident(s[1]) || (aspar && isstring(s[1]) &&
|
|
(s[2] == Inbrace || s[2] == Inpar)))
|
|
chkset = 1, s++;
|
|
else if (!inbrace) {
|
|
/* Special case for `$+' on its own --- leave unmodified */
|
|
*aptr = '$';
|
|
*str = aptr + 1;
|
|
return n;
|
|
} else {
|
|
zerr("bad substitution", NULL, 0);
|
|
return NULL;
|
|
}
|
|
} else if (inbrace && INULL(*s)) {
|
|
/*
|
|
* Handles things like ${(f)"$(<file)"} by skipping
|
|
* the double quotes. We don't need to know what was
|
|
* actually there; the presence of a String or Qstring
|
|
* is good enough.
|
|
*/
|
|
s++;
|
|
} else
|
|
break;
|
|
}
|
|
/* Don't activate special pattern characters if inside quotes */
|
|
globsubst = globsubst && !qt;
|
|
|
|
/*
|
|
* At this point, we usually expect a parameter name.
|
|
* However, there may be a nested ${...} or $(...).
|
|
* These say that the parameter itself is somewhere inside,
|
|
* or that there isn't a parameter and we will get the values
|
|
* from a command substitution itself. In either case,
|
|
* the current instance of paramsubst() doesn't fetch a value,
|
|
* it just operates on what gets passed up.
|
|
* (The first ought to have been {...}, reserving ${...}
|
|
* for substituting a value at that point, but it's too late now.)
|
|
*/
|
|
idbeg = s;
|
|
if ((subexp = (inbrace && s[-1] && isstring(*s) &&
|
|
(s[1] == Inbrace || s[1] == Inpar)))) {
|
|
int sav;
|
|
int quoted = *s == Qstring;
|
|
|
|
val = s++;
|
|
skipparens(*s, *s == Inpar ? Outpar : Outbrace, &s);
|
|
sav = *s;
|
|
*s = 0;
|
|
/*
|
|
* This handles arrays. TODO: this is not the most obscure call to
|
|
* multsub() (see below) but even so it would be nicer to pass down
|
|
* and back the arrayness more rationally. In that case, we should
|
|
* remove the aspar test and extract a value from an array, if
|
|
* necessary, when we handle (P) lower down.
|
|
*/
|
|
if (multsub(&val, (aspar ? NULL : &aval), &isarr, NULL) && quoted) {
|
|
/* Empty quoted string --- treat as null string, not elided */
|
|
isarr = -1;
|
|
aval = (char **) hcalloc(sizeof(char *));
|
|
aspar = 0;
|
|
} else if (aspar)
|
|
idbeg = val;
|
|
*s = sav;
|
|
/*
|
|
* This tests for the second double quote in an expression
|
|
* like ${(f)"$(<file)"}, compare above.
|
|
*/
|
|
while (INULL(*s))
|
|
s++;
|
|
v = (Value) NULL;
|
|
} else if (aspar) {
|
|
/*
|
|
* No subexpression, but in any case the value is going
|
|
* to give us the name of a parameter on which we do
|
|
* our remaining processing. In other words, this
|
|
* makes ${(P)param} work like ${(P)${param}}. (Probably
|
|
* better looked at, this is the basic code for ${(P)param}
|
|
* and it's been kludged into the subexp code because no
|
|
* opportunity for a kludge has been neglected.)
|
|
*/
|
|
if ((v = fetchvalue(&vbuf, &s, 1, (qt ? SCANPM_DQUOTED : 0)))) {
|
|
val = idbeg = getstrvalue(v);
|
|
subexp = 1;
|
|
} else
|
|
vunset = 1;
|
|
}
|
|
/*
|
|
* We need to retrieve a value either if we haven't already
|
|
* got it from a subexpression, or if the processing so
|
|
* far has just yielded us a parameter name to be processed
|
|
* with (P).
|
|
*/
|
|
if (!subexp || aspar) {
|
|
char *ov = val;
|
|
|
|
/*
|
|
* Second argument: decide whether to use the subexpression or
|
|
* the string next on the line as the parameter name.
|
|
* Third argument: decide how processing for brackets
|
|
* 1 means full processing
|
|
* -1 appears to mean something along the lines of
|
|
* only handle single digits and don't handle brackets.
|
|
* I *think* (but it's really only a guess) that this
|
|
* is used by the test below the wantt handling, so
|
|
* that in certain cases we handle brackets there.
|
|
* 0 would apparently mean something like we know we
|
|
* should have the name of a scalar and we get cross
|
|
* if there's anything present which disagrees with that
|
|
* but you will search fetchvalue() in vain for comments on this.
|
|
* Fourth argument gives flags to do with keys, values, quoting,
|
|
* assigning depending on context and parameter flags.
|
|
*
|
|
* This is the last mention of subexp, so presumably this
|
|
* is what the code which makes sure subexp is set if aspar (the
|
|
* (P) flag) is set. I *think* what's going on here is the
|
|
* second argument is for both input and output: with
|
|
* subexp, we only want the input effect, whereas normally
|
|
* we let fetchvalue set the main string pointer s to
|
|
* the end of the bit it's fetched.
|
|
*/
|
|
if (!(v = fetchvalue(&vbuf, (subexp ? &ov : &s),
|
|
(wantt ? -1 :
|
|
((unset(KSHARRAYS) || inbrace) ? 1 : -1)),
|
|
hkeys|hvals|
|
|
(arrasg ? SCANPM_ASSIGNING : 0)|
|
|
(qt ? SCANPM_DQUOTED : 0))) ||
|
|
(v->pm && (v->pm->flags & PM_UNSET)))
|
|
vunset = 1;
|
|
|
|
if (wantt) {
|
|
/*
|
|
* Handle the (t) flag: value now becomes the type
|
|
* information for the parameter.
|
|
*/
|
|
if (v && v->pm && !(v->pm->flags & PM_UNSET)) {
|
|
int f = v->pm->flags;
|
|
|
|
switch (PM_TYPE(f)) {
|
|
case PM_SCALAR: val = "scalar"; break;
|
|
case PM_ARRAY: val = "array"; break;
|
|
case PM_INTEGER: val = "integer"; break;
|
|
case PM_EFLOAT:
|
|
case PM_FFLOAT: val = "float"; break;
|
|
case PM_HASHED: val = "association"; break;
|
|
}
|
|
val = dupstring(val);
|
|
if (v->pm->level)
|
|
val = dyncat(val, "-local");
|
|
if (f & PM_LEFT)
|
|
val = dyncat(val, "-left");
|
|
if (f & PM_RIGHT_B)
|
|
val = dyncat(val, "-right_blanks");
|
|
if (f & PM_RIGHT_Z)
|
|
val = dyncat(val, "-right_zeros");
|
|
if (f & PM_LOWER)
|
|
val = dyncat(val, "-lower");
|
|
if (f & PM_UPPER)
|
|
val = dyncat(val, "-upper");
|
|
if (f & PM_READONLY)
|
|
val = dyncat(val, "-readonly");
|
|
if (f & PM_TAGGED)
|
|
val = dyncat(val, "-tag");
|
|
if (f & PM_EXPORTED)
|
|
val = dyncat(val, "-export");
|
|
if (f & PM_UNIQUE)
|
|
val = dyncat(val, "-unique");
|
|
if (f & PM_HIDE)
|
|
val = dyncat(val, "-hide");
|
|
if (f & PM_HIDE)
|
|
val = dyncat(val, "-hideval");
|
|
if (f & PM_SPECIAL)
|
|
val = dyncat(val, "-special");
|
|
vunset = 0;
|
|
} else
|
|
val = dupstring("");
|
|
|
|
v = NULL;
|
|
isarr = 0;
|
|
}
|
|
}
|
|
/*
|
|
* We get in here two ways; either we need to convert v into
|
|
* the local value system, or we need to get rid of brackets
|
|
* even if there isn't a v.
|
|
*/
|
|
while (v || ((inbrace || (unset(KSHARRAYS) && vunset)) && isbrack(*s))) {
|
|
if (!v) {
|
|
/*
|
|
* Index applied to non-existent parameter; we may or may
|
|
* not have a value to index, however. Create a temporary
|
|
* empty parameter as a trick, and index on that. This
|
|
* usually happens the second time around the loop when
|
|
* we've used up the original parameter value and want to
|
|
* apply a subscript to what's left. However, it's also
|
|
* possible it's got something to do with some of that murky
|
|
* passing of -1's as the third argument to fetchvalue() to
|
|
* inhibit bracket parsing at that stage.
|
|
*/
|
|
Param pm;
|
|
char *os = s;
|
|
|
|
if (!isbrack(*s))
|
|
break;
|
|
if (vunset) {
|
|
val = dupstring("");
|
|
isarr = 0;
|
|
}
|
|
pm = createparam(nulstring, isarr ? PM_ARRAY : PM_SCALAR);
|
|
DPUTS(!pm, "BUG: parameter not created");
|
|
if (isarr)
|
|
pm->u.arr = aval;
|
|
else
|
|
pm->u.str = val;
|
|
v = (Value) hcalloc(sizeof *v);
|
|
v->isarr = isarr;
|
|
v->pm = pm;
|
|
v->end = -1;
|
|
if (getindex(&s, v, qt) || s == os)
|
|
break;
|
|
}
|
|
/*
|
|
* This is where we extract a value (we know now we have
|
|
* one) into the local parameters for a scalar (val) or
|
|
* array (aval) value. TODO: move val and aval into
|
|
* a structure with a discriminator. Hope we can make
|
|
* more things array values at this point and dearrayify later.
|
|
* v->isarr tells us whether the stuff form down below looks
|
|
* like an array. Unlike multsub() this is probably clean
|
|
* enough to keep, although possibly the parameter passing
|
|
* needs reorganising.
|
|
*
|
|
* I think we get to discard the existing value of isarr
|
|
* here because it's already been taken account of, either
|
|
* in the subexp stuff or immediately above.
|
|
*/
|
|
if ((isarr = v->isarr)) {
|
|
/* No way to get here with v->inv != 0, so getvaluearr() *
|
|
* is called by getarrvalue(); needn't test PM_HASHED. */
|
|
if (v->isarr == SCANPM_WANTINDEX) {
|
|
isarr = v->isarr = 0;
|
|
val = dupstring(v->pm->nam);
|
|
} else
|
|
aval = getarrvalue(v);
|
|
} else {
|
|
/* Value retrieved from parameter/subexpression is scalar */
|
|
if (v->pm->flags & PM_ARRAY) {
|
|
/*
|
|
* Although the value is a scalar, the parameter
|
|
* itself is an array. Presumably this is due to
|
|
* being quoted, or doing single substitution or something,
|
|
* TODO: we're about to do some definitely stringy
|
|
* stuff, so something like this bit is probably
|
|
* necessary. However, I'd like to leave any
|
|
* necessary joining of arrays until this point
|
|
* to avoid the multsub() horror.
|
|
*/
|
|
int tmplen = arrlen(v->pm->gsu.a->getfn(v->pm));
|
|
|
|
if (v->start < 0)
|
|
v->start += tmplen + v->inv;
|
|
if (!v->inv && (v->start >= tmplen || v->start < 0))
|
|
vunset = 1;
|
|
}
|
|
if (!vunset) {
|
|
/*
|
|
* There really is a value. Apply any necessary
|
|
* padding or case transformation. Note these
|
|
* are the per-parameter transformations specified
|
|
* with typeset, not the per-substitution ones set
|
|
* by flags. TODO: maybe therefore this would
|
|
* be more consistent if moved into getstrvalue()?
|
|
* Bet that's easier said than done.
|
|
*/
|
|
val = getstrvalue(v);
|
|
fwidth = v->pm->width ? v->pm->width : (int)strlen(val);
|
|
switch (v->pm->flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) {
|
|
char *t;
|
|
unsigned int t0;
|
|
|
|
case PM_LEFT:
|
|
case PM_LEFT | PM_RIGHT_Z:
|
|
t = val;
|
|
if (v->pm->flags & PM_RIGHT_Z)
|
|
while (*t == '0')
|
|
t++;
|
|
else
|
|
while (iblank(*t))
|
|
t++;
|
|
val = (char *) hcalloc(fwidth + 1);
|
|
val[fwidth] = '\0';
|
|
if ((t0 = strlen(t)) > fwidth)
|
|
t0 = fwidth;
|
|
memset(val, ' ', fwidth);
|
|
strncpy(val, t, t0);
|
|
break;
|
|
case PM_RIGHT_B:
|
|
case PM_RIGHT_Z:
|
|
case PM_RIGHT_Z | PM_RIGHT_B:
|
|
{
|
|
int zero = 1;
|
|
|
|
if (strlen(val) < fwidth) {
|
|
char *valprefend = val;
|
|
if (v->pm->flags & PM_RIGHT_Z) {
|
|
/*
|
|
* This is a documented feature: when deciding
|
|
* whether to pad with zeroes, ignore
|
|
* leading blanks already in the value;
|
|
* only look for numbers after that.
|
|
* Not sure how useful this really is.
|
|
* It's certainly confusing to code around.
|
|
*/
|
|
for (t = val; iblank(*t); t++)
|
|
;
|
|
/*
|
|
* Allow padding after initial minus
|
|
* for numeric variables.
|
|
*/
|
|
if ((v->pm->flags &
|
|
(PM_INTEGER|PM_EFLOAT|PM_FFLOAT)) &&
|
|
*t == '-')
|
|
t++;
|
|
/*
|
|
* Allow padding after initial 0x or
|
|
* base# for integer variables.
|
|
*/
|
|
if (v->pm->flags & PM_INTEGER) {
|
|
if (isset(CBASES) &&
|
|
t[0] == '0' && t[1] == 'x')
|
|
t += 2;
|
|
else if ((valprefend = strchr(t, '#')))
|
|
t = valprefend + 1;
|
|
}
|
|
valprefend = t;
|
|
if (!*t)
|
|
zero = 0;
|
|
else if (v->pm->flags &
|
|
(PM_INTEGER|PM_EFLOAT|PM_FFLOAT)) {
|
|
/* zero always OK */
|
|
} else if (!idigit(*t))
|
|
zero = 0;
|
|
}
|
|
t = (char *) hcalloc(fwidth + 1);
|
|
memset(t, (((v->pm->flags & PM_RIGHT_B) || !zero) ?
|
|
' ' : '0'), fwidth);
|
|
/*
|
|
* How can the following trigger? We
|
|
* haven't altered val or fwidth since
|
|
* the last time we tested this.
|
|
*/
|
|
if ((t0 = strlen(val)) > fwidth)
|
|
t0 = fwidth;
|
|
/*
|
|
* Copy - or 0x or base# before any padding
|
|
* zeroes.
|
|
*/
|
|
if (zero && val != valprefend) {
|
|
int preflen = valprefend - val;
|
|
memcpy(t, val, preflen);
|
|
strcpy(t + (fwidth - t0) + preflen,
|
|
valprefend);
|
|
} else
|
|
strcpy(t + (fwidth - t0), val);
|
|
val = t;
|
|
} else {
|
|
t = (char *) hcalloc(fwidth + 1);
|
|
t[fwidth] = '\0';
|
|
strncpy(t, val + strlen(val) - fwidth, fwidth);
|
|
val = t;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
switch (v->pm->flags & (PM_LOWER | PM_UPPER)) {
|
|
char *t;
|
|
|
|
case PM_LOWER:
|
|
t = val;
|
|
for (; (c = *t); t++)
|
|
*t = tulower(c);
|
|
break;
|
|
case PM_UPPER:
|
|
t = val;
|
|
for (; (c = *t); t++)
|
|
*t = tuupper(c);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* Finished with the original parameter and its indices;
|
|
* carry on looping to see if we need to do more indexing.
|
|
* This means we final get rid of v in favour of val and
|
|
* aval. We could do with somehow encapsulating the bit
|
|
* where we need v.
|
|
*/
|
|
v = NULL;
|
|
if (!inbrace)
|
|
break;
|
|
}
|
|
/*
|
|
* We're now past the name or subexpression; the only things
|
|
* which can happen now are a closing brace, one of the standard
|
|
* parameter postmodifiers, or a history-style colon-modifier.
|
|
*
|
|
* Again, this duplicates tests for characters we're about to
|
|
* examine properly later on.
|
|
*/
|
|
if (inbrace &&
|
|
(c = *s) != '-' && c != '+' && c != ':' && c != '%' && c != '/' &&
|
|
c != '=' && c != Equals &&
|
|
c != '#' && c != Pound &&
|
|
c != '?' && c != Quest &&
|
|
c != '}' && c != Outbrace) {
|
|
zerr("bad substitution", NULL, 0);
|
|
return NULL;
|
|
}
|
|
/*
|
|
* Join arrays up if we're in quotes and there isn't some
|
|
* override such as (@).
|
|
* TODO: hmm, if we're called as part of some recursive
|
|
* substitution do we want to delay this until we get back to
|
|
* the top level? Or is if there's a qt (i.e. this parameter
|
|
* substitution is in quotes) always good enough? Potentially
|
|
* we may be OK by now --- all potential `@'s and subexpressions
|
|
* have been handled, including any [@] index which comes up
|
|
* by virture of v->isarr being set to SCANPM_ISVAR_AT which
|
|
* is now in isarr.
|
|
*
|
|
* However, if we are replacing multsub() with something that
|
|
* doesn't mangle arrays, we may need to delay this step until after
|
|
* the foo:- or foo:= or whatever that causes that. Note the value
|
|
* (string or array) at this point is irrelevant if we are going to
|
|
* be doing that. This would mean // and stuff get applied
|
|
* arraywise even if quoted. That's probably wrong, so maybe
|
|
* this just stays.
|
|
*
|
|
* We do a separate stage of dearrayification in the YUK chunk,
|
|
* I think mostly because of the way we make array or scalar
|
|
* values appear to the caller.
|
|
*/
|
|
if (isarr) {
|
|
if (nojoin)
|
|
isarr = -1;
|
|
if (qt && !getlen && isarr > 0) {
|
|
val = sepjoin(aval, sep, 1);
|
|
isarr = 0;
|
|
}
|
|
}
|
|
|
|
idend = s;
|
|
if (inbrace) {
|
|
/*
|
|
* This is to match a closing double quote in case
|
|
* we didn't have a subexpression, e.g. ${"foo"}.
|
|
* This form is pointless, but logically it ought to work.
|
|
*/
|
|
while (INULL(*s))
|
|
s++;
|
|
}
|
|
/*
|
|
* We don't yet know whether a `:' introduces a history-style
|
|
* colon modifier or qualifies something like ${...:=...}.
|
|
* But if we remember the colon here it's easy to check later.
|
|
*/
|
|
if ((colf = *s == ':'))
|
|
s++;
|
|
|
|
|
|
/* fstr is to be the text following the substitution. If we have *
|
|
* braces, we look for it here, else we infer it later on. */
|
|
fstr = s;
|
|
if (inbrace) {
|
|
int bct;
|
|
for (bct = 1; (c = *fstr); fstr++) {
|
|
if (c == Inbrace)
|
|
bct++;
|
|
else if (c == Outbrace && !--bct)
|
|
break;
|
|
}
|
|
|
|
if (bct) {
|
|
noclosebrace:
|
|
zerr("closing brace expected", NULL, 0);
|
|
return NULL;
|
|
}
|
|
if (c)
|
|
*fstr++ = '\0';
|
|
}
|
|
|
|
/* Check for ${..?..} or ${..=..} or one of those. *
|
|
* Only works if the name is in braces. */
|
|
|
|
if (inbrace && ((c = *s) == '-' ||
|
|
c == '+' ||
|
|
c == ':' || /* i.e. a doubled colon */
|
|
c == '=' || c == Equals ||
|
|
c == '%' ||
|
|
c == '#' || c == Pound ||
|
|
c == '?' || c == Quest ||
|
|
c == '/')) {
|
|
|
|
/*
|
|
* Default index is 1 if no (I) or (I) gave zero. But
|
|
* why don't we set the default explicitly at the start
|
|
* and massage any passed index where we set flnum anyway?
|
|
*/
|
|
if (!flnum)
|
|
flnum++;
|
|
if (c == '%')
|
|
flags |= SUB_END;
|
|
|
|
/* Check for ${..%%..} or ${..##..} */
|
|
if ((c == '%' || c == '#' || c == Pound) && c == s[1]) {
|
|
s++;
|
|
/* we have %%, not %, or ##, not # */
|
|
flags |= SUB_LONG;
|
|
}
|
|
s++;
|
|
if (s[-1] == '/') {
|
|
char *ptr;
|
|
/*
|
|
* previous flags are irrelevant, except for (S) which
|
|
* indicates shortest substring; else look for longest.
|
|
*/
|
|
flags = (flags & SUB_SUBSTR) ? 0 : SUB_LONG;
|
|
if ((c = *s) == '/') {
|
|
/* doubled, so replace all occurrences */
|
|
flags |= SUB_GLOBAL;
|
|
c = *++s;
|
|
}
|
|
/* Check for anchored substitution */
|
|
if (c == '%') {
|
|
/* anchor at tail */
|
|
flags |= SUB_END;
|
|
s++;
|
|
} else if (c == '#' || c == Pound) {
|
|
/* anchor at head: this is the `normal' case in getmatch */
|
|
s++;
|
|
} else
|
|
flags |= SUB_SUBSTR;
|
|
/*
|
|
* Find the / marking the end of the search pattern.
|
|
* If there isn't one, we're just going to delete that,
|
|
* i.e. replace it with an empty string.
|
|
*
|
|
* We used to use double backslashes to quote slashes,
|
|
* but actually that was buggy and using a single backslash
|
|
* is easier and more obvious.
|
|
*/
|
|
for (ptr = s; (c = *ptr) && c != '/'; ptr++)
|
|
{
|
|
if ((c == Bnull || c == '\\') && ptr[1])
|
|
{
|
|
if (ptr[1] == '/')
|
|
chuck(ptr);
|
|
else
|
|
ptr++;
|
|
}
|
|
}
|
|
replstr = (*ptr && ptr[1]) ? ptr+1 : "";
|
|
*ptr = '\0';
|
|
}
|
|
|
|
/* See if this was ${...:-...}, ${...:=...}, etc. */
|
|
if (colf)
|
|
flags |= SUB_ALL;
|
|
/*
|
|
* With no special flags, i.e. just a # or % or whatever,
|
|
* the matched portion is removed and we keep the rest.
|
|
* We also want the rest when we're doing a substitution.
|
|
*/
|
|
if (!(flags & (SUB_MATCH|SUB_REST|SUB_BIND|SUB_EIND|SUB_LEN)))
|
|
flags |= SUB_REST;
|
|
|
|
if (colf && !vunset)
|
|
vunset = (isarr) ? !*aval : !*val || (*val == Nularg && !val[1]);
|
|
|
|
switch (s[-1]) {
|
|
case '+':
|
|
if (vunset) {
|
|
val = dupstring("");
|
|
copied = 1;
|
|
isarr = 0;
|
|
break;
|
|
}
|
|
vunset = 1;
|
|
/* Fall Through! */
|
|
case '-':
|
|
if (vunset) {
|
|
val = dupstring(s);
|
|
/*
|
|
* This is not good enough for sh emulation! Sh would
|
|
* split unquoted substrings, yet not split quoted ones
|
|
* (except according to $@ rules); but this leaves the
|
|
* unquoted substrings unsplit, and other code below
|
|
* for spbreak splits even within the quoted substrings.
|
|
*
|
|
* TODO: I think multsub needs to be told enough to
|
|
* decide about splitting with spbreak at this point
|
|
* (and equally in the `=' handler below). Then
|
|
* we can turn off spbreak to avoid the join & split
|
|
* nastiness later.
|
|
*
|
|
* What we really want to do is make this look as
|
|
* if it were the result of an assignment from
|
|
* the same value, taking account of quoting.
|
|
*/
|
|
multsub(&val, (aspar ? NULL : &aval), &isarr, NULL);
|
|
copied = 1;
|
|
}
|
|
break;
|
|
case ':':
|
|
/* this must be `::=', unconditional assignment */
|
|
if (*s != '=' && *s != Equals)
|
|
goto noclosebrace;
|
|
vunset = 1;
|
|
s++;
|
|
/* Fall through */
|
|
case '=':
|
|
case Equals:
|
|
if (vunset) {
|
|
char sav = *idend;
|
|
int l;
|
|
|
|
*idend = '\0';
|
|
val = dupstring(s);
|
|
isarr = 0;
|
|
/*
|
|
* TODO: this is one of those places where I don't
|
|
* think we want to do the joining until later on.
|
|
* We also need to handle spbreak and spsep at this
|
|
* point and unset them.
|
|
*/
|
|
if (spsep || spbreak || !arrasg)
|
|
multsub(&val, NULL, NULL, sep);
|
|
else
|
|
multsub(&val, &aval, &isarr, NULL);
|
|
if (arrasg) {
|
|
/*
|
|
* This is an array assignment in a context
|
|
* where we have no syntactic way of finding
|
|
* out what an array element is. So we just guess.
|
|
*/
|
|
char *arr[2], **t, **a, **p;
|
|
if (spsep || spbreak) {
|
|
aval = sepsplit(val, spsep, 0, 1);
|
|
isarr = 2;
|
|
l = arrlen(aval);
|
|
if (l && !*(aval[l-1]))
|
|
l--;
|
|
if (l && !**aval)
|
|
l--, t = aval + 1;
|
|
else
|
|
t = aval;
|
|
} else if (!isarr) {
|
|
if (!*val && arrasg > 1) {
|
|
arr[0] = NULL;
|
|
l = 0;
|
|
} else {
|
|
arr[0] = val;
|
|
arr[1] = NULL;
|
|
l = 1;
|
|
}
|
|
t = aval = arr;
|
|
} else
|
|
l = arrlen(aval), t = aval;
|
|
p = a = zalloc(sizeof(char *) * (l + 1));
|
|
while (l--) {
|
|
untokenize(*t);
|
|
*p++ = ztrdup(*t++);
|
|
}
|
|
*p++ = NULL;
|
|
if (arrasg > 1) {
|
|
Param pm = sethparam(idbeg, a);
|
|
if (pm)
|
|
aval = paramvalarr(pm->gsu.h->getfn(pm), hkeys|hvals);
|
|
} else
|
|
setaparam(idbeg, a);
|
|
} else {
|
|
untokenize(val);
|
|
setsparam(idbeg, ztrdup(val));
|
|
}
|
|
*idend = sav;
|
|
copied = 1;
|
|
if (isarr) {
|
|
if (nojoin)
|
|
isarr = -1;
|
|
if (qt && !getlen && isarr > 0 && !spsep && spbreak < 2) {
|
|
val = sepjoin(aval, sep, 1);
|
|
isarr = 0;
|
|
}
|
|
sep = spsep = NULL;
|
|
spbreak = 0;
|
|
}
|
|
}
|
|
break;
|
|
case '?':
|
|
case Quest:
|
|
if (vunset) {
|
|
char *msg;
|
|
|
|
*idend = '\0';
|
|
msg = tricat(idbeg, ": ", *s ? s : "parameter not set");
|
|
zerr("%s", msg, 0);
|
|
zsfree(msg);
|
|
if (!interact)
|
|
exit(1);
|
|
return NULL;
|
|
}
|
|
break;
|
|
case '%':
|
|
case '#':
|
|
case Pound:
|
|
case '/':
|
|
/* This once was executed only `if (qt) ...'. But with that
|
|
* patterns in a expansion resulting from a ${(e)...} aren't
|
|
* tokenized even though this function thinks they are (it thinks
|
|
* they are because subst_parse_str() turns Qstring tokens
|
|
* into String tokens and for unquoted parameter expansions the
|
|
* lexer normally does tokenize patterns inside parameter
|
|
* expansions). */
|
|
{
|
|
int one = noerrs, oef = errflag, haserr;
|
|
|
|
if (!quoteerr)
|
|
noerrs = 1;
|
|
haserr = parse_subst_string(s);
|
|
noerrs = one;
|
|
if (!quoteerr) {
|
|
errflag = oef;
|
|
if (haserr)
|
|
shtokenize(s);
|
|
} else if (haserr || errflag) {
|
|
zerr("parse error in ${...%c...} substitution",
|
|
NULL, s[-1]);
|
|
return NULL;
|
|
}
|
|
}
|
|
{
|
|
#if 0
|
|
/*
|
|
* This allows # and % to be at the start of
|
|
* a parameter in the substitution, which is
|
|
* a bit nasty, and can be done (although
|
|
* less efficiently) with anchors.
|
|
*/
|
|
|
|
char t = s[-1];
|
|
|
|
singsub(&s);
|
|
|
|
if (t == '/' && (flags & SUB_SUBSTR)) {
|
|
if ((c = *s) == '#' || c == '%') {
|
|
flags &= ~SUB_SUBSTR;
|
|
if (c == '%')
|
|
flags |= SUB_END;
|
|
s++;
|
|
} else if (c == '\\') {
|
|
s++;
|
|
}
|
|
}
|
|
#else
|
|
singsub(&s);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Either loop over an array doing replacements or
|
|
* do the replacment on a string.
|
|
*/
|
|
if (!vunset && isarr) {
|
|
getmatcharr(&aval, s, flags, flnum, replstr);
|
|
copied = 1;
|
|
} else {
|
|
if (vunset)
|
|
val = dupstring("");
|
|
getmatch(&val, s, flags, flnum, replstr);
|
|
copied = 1;
|
|
}
|
|
break;
|
|
}
|
|
} else { /* no ${...=...} or anything, but possible modifiers. */
|
|
/*
|
|
* Handler ${+...}. TODO: strange, why do we handle this only
|
|
* if there isn't a trailing modifier? Why don't we do this
|
|
* e.g. when we hanlder the ${(t)...} flag?
|
|
*/
|
|
if (chkset) {
|
|
val = dupstring(vunset ? "0" : "1");
|
|
isarr = 0;
|
|
} else if (vunset) {
|
|
if (unset(UNSET)) {
|
|
*idend = '\0';
|
|
zerr("%s: parameter not set", idbeg, 0);
|
|
return NULL;
|
|
}
|
|
val = dupstring("");
|
|
}
|
|
if (colf) {
|
|
/*
|
|
* History style colon modifiers. May need to apply
|
|
* on multiple elements of an array.
|
|
*/
|
|
s--;
|
|
if (unset(KSHARRAYS) || inbrace) {
|
|
if (!isarr)
|
|
modify(&val, &s);
|
|
else {
|
|
char *ss;
|
|
char **ap = aval;
|
|
char **pp = aval = (char **) hcalloc(sizeof(char *) *
|
|
(arrlen(aval) + 1));
|
|
|
|
while ((*pp = *ap++)) {
|
|
ss = s;
|
|
modify(pp++, &ss);
|
|
}
|
|
if (pp == aval) {
|
|
char *t = "";
|
|
ss = s;
|
|
modify(&t, &ss);
|
|
}
|
|
s = ss;
|
|
}
|
|
copied = 1;
|
|
if (inbrace && *s) {
|
|
if (*s == ':' && !imeta(s[1]))
|
|
zerr("unrecognized modifier `%c'", NULL, s[1]);
|
|
else
|
|
zerr("unrecognized modifier", NULL, 0);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
if (!inbrace)
|
|
fstr = s;
|
|
}
|
|
if (errflag)
|
|
return NULL;
|
|
/*
|
|
* This handles taking a length with ${#foo} and variations.
|
|
* TODO: again. one might naively have thought this had the
|
|
* same sort of effect as the ${(t)...} flag and the ${+...}
|
|
* test, although in this case we do need the value rather
|
|
* the the parameter, so maybe it's a bit different.
|
|
*/
|
|
if (getlen) {
|
|
long len = 0;
|
|
char buf[14];
|
|
|
|
if (isarr) {
|
|
char **ctr;
|
|
int sl = sep ? ztrlen(sep) : 1;
|
|
|
|
if (getlen == 1)
|
|
for (ctr = aval; *ctr; ctr++, len++);
|
|
else if (getlen == 2) {
|
|
if (*aval)
|
|
for (len = -sl, ctr = aval;
|
|
len += sl + ztrlen(*ctr), *++ctr;);
|
|
}
|
|
else
|
|
for (ctr = aval;
|
|
*ctr;
|
|
len += wordcount(*ctr, spsep, getlen > 3), ctr++);
|
|
} else {
|
|
if (getlen < 3)
|
|
len = ztrlen(val);
|
|
else
|
|
len = wordcount(val, spsep, getlen > 3);
|
|
}
|
|
|
|
sprintf(buf, "%ld", len);
|
|
val = dupstring(buf);
|
|
isarr = 0;
|
|
}
|
|
/*
|
|
* I think this mult_isarr stuff here is used to pass back
|
|
* the setting of whether we are an array to multsub, and
|
|
* thence to the top-level paramsubst(). The way the
|
|
* setting is passed back is completely obscure, however.
|
|
* It's presumably at this point because we try to remember
|
|
* whether the value was `really' an array before massaging
|
|
* some special cases.
|
|
*
|
|
* TODO: YUK. This is not the right place to turn arrays into
|
|
* scalars; we should pass back as an array, and let the calling
|
|
* code decide how to deal with it. This is almost certainly
|
|
* a lot harder than it sounds. Do we really need to handle
|
|
* one-element arrays as scalars at this point? Couldn't
|
|
* we just test for it later rather than having a multiple-valued
|
|
* wave-function for isarr?
|
|
*/
|
|
mult_isarr = isarr;
|
|
if (isarr > 0 && !plan9 && (!aval || !aval[0])) {
|
|
val = dupstring("");
|
|
isarr = 0;
|
|
} else if (isarr && aval && aval[0] && !aval[1]) {
|
|
/* treat a one-element array as a scalar for purposes of *
|
|
* concatenation with surrounding text (some${param}thing) *
|
|
* and rc_expand_param handling. Note: mult_isarr (above) *
|
|
* propagates the true array type from nested expansions. */
|
|
val = aval[0];
|
|
isarr = 0;
|
|
}
|
|
/* ssub is true when we are called from singsub (via prefork).
|
|
* It means that we must join arrays and should not split words. */
|
|
/*
|
|
* TODO: this is what is screwing up the use of SH_WORD_SPLIT
|
|
* after `:-' etc. If we fix multsub(), we might get away
|
|
* with simply unsetting the appropriate flags when they
|
|
* get handled.
|
|
*/
|
|
if (ssub || spbreak || spsep || sep) {
|
|
if (isarr)
|
|
val = sepjoin(aval, sep, 1), isarr = 0;
|
|
if (!ssub && (spbreak || spsep)) {
|
|
aval = sepsplit(val, spsep, 0, 1);
|
|
if (!aval || !aval[0])
|
|
val = dupstring("");
|
|
else if (!aval[1])
|
|
val = aval[0];
|
|
else
|
|
isarr = 2;
|
|
}
|
|
mult_isarr = isarr;
|
|
}
|
|
/*
|
|
* Perform case modififications.
|
|
*/
|
|
if (casmod) {
|
|
if (isarr) {
|
|
char **ap;
|
|
|
|
if (!copied)
|
|
aval = arrdup(aval), copied = 1;
|
|
ap = aval;
|
|
|
|
if (casmod == 1)
|
|
for (; *ap; ap++)
|
|
makeuppercase(ap);
|
|
else if (casmod == 2)
|
|
for (; *ap; ap++)
|
|
makelowercase(ap);
|
|
else
|
|
for (; *ap; ap++)
|
|
makecapitals(ap);
|
|
|
|
} else {
|
|
if (!copied)
|
|
val = dupstring(val), copied = 1;
|
|
if (casmod == 1)
|
|
makeuppercase(&val);
|
|
else if (casmod == 2)
|
|
makelowercase(&val);
|
|
else
|
|
makecapitals(&val);
|
|
}
|
|
}
|
|
/*
|
|
* Perform prompt-style modifications.
|
|
*/
|
|
if (presc) {
|
|
int ops = opts[PROMPTSUBST], opb = opts[PROMPTBANG];
|
|
int opp = opts[PROMPTPERCENT], len;
|
|
|
|
if (presc < 2) {
|
|
opts[PROMPTPERCENT] = 1;
|
|
opts[PROMPTSUBST] = opts[PROMPTBANG] = 0;
|
|
}
|
|
/*
|
|
* TODO: It would be really quite nice to abstract the
|
|
* isarr and !issarr code into a function which gets
|
|
* passed a pointer to a function with the effect of
|
|
* the promptexpand bit. Then we could use this for
|
|
* a lot of stuff and bury val/aval/isarr inside a structure
|
|
* which gets passed to it.
|
|
*/
|
|
if (isarr) {
|
|
char **ap;
|
|
|
|
if (!copied)
|
|
aval = arrdup(aval), copied = 1;
|
|
ap = aval;
|
|
for (; *ap; ap++) {
|
|
char *tmps;
|
|
unmetafy(*ap, &len);
|
|
untokenize(*ap);
|
|
tmps = unmetafy(promptexpand(metafy(*ap, len, META_NOALLOC),
|
|
0, NULL, NULL), &len);
|
|
*ap = dupstring(tmps);
|
|
free(tmps);
|
|
}
|
|
} else {
|
|
char *tmps;
|
|
if (!copied)
|
|
val = dupstring(val), copied = 1;
|
|
unmetafy(val, &len);
|
|
untokenize(val);
|
|
tmps = unmetafy(promptexpand(metafy(val, len, META_NOALLOC),
|
|
0, NULL, NULL), &len);
|
|
val = dupstring(tmps);
|
|
free(tmps);
|
|
}
|
|
opts[PROMPTSUBST] = ops;
|
|
opts[PROMPTBANG] = opb;
|
|
opts[PROMPTPERCENT] = opp;
|
|
}
|
|
/*
|
|
* One of the possible set of quotes to apply, depending on
|
|
* the repetitions of the (q) flag.
|
|
*/
|
|
if (quotemod) {
|
|
if (--quotetype > 3)
|
|
quotetype = 3;
|
|
if (isarr) {
|
|
char **ap;
|
|
|
|
if (!copied)
|
|
aval = arrdup(aval), copied = 1;
|
|
ap = aval;
|
|
|
|
if (quotemod > 0) {
|
|
if (quotetype) {
|
|
int sl;
|
|
char *tmp;
|
|
|
|
for (; *ap; ap++) {
|
|
int pre = quotetype != 3 ? 1 : 2;
|
|
tmp = bslashquote(*ap, NULL, quotetype);
|
|
sl = strlen(tmp);
|
|
*ap = (char *) zhalloc(pre + sl + 2);
|
|
strcpy((*ap) + pre, tmp);
|
|
ap[0][pre - 1] = ap[0][pre + sl] = (quotetype != 2 ? '\'' : '"');
|
|
ap[0][pre + sl + 1] = '\0';
|
|
if (quotetype == 3)
|
|
ap[0][0] = '$';
|
|
}
|
|
} else
|
|
for (; *ap; ap++)
|
|
*ap = bslashquote(*ap, NULL, 0);
|
|
} else {
|
|
int one = noerrs, oef = errflag, haserr = 0;
|
|
|
|
if (!quoteerr)
|
|
noerrs = 1;
|
|
for (; *ap; ap++) {
|
|
haserr |= parse_subst_string(*ap);
|
|
remnulargs(*ap);
|
|
untokenize(*ap);
|
|
}
|
|
noerrs = one;
|
|
if (!quoteerr)
|
|
errflag = oef;
|
|
else if (haserr || errflag) {
|
|
zerr("parse error in parameter value", NULL, 0);
|
|
return NULL;
|
|
}
|
|
}
|
|
} else {
|
|
if (!copied)
|
|
val = dupstring(val), copied = 1;
|
|
if (quotemod > 0) {
|
|
if (quotetype) {
|
|
int pre = quotetype != 3 ? 1 : 2;
|
|
int sl;
|
|
char *tmp;
|
|
tmp = bslashquote(val, NULL, quotetype);
|
|
sl = strlen(tmp);
|
|
val = (char *) zhalloc(pre + sl + 2);
|
|
strcpy(val + pre, tmp);
|
|
val[pre - 1] = val[pre + sl] = (quotetype != 2 ? '\'' : '"');
|
|
val[pre + sl + 1] = '\0';
|
|
if (quotetype == 3)
|
|
val[0] = '$';
|
|
} else
|
|
val = bslashquote(val, NULL, 0);
|
|
} else {
|
|
int one = noerrs, oef = errflag, haserr;
|
|
|
|
if (!quoteerr)
|
|
noerrs = 1;
|
|
haserr = parse_subst_string(val);
|
|
noerrs = one;
|
|
if (!quoteerr)
|
|
errflag = oef;
|
|
else if (haserr || errflag) {
|
|
zerr("parse error in parameter value", NULL, 0);
|
|
return NULL;
|
|
}
|
|
remnulargs(val);
|
|
untokenize(val);
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* Transform special characters in the string to make them
|
|
* printable.
|
|
*/
|
|
if (visiblemod) {
|
|
if (isarr) {
|
|
char **ap;
|
|
if (!copied)
|
|
aval = arrdup(aval), copied = 1;
|
|
for (ap = aval; *ap; ap++)
|
|
*ap = nicedupstring(*ap);
|
|
} else {
|
|
if (!copied)
|
|
val = dupstring(val), copied = 1;
|
|
val = nicedupstring(val);
|
|
}
|
|
}
|
|
/*
|
|
* Nothing particularly to do with SH_WORD_SPLIT --- this
|
|
* performs lexical splitting on a string as specified by
|
|
* the (z) flag.
|
|
*/
|
|
if (shsplit) {
|
|
LinkList list = NULL;
|
|
|
|
if (isarr) {
|
|
char **ap;
|
|
for (ap = aval; *ap; ap++)
|
|
list = bufferwords(list, *ap, NULL);
|
|
isarr = 0;
|
|
} else
|
|
list = bufferwords(NULL, val, NULL);
|
|
|
|
if (!list || !firstnode(list))
|
|
val = dupstring("");
|
|
else if (!nextnode(firstnode(list)))
|
|
val = getdata(firstnode(list));
|
|
else {
|
|
char **ap;
|
|
LinkNode node;
|
|
|
|
aval = ap = (char **) zhalloc((countlinknodes(list) + 1) *
|
|
sizeof(char *));
|
|
for (node = firstnode(list); node; incnode(node))
|
|
*ap++ = (char *) getdata(node);
|
|
*ap = NULL;
|
|
mult_isarr = isarr = 2;
|
|
}
|
|
copied = 1;
|
|
}
|
|
/*
|
|
* TODO: hmm. At this point we have to be on our toes about
|
|
* whether we're putting stuff into a line or not, i.e.
|
|
* we don't want to do this from a recursive call; this is
|
|
* probably part of the point of the mult_isarr monkey business.
|
|
* Rather than passing back flags in a non-trivial way, maybe
|
|
* we could decide on the basis of flags passed down to us.
|
|
*
|
|
* This is the ideal place to do any last-minute conversion from
|
|
* array to strings. However, given all the transformations we've
|
|
* already done, probably if it's going to be done it will already
|
|
* have been. (I'd really like to keep everying in aval or
|
|
* equivalent and only locally decide if we need to treat it
|
|
* as a scalar.)
|
|
*/
|
|
if (isarr) {
|
|
char *x;
|
|
char *y;
|
|
int xlen;
|
|
int i;
|
|
LinkNode on = n;
|
|
|
|
/* Handle the (u) flag; we need this before the next test */
|
|
if (unique) {
|
|
if(!copied)
|
|
aval = arrdup(aval);
|
|
|
|
i = arrlen(aval);
|
|
if (i > 1)
|
|
zhuniqarray(aval);
|
|
}
|
|
if ((!aval[0] || !aval[1]) && !plan9) {
|
|
/*
|
|
* Empty array or single element. Currently you only
|
|
* get a single element array at this point from the
|
|
* unique expansion above. but we can potentially
|
|
* have other reasons.
|
|
*
|
|
* The following test removes the markers
|
|
* from surrounding double quotes, but I don't know why
|
|
* that's necessary.
|
|
*/
|
|
int vallen;
|
|
if (aptr > (char *) getdata(n) &&
|
|
aptr[-1] == Dnull && *fstr == Dnull)
|
|
*--aptr = '\0', fstr++;
|
|
vallen = aval[0] ? strlen(aval[0]) : 0;
|
|
y = (char *) hcalloc((aptr - ostr) + vallen + strlen(fstr) + 1);
|
|
strcpy(y, ostr);
|
|
*str = y + (aptr - ostr);
|
|
if (vallen)
|
|
{
|
|
strcpy(*str, aval[0]);
|
|
*str += vallen;
|
|
}
|
|
strcpy(*str, fstr);
|
|
setdata(n, y);
|
|
return n;
|
|
}
|
|
/* Handle (o) and (O) and their variants */
|
|
if (sortit) {
|
|
if (!copied)
|
|
aval = arrdup(aval);
|
|
if (indord) {
|
|
if (sortit & 2) {
|
|
char *copy;
|
|
char **end = aval + arrlen(aval) - 1, **start = aval;
|
|
|
|
/* reverse the array */
|
|
while (start < end) {
|
|
copy = *end;
|
|
*end-- = *start;
|
|
*start++ = copy;
|
|
}
|
|
}
|
|
} else {
|
|
static CompareFn sortfn[] = {
|
|
strpcmp, invstrpcmp, cstrpcmp, invcstrpcmp,
|
|
nstrpcmp, invnstrpcmp, instrpcmp, invinstrpcmp
|
|
};
|
|
|
|
i = arrlen(aval);
|
|
if (i && (*aval[i-1] || --i))
|
|
qsort(aval, i, sizeof(char *), sortfn[sortit-1]);
|
|
}
|
|
}
|
|
if (plan9) {
|
|
/* Handle RC_EXPAND_PARAM */
|
|
LinkNode tn;
|
|
local_list1(tl);
|
|
|
|
*--fstr = Marker;
|
|
init_list1(tl, fstr);
|
|
if (!eval && !stringsubst(&tl, firstnode(&tl), ssub, 0))
|
|
return NULL;
|
|
*str = aptr;
|
|
tn = firstnode(&tl);
|
|
while ((x = *aval++)) {
|
|
if (prenum || postnum)
|
|
x = dopadding(x, prenum, postnum, preone, postone,
|
|
premul, postmul);
|
|
if (eval && subst_parse_str(&x, (qt && !nojoin), quoteerr))
|
|
return NULL;
|
|
xlen = strlen(x);
|
|
for (tn = firstnode(&tl);
|
|
tn && *(y = (char *) getdata(tn)) == Marker;
|
|
incnode(tn)) {
|
|
strcatsub(&y, ostr, aptr, x, xlen, y + 1, globsubst,
|
|
copied);
|
|
if (qt && !*y && isarr != 2)
|
|
y = dupstring(nulstring);
|
|
if (plan9)
|
|
setdata(n, (void *) y), plan9 = 0;
|
|
else
|
|
insertlinknode(l, n, (void *) y), incnode(n);
|
|
}
|
|
}
|
|
for (; tn; incnode(tn)) {
|
|
y = (char *) getdata(tn);
|
|
if (*y == Marker)
|
|
continue;
|
|
if (qt && !*y && isarr != 2)
|
|
y = dupstring(nulstring);
|
|
if (plan9)
|
|
setdata(n, (void *) y), plan9 = 0;
|
|
else
|
|
insertlinknode(l, n, (void *) y), incnode(n);
|
|
}
|
|
if (plan9) {
|
|
uremnode(l, n);
|
|
return n;
|
|
}
|
|
} else {
|
|
/*
|
|
* Not RC_EXPAND_PARAM: simply join the first and
|
|
* last values.
|
|
* TODO: how about removing the restriction that
|
|
* aval[1] is non-NULL to promote consistency?, or
|
|
* simply changing the test so that we drop into
|
|
* the scalar branch, instead of tricking isarr?
|
|
*/
|
|
x = aval[0];
|
|
if (prenum || postnum)
|
|
x = dopadding(x, prenum, postnum, preone, postone,
|
|
premul, postmul);
|
|
if (eval && subst_parse_str(&x, (qt && !nojoin), quoteerr))
|
|
return NULL;
|
|
xlen = strlen(x);
|
|
strcatsub(&y, ostr, aptr, x, xlen, NULL, globsubst, copied);
|
|
if (qt && !*y && isarr != 2)
|
|
y = dupstring(nulstring);
|
|
setdata(n, (void *) y);
|
|
|
|
i = 1;
|
|
/* aval[1] is non-null here */
|
|
while (aval[i + 1]) {
|
|
x = aval[i++];
|
|
if (prenum || postnum)
|
|
x = dopadding(x, prenum, postnum, preone, postone,
|
|
premul, postmul);
|
|
if (eval && subst_parse_str(&x, (qt && !nojoin), quoteerr))
|
|
return NULL;
|
|
if (qt && !*x && isarr != 2)
|
|
y = dupstring(nulstring);
|
|
else {
|
|
y = dupstring(x);
|
|
if (globsubst)
|
|
shtokenize(y);
|
|
}
|
|
insertlinknode(l, n, (void *) y), incnode(n);
|
|
}
|
|
|
|
x = aval[i];
|
|
if (prenum || postnum)
|
|
x = dopadding(x, prenum, postnum, preone, postone,
|
|
premul, postmul);
|
|
if (eval && subst_parse_str(&x, (qt && !nojoin), quoteerr))
|
|
return NULL;
|
|
xlen = strlen(x);
|
|
*str = strcatsub(&y, aptr, aptr, x, xlen, fstr, globsubst, copied);
|
|
if (qt && !*y && isarr != 2)
|
|
y = dupstring(nulstring);
|
|
insertlinknode(l, n, (void *) y), incnode(n);
|
|
}
|
|
if (eval)
|
|
n = on;
|
|
} else {
|
|
/*
|
|
* Scalar value. Handle last minute transformations
|
|
* such as left- or right-padding and the (e) flag to
|
|
* revaluate the result.
|
|
*/
|
|
int xlen;
|
|
char *x;
|
|
char *y;
|
|
|
|
x = val;
|
|
if (prenum || postnum)
|
|
x = dopadding(x, prenum, postnum, preone, postone,
|
|
premul, postmul);
|
|
if (eval && subst_parse_str(&x, (qt && !nojoin), quoteerr))
|
|
return NULL;
|
|
xlen = strlen(x);
|
|
*str = strcatsub(&y, ostr, aptr, x, xlen, fstr, globsubst, copied);
|
|
if (qt && !*y)
|
|
y = dupstring(nulstring);
|
|
setdata(n, (void *) y);
|
|
}
|
|
if (eval)
|
|
*str = (char *) getdata(n);
|
|
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* Arithmetic substitution: `a' is the string to be evaluated, `bptr'
|
|
* points to the beginning of the string containing it. The tail of
|
|
* the string is given by `rest'. *bptr is modified with the substituted
|
|
* string. The function returns a pointer to the tail in the substituted
|
|
* string.
|
|
*/
|
|
|
|
/**/
|
|
static char *
|
|
arithsubst(char *a, char **bptr, char *rest)
|
|
{
|
|
char *s = *bptr, *t;
|
|
char buf[BDIGBUFSIZE], *b = buf;
|
|
mnumber v;
|
|
|
|
singsub(&a);
|
|
v = matheval(a);
|
|
if ((v.type & MN_FLOAT) && !outputradix)
|
|
b = convfloat(v.u.d, 0, 0, NULL);
|
|
else {
|
|
if (v.type & MN_FLOAT)
|
|
v.u.l = (zlong) v.u.d;
|
|
convbase(buf, v.u.l, outputradix);
|
|
}
|
|
t = *bptr = (char *) hcalloc(strlen(*bptr) + strlen(b) +
|
|
strlen(rest) + 1);
|
|
t--;
|
|
while ((*++t = *s++));
|
|
t--;
|
|
while ((*++t = *b++));
|
|
strcat(t, rest);
|
|
return t;
|
|
}
|
|
|
|
/**/
|
|
void
|
|
modify(char **str, char **ptr)
|
|
{
|
|
char *ptr1, *ptr2, *ptr3, del, *lptr, c, *test, *sep, *t, *tt, tc, *e;
|
|
char *copy, *all, *tmp, sav;
|
|
int gbal, wall, rec, al, nl;
|
|
|
|
test = NULL;
|
|
|
|
if (**ptr == ':')
|
|
*str = dupstring(*str);
|
|
|
|
while (**ptr == ':') {
|
|
lptr = *ptr;
|
|
(*ptr)++;
|
|
wall = gbal = 0;
|
|
rec = 1;
|
|
c = '\0';
|
|
sep = NULL;
|
|
|
|
for (; !c && **ptr;) {
|
|
switch (**ptr) {
|
|
case 'h':
|
|
case 'r':
|
|
case 'e':
|
|
case 't':
|
|
case 'l':
|
|
case 'u':
|
|
case 'q':
|
|
case 'Q':
|
|
c = **ptr;
|
|
break;
|
|
|
|
case 's':
|
|
c = **ptr;
|
|
(*ptr)++;
|
|
ptr1 = *ptr;
|
|
del = *ptr1++;
|
|
for (ptr2 = ptr1; *ptr2 != del && *ptr2; ptr2++);
|
|
if (!*ptr2) {
|
|
zerr("bad substitution", NULL, 0);
|
|
return;
|
|
}
|
|
*ptr2++ = '\0';
|
|
for (ptr3 = ptr2; *ptr3 != del && *ptr3; ptr3++);
|
|
if ((sav = *ptr3))
|
|
*ptr3++ = '\0';
|
|
if (*ptr1) {
|
|
zsfree(hsubl);
|
|
hsubl = ztrdup(ptr1);
|
|
}
|
|
if (!hsubl) {
|
|
zerr("no previous substitution", NULL, 0);
|
|
return;
|
|
}
|
|
zsfree(hsubr);
|
|
for (tt = hsubl; *tt; tt++)
|
|
if (INULL(*tt))
|
|
chuck(tt--);
|
|
untokenize(hsubl);
|
|
for (tt = hsubr = ztrdup(ptr2); *tt; tt++)
|
|
if (INULL(*tt))
|
|
chuck(tt--);
|
|
ptr2[-1] = del;
|
|
if (sav)
|
|
ptr3[-1] = sav;
|
|
*ptr = ptr3 - 1;
|
|
break;
|
|
|
|
case '&':
|
|
c = 's';
|
|
break;
|
|
|
|
case 'g':
|
|
(*ptr)++;
|
|
gbal = 1;
|
|
break;
|
|
|
|
case 'w':
|
|
wall = 1;
|
|
(*ptr)++;
|
|
break;
|
|
case 'W':
|
|
wall = 1;
|
|
(*ptr)++;
|
|
ptr1 = get_strarg(ptr2 = *ptr);
|
|
if ((sav = *ptr1))
|
|
*ptr1 = '\0';
|
|
sep = dupstring(ptr2 + 1);
|
|
if (sav)
|
|
*ptr1 = sav;
|
|
*ptr = ptr1 + 1;
|
|
c = '\0';
|
|
break;
|
|
|
|
case 'f':
|
|
rec = -1;
|
|
(*ptr)++;
|
|
break;
|
|
case 'F':
|
|
rec = get_intarg(ptr);
|
|
(*ptr)++;
|
|
break;
|
|
default:
|
|
*ptr = lptr;
|
|
return;
|
|
}
|
|
}
|
|
(*ptr)++;
|
|
if (!c) {
|
|
*ptr = lptr;
|
|
return;
|
|
}
|
|
if (rec < 0)
|
|
test = dupstring(*str);
|
|
|
|
while (rec--) {
|
|
if (wall) {
|
|
al = 0;
|
|
all = NULL;
|
|
for (t = e = *str; (tt = findword(&e, sep));) {
|
|
tc = *e;
|
|
*e = '\0';
|
|
copy = dupstring(tt);
|
|
*e = tc;
|
|
switch (c) {
|
|
case 'h':
|
|
remtpath(©);
|
|
break;
|
|
case 'r':
|
|
remtext(©);
|
|
break;
|
|
case 'e':
|
|
rembutext(©);
|
|
break;
|
|
case 't':
|
|
remlpaths(©);
|
|
break;
|
|
case 'l':
|
|
downcase(©);
|
|
break;
|
|
case 'u':
|
|
upcase(©);
|
|
break;
|
|
case 's':
|
|
if (hsubl && hsubr)
|
|
subst(©, hsubl, hsubr, gbal);
|
|
break;
|
|
case 'q':
|
|
copy = bslashquote(copy, NULL, 0);
|
|
break;
|
|
case 'Q':
|
|
{
|
|
int one = noerrs, oef = errflag;
|
|
|
|
noerrs = 1;
|
|
parse_subst_string(copy);
|
|
noerrs = one;
|
|
errflag = oef;
|
|
remnulargs(copy);
|
|
untokenize(copy);
|
|
}
|
|
break;
|
|
}
|
|
tc = *tt;
|
|
*tt = '\0';
|
|
nl = al + strlen(t) + strlen(copy);
|
|
ptr1 = tmp = (char *)zhalloc(nl + 1);
|
|
if (all)
|
|
for (ptr2 = all; *ptr2;)
|
|
*ptr1++ = *ptr2++;
|
|
for (ptr2 = t; *ptr2;)
|
|
*ptr1++ = *ptr2++;
|
|
*tt = tc;
|
|
for (ptr2 = copy; *ptr2;)
|
|
*ptr1++ = *ptr2++;
|
|
*ptr1 = '\0';
|
|
al = nl;
|
|
all = tmp;
|
|
t = e;
|
|
}
|
|
*str = all;
|
|
|
|
} else {
|
|
switch (c) {
|
|
case 'h':
|
|
remtpath(str);
|
|
break;
|
|
case 'r':
|
|
remtext(str);
|
|
break;
|
|
case 'e':
|
|
rembutext(str);
|
|
break;
|
|
case 't':
|
|
remlpaths(str);
|
|
break;
|
|
case 'l':
|
|
downcase(str);
|
|
break;
|
|
case 'u':
|
|
upcase(str);
|
|
break;
|
|
case 's':
|
|
if (hsubl && hsubr) {
|
|
char *oldstr = *str;
|
|
|
|
subst(str, hsubl, hsubr, gbal);
|
|
if (*str != oldstr) {
|
|
*str = dupstring(oldstr = *str);
|
|
zsfree(oldstr);
|
|
}
|
|
}
|
|
break;
|
|
case 'q':
|
|
*str = bslashquote(*str, NULL, 0);
|
|
break;
|
|
case 'Q':
|
|
{
|
|
int one = noerrs, oef = errflag;
|
|
|
|
noerrs = 1;
|
|
parse_subst_string(*str);
|
|
noerrs = one;
|
|
errflag = oef;
|
|
remnulargs(*str);
|
|
untokenize(*str);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (rec < 0) {
|
|
if (!strcmp(test, *str))
|
|
rec = 0;
|
|
else
|
|
test = dupstring(*str);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* get a directory stack entry */
|
|
|
|
/**/
|
|
static char *
|
|
dstackent(char ch, int val)
|
|
{
|
|
int backwards;
|
|
LinkNode end=(LinkNode)dirstack, n;
|
|
|
|
backwards = ch == (isset(PUSHDMINUS) ? '+' : '-');
|
|
if(!backwards && !val--)
|
|
return pwd;
|
|
if (backwards)
|
|
for (n=lastnode(dirstack); n != end && val; val--, n=prevnode(n));
|
|
else
|
|
for (end=NULL, n=firstnode(dirstack); n && val; val--, n=nextnode(n));
|
|
if (n == end) {
|
|
if (backwards && !val)
|
|
return pwd;
|
|
if (isset(NOMATCH))
|
|
zerr("not enough directory stack entries.", NULL, 0);
|
|
return NULL;
|
|
}
|
|
return (char *)getdata(n);
|
|
}
|