Logo Search packages:      
Sourcecode: ksh version File versions  Download package

macro.c

/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1982-2010 AT&T Intellectual Property          *
*                      and is licensed under the                       *
*                  Common Public License, Version 1.0                  *
*                    by AT&T Intellectual Property                     *
*                                                                      *
*                A copy of the License is available at                 *
*            http://www.opensource.org/licenses/cpl1.0.txt             *
*         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
*                                                                      *
*              Information and Software Systems Research               *
*                            AT&T Research                             *
*                           Florham Park NJ                            *
*                                                                      *
*                  David Korn <dgk@research.att.com>                   *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * Shell macro expander
 * expands ~
 * expands ${...}
 * expands $(...)
 * expands $((...))
 * expands `...`
 *
 *   David Korn
 *   AT&T Labs
 *
 */

#include    "defs.h"
#include    <fcin.h>
#include    <pwd.h>
#include    "name.h"
#include    "variables.h"
#include    "shlex.h"
#include    "io.h"
#include    "jobs.h"
#include    "shnodes.h"
#include    "path.h"
#include    "national.h"
#include    "streval.h"

#undef STR_GROUP
#ifndef STR_GROUP
#   define STR_GROUP    0
#endif

#if !SHOPT_MULTIBYTE
#define mbchar(p)       (*(unsigned char*)p++)
#endif

static int  _c_;
typedef struct  _mac_
{
      Shell_t           *shp;       /* pointer to shell interpreter */
      Sfio_t            *sp;        /* stream pointer for here-document */
      struct argnod     **arghead;  /* address of head of argument list */
      char        *ifsp;            /* pointer to IFS value */
      int         fields;           /* number of fields */
      short       quoted;           /* set when word has quotes */
      unsigned char     ifs;        /* first char of IFS */
      char        quote;            /* set within double quoted contexts */
      char        lit;        /* set within single quotes */
      char        split;            /* set when word splittin is possible */
      char        pattern;    /* set when file expansion follows */
      char        patfound;   /* set if pattern character found */
      char        assign;           /* set for assignments */
      char        arith;            /* set for ((...)) */
      char        let;        /* set when expanding let arguments */
      char        zeros;            /* strip leading zeros when set */
      char        arrayok;    /* $x[] ok for arrays */
      char        subcopy;    /* set when copying subscript */
      int         dotdot;           /* set for .. in subscript */
      void        *nvwalk;    /* for name space walking*/
} Mac_t;

#undef ESCAPE
#define ESCAPE          '\\'
#define isescchar(s)    ((s)>S_QUOTE)
#define isqescchar(s)   ((s)>=S_QUOTE)
#define isbracechar(c)  ((c)==RBRACE || (_c_=sh_lexstates[ST_BRACE][c])==S_MOD1 ||_c_==S_MOD2)
#define ltos(x)         fmtbase((long)(x),0,0)

/* type of macro expansions */
#define M_BRACE         1     /* ${var}   */
#define M_TREE          2     /* ${var.}  */
#define M_SIZE          3     /* ${#var}  */
#define M_VNAME         4     /* ${!var}  */
#define M_SUBNAME 5     /* ${!var[sub]}   */
#define M_NAMESCAN      6     /* ${!var*} */
#define M_NAMECOUNT     7     /* ${#var*} */
#define M_TYPE          8     /* ${@var}  */

static int  substring(const char*, const char*, int[], int);
static void copyto(Mac_t*, int, int);
static void comsubst(Mac_t*, Shnode_t*, int);
static int  varsub(Mac_t*);
static void mac_copy(Mac_t*,const char*, int);
static void tilde_expand2(Shell_t*,int);
static char       *sh_tilde(Shell_t*,const char*);
static char *special(Shell_t *,int);
static void endfield(Mac_t*,int);
static void mac_error(Namval_t*);
static char *mac_getstring(char*);
static int  charlen(const char*,int);
#if SHOPT_MULTIBYTE
    static char   *lastchar(const char*,const char*);
#endif /* SHOPT_MULTIBYTE */

void *sh_macopen(Shell_t *shp)
{
      void *addr = newof(0,Mac_t,1,0);
      Mac_t *mp = (Mac_t*)addr;
      mp->shp = shp;
      return(addr);
}

/*
 * perform only parameter substitution and catch failures
 */
char *sh_mactry(Shell_t *shp,register char *string)
{
      if(string)
      {
            int         jmp_val;
            int         savexit = shp->savexit;
            struct checkpt    buff;
            sh_pushcontext(&buff,SH_JMPSUB);
            jmp_val = sigsetjmp(buff.buff,0);
            if(jmp_val == 0)
                  string = sh_mactrim(shp,string,0);
            sh_popcontext(&buff);
            shp->savexit = savexit;
            return(string);
      }
      return("");
}

/*
 * Perform parameter expansion, command substitution, and arithmetic
 * expansion on <str>. 
 * If <mode> greater than 1 file expansion is performed if the result 
 * yields a single pathname.
 * If <mode> negative, than expansion rules for assignment are applied.
 */
char *sh_mactrim(Shell_t *shp, char *str, register int mode)
{
      register Mac_t    *mp = (Mac_t*)shp->mac_context;
      Stk_t       *stkp = shp->stk;
      Mac_t       savemac;
      savemac = *mp;
      stkseek(stkp,0);
      mp->arith = (mode==3);
      mp->let = 0;
      shp->argaddr = 0;
      mp->pattern = (mode==1||mode==2);
      mp->patfound = 0;
      mp->assign = 0;
      if(mode<0)
            mp->assign = -mode;
      mp->quoted = mp->lit = mp->split = mp->quote = 0;
      mp->sp = 0;
      if(mp->ifsp=nv_getval(sh_scoped(shp,IFSNOD)))
            mp->ifs = *mp->ifsp;
      else
            mp->ifs = ' ';
      stkseek(stkp,0);
      fcsopen(str);
      copyto(mp,0,mp->arith);
      str = stkfreeze(stkp,1);
      if(mode==2)
      {
            /* expand only if unique */
            struct argnod *arglist=0;
            if((mode=path_expand(str,&arglist))==1)
                  str = arglist->argval;
            else if(mode>1)
                  errormsg(SH_DICT,ERROR_exit(1),e_ambiguous,str);
            sh_trim(str);
      }
      *mp = savemac;
      return(str);
}

/*
 * Perform all the expansions on the argument <argp>
 */
int sh_macexpand(Shell_t* shp, register struct argnod *argp, struct argnod **arghead,int flag)
{
      register int      flags = argp->argflag;
      register char     *str = argp->argval;
      register Mac_t  *mp = (Mac_t*)shp->mac_context;
      char        **saveargaddr = shp->argaddr;
      Mac_t       savemac;
      Stk_t       *stkp = shp->stk;
      savemac = *mp;
      mp->sp = 0;
      if(mp->ifsp=nv_getval(sh_scoped(shp,IFSNOD)))
            mp->ifs = *mp->ifsp;
      else
            mp->ifs = ' ';
      if((flag&ARG_OPTIMIZE) && !shp->indebug)
            shp->argaddr = (char**)&argp->argchn.ap;
      else
            shp->argaddr = 0;
      mp->arghead = arghead;
      mp->quoted = mp->lit = mp->quote = 0;
      mp->arith = ((flag&ARG_ARITH)!=0);
      mp->let = ((flag&ARG_LET)!=0);
      mp->split = !(flag&ARG_ASSIGN);
      mp->assign = !mp->split;
      mp->pattern = mp->split && !(flag&ARG_NOGLOB) && !sh_isoption(SH_NOGLOB);
      mp->arrayok = mp->arith || (flag&ARG_ARRAYOK);
      str = argp->argval;
      fcsopen(str);
      mp->fields = 0;
      if(!arghead)
      {
            mp->split = 0;
            mp->pattern = ((flag&ARG_EXP)!=0);
            stkseek(stkp,0);
      }
      else
      {
            stkseek(stkp,ARGVAL);
            *stkptr(stkp,ARGVAL-1) = 0;
      }
      mp->patfound = 0;
      if(mp->pattern)
            mp->arrayok = 0;
      copyto(mp,0,mp->arith);
      if(!arghead)
      {
            argp->argchn.cp = stkfreeze(stkp,1);
            if(shp->argaddr)
                  argp->argflag |= ARG_MAKE;
      }
      else
      {
            endfield(mp,mp->quoted);
            flags = mp->fields;
            if(flags==1 && shp->argaddr)
                  argp->argchn.ap = *arghead; 
      }
      shp->argaddr = saveargaddr;
      *mp = savemac;
      return(flags);
}

/*
 * Expand here document which is stored in <infile> or <string>
 * The result is written to <outfile>
 */
void sh_machere(Shell_t *shp,Sfio_t *infile, Sfio_t *outfile, char *string)
{
      register int      c,n;
      register const char     *state = sh_lexstates[ST_QUOTE];
      register char     *cp;
      register Mac_t    *mp = (Mac_t*)shp->mac_context;
      Lex_t       *lp = (Lex_t*)mp->shp->lex_context;
      Fcin_t            save;
      Mac_t       savemac;
      Stk_t       *stkp = shp->stk;
      savemac = *mp;
      stkseek(stkp,0);
      shp->argaddr = 0;
      mp->sp = outfile;
      mp->split = mp->assign = mp->pattern = mp->patfound = mp->lit = mp->arith = mp->let = 0;
      mp->quote = 1;
      mp->ifsp = nv_getval(sh_scoped(shp,IFSNOD));
      mp->ifs = ' ';
      fcsave(&save);
      if(infile)
            fcfopen(infile);
      else
            fcsopen(string);
      fcnotify(0,lp);
      cp = fcseek(0);
      while(1)
      {
#if SHOPT_MULTIBYTE
            if(mbwide())
            {
                  do
                  {
                        ssize_t len;
                        switch(len = mbsize(cp))
                        {
                            case -1:      /* illegal multi-byte char */
                            case 0:
                            case 1:
                              n=state[*(unsigned char*)cp++];
                              break;
                            default:
                              /* use state of alpha character */
                              n=state['a'];
                              cp += len;
                        }
                  }
                  while(n == 0);
            }
            else
#endif /* SHOPT_MULTIBYTE */
            while((n=state[*(unsigned char*)cp++])==0);
            if(n==S_NL || n==S_QUOTE || n==S_RBRA)
                  continue;
            if(c=(cp-1)-fcseek(0))
                  sfwrite(outfile,fcseek(0),c);
            cp = fcseek(c+1);
            switch(n)
            {
                case S_EOF:
                  if((n=fcfill()) <=0)
                  {
                        /* ignore 0 byte when reading from file */
                        if(n==0 && fcfile())
                              continue;
                        fcrestore(&save);
                        *mp = savemac;
                        return;
                  }
                  cp = fcseek(-1);
                  continue;
                case S_ESC:
                  fcgetc(c);
                  cp=fcseek(-1);
                  if(c>0)
                        cp++;
                  if(!isescchar(state[c]))
                        sfputc(outfile,ESCAPE);
                  continue;
                case S_GRAVE:
                  comsubst(mp,(Shnode_t*)0,0);
                  break;
                case S_DOL:
                  c = fcget();
                  if(c=='.')
                        goto regular;
                again:
                  switch(n=sh_lexstates[ST_DOL][c])
                  {
                      case S_ALP: case S_SPC1: case S_SPC2:
                      case S_DIG: case S_LBRA:
                      {
                        Fcin_t      save2;
                        int   offset = stktell(stkp);
                        int   offset2;
                        sfputc(stkp,c);
                        if(n==S_LBRA)
                        {
                              c = fcget();
                              fcseek(-1);
                              if(sh_lexstates[ST_NORM][c]==S_BREAK)
                              {
                                    comsubst(mp,(Shnode_t*)0,2);
                                    break;
                              }
                              sh_lexskip(lp,RBRACE,1,ST_BRACE);
                        }
                        else if(n==S_ALP)
                        {
                              while(fcgetc(c),isaname(c))
                                    sfputc(stkp,c);
                              fcseek(-1);
                        }
                        sfputc(stkp,0);
                        offset2 = stktell(stkp);
                        fcsave(&save2);
                        fcsopen(stkptr(stkp,offset));
                        varsub(mp);
                        if(c=stktell(stkp)-offset2)
                              sfwrite(outfile,(char*)stkptr(stkp,offset2),c);
                        fcrestore(&save2);
                        stkseek(stkp,offset);
                        break;
                      }
                      case S_PAR:
                        comsubst(mp,(Shnode_t*)0,1);
                        break;
                      case S_EOF:
                        if((c=fcfill()) > 0)
                              goto again;
                        /* FALL THRU */
                      default:
                      regular:
                        sfputc(outfile,'$');
                        fcseek(-1);
                        break;
                  }
            }
            cp = fcseek(0);
      }
}

/*
 * expand argument but do not trim pattern characters
 */
char *sh_macpat(Shell_t *shp,register struct argnod *arg, int flags)
{
      register char *sp = arg->argval;
      if((arg->argflag&ARG_RAW))
            return(sp);
      sh_stats(STAT_ARGEXPAND);
      if(flags&ARG_OPTIMIZE)
            arg->argchn.ap=0;
      if(!(sp=arg->argchn.cp))
      {
            sh_macexpand(shp,arg,NIL(struct argnod**),flags|ARG_ARRAYOK);
            sp = arg->argchn.cp;
            if(!(flags&ARG_OPTIMIZE) || !(arg->argflag&ARG_MAKE))
                  arg->argchn.cp = 0;
            arg->argflag &= ~ARG_MAKE;
      }
      else
            sh_stats(STAT_ARGHITS);
      return(sp);
}

/*
 * Process the characters up to <endch> or end of input string 
 */
static void copyto(register Mac_t *mp,int endch, int newquote)
{
      register int      c,n;
      register const char     *state = sh_lexstates[ST_MACRO];
      register char     *cp,*first;
      Lex_t       *lp = (Lex_t*)mp->shp->lex_context;
      int         tilde = -1;
      int         oldquote = mp->quote;
      int         ansi_c = 0;
      int         paren = 0;
      int         ere = 0;
      int         brace = 0;
      Sfio_t            *sp = mp->sp;
      Stk_t       *stkp = mp->shp->stk;
      mp->sp = NIL(Sfio_t*);
      mp->quote = newquote;
      first = cp = fcseek(0);
      if(!mp->quote && *cp=='~' && cp[1]!=LPAREN)
            tilde = stktell(stkp);
      /* handle // operator specially */
      if(mp->pattern==2 && *cp=='/')
            cp++;
      while(1)
      {
#if SHOPT_MULTIBYTE
            if(mbwide())
            {
                  ssize_t len;
                  do
                  {
                        switch(len = mbsize(cp))
                        {
                            case -1:      /* illegal multi-byte char */
                            case 0:
                              len = 1;
                            case 1:
                              n = state[*(unsigned char*)cp++];
                              break;
                            default:
                              /* treat as if alpha */
                              cp += len;
                              n=state['a'];
                        }
                  }
                  while(n == 0);
                  c = (cp-len) - first;
            }
            else
#endif /* SHOPT_MULTIBYTE */
            {
                  while((n=state[*(unsigned char*)cp++])==0);
                  c = (cp-1) - first;
            }
            switch(n)
            {
                case S_ESC:
                  if(ansi_c)
                  {
                        /* process ANSI-C escape character */
                        char *addr= --cp;
                        if(c)
                              sfwrite(stkp,first,c);
                        c = chresc(cp,&addr);
                        cp = addr;
                        first = fcseek(cp-first);
#if SHOPT_MULTIBYTE
                        if(c > UCHAR_MAX && mbwide())
                        {
                              int         i;
                              unsigned char     mb[8];

                              n = wctomb((char*)mb, c);
                              for(i=0;i<n;i++)
                                    sfputc(stkp,mb[i]);
                        }
                        else
#endif /* SHOPT_MULTIBYTE */
                        sfputc(stkp,c);
                        if(c==ESCAPE && mp->pattern)
                              sfputc(stkp,ESCAPE);
                        break;
                  }
                  else if(sh_isoption(SH_BRACEEXPAND) && mp->pattern==4 && (*cp==',' || *cp==LBRACE || *cp==RBRACE || *cp=='.'))
                        break;
                  else if(mp->split && endch && !mp->quote && !mp->lit)
                  {
                        if(c)
                              mac_copy(mp,first,c);
                        cp = fcseek(c+2);
                        if(c= cp[-1])
                        {
                              sfputc(stkp,c);
                              if(c==ESCAPE)
                                    sfputc(stkp,ESCAPE);
                        }
                        else
                              cp--;
                        first = cp;
                        break;
                  }
                  n = state[*(unsigned char*)cp];
                  if(n==S_ENDCH && *cp!=endch)
                        n = S_PAT;
                  if(mp->pattern)
                  {
                        /* preserve \digit for pattern matching */
                        /* also \alpha for extended patterns */
                        if(!mp->lit && !mp->quote)
                        {
                              if((n==S_DIG || ((paren+ere) && sh_lexstates[ST_DOL][*(unsigned char*)cp]==S_ALP)))
                                    break;
                              if(ere && mp->pattern==1 && strchr(".[()*+?{|^$&!",*cp))
                                    break;
                        }
                        /* followed by file expansion */
                        if(!mp->lit && (n==S_ESC || (!mp->quote && 
                              (n==S_PAT||n==S_ENDCH||n==S_SLASH||n==S_BRACT||*cp=='-'))))
                        {
                              cp += (n!=S_EOF);
                              if(ere && n==S_ESC && *cp =='\\' && cp[1]=='$')
                              {
                                    /* convert \\\$ into \$' */
                                    sfwrite(stkp,first,c+1);
                                    cp = first = fcseek(c+3);
                              }
                              break;
                        }
                        if(!(ere && *cp=='$') && (mp->lit || (mp->quote && !isqescchar(n) && n!=S_ENDCH)))
                        {
                              /* add \ for file expansion */
                              sfwrite(stkp,first,c+1);
                              first = fcseek(c);
                              break;
                        }
                  }
                  if(mp->lit)
                        break;
                  if(!mp->quote || isqescchar(n) || n==S_ENDCH)
                  {
                        /* eliminate \ */
                        if(c)
                              sfwrite(stkp,first,c);
                        /* check new-line joining */
                        first = fcseek(c+1);
                  }
                  cp += (n!=S_EOF);
                  break;
                case S_GRAVE: case S_DOL:
                  if(mp->lit)
                        break;
                  if(c)
                  {
                        if(mp->split && !mp->quote && endch)
                              mac_copy(mp,first,c);
                        else
                              sfwrite(stkp,first,c);
                  }
                  first = fcseek(c+1);
                  c = mp->pattern;
                  if(n==S_GRAVE)
                        comsubst(mp,(Shnode_t*)0,0);
                  else if((n= *cp)==0 || !varsub(mp))
                  {
                        if(n=='\'' && !mp->quote)
                              ansi_c = 1;
                        else if(mp->quote || n!='"')
                              sfputc(stkp,'$');
                  }
                  cp = first = fcseek(0);
                  if(*cp)
                        mp->pattern = c;
                  break;
                case S_ENDCH:
                  if((mp->lit || cp[-1]!=endch || mp->quote!=newquote))
                        goto pattern;
                  if(endch==RBRACE && *cp==LPAREN && mp->pattern && brace)
                        goto pattern;
                case S_EOF:
                  if(c)
                  {
                        if(mp->split && !mp->quote && !mp->lit && endch)
                              mac_copy(mp,first,c);
                        else
                              sfwrite(stkp,first,c);
                  }
                  c += (n!=S_EOF);
                  first = fcseek(c);
                  if(tilde>=0)
                        tilde_expand2(mp->shp,tilde);
                  goto done;
                case S_QUOTE:
                  if(mp->lit || mp->arith)
                        break;
                case S_LIT:
                  if(mp->arith)
                  {
                        if((*cp=='`' || *cp=='[') && cp[1]=='\'')
                              cp +=2;
                        break;
                  }
                  if(n==S_LIT && mp->quote)
                        break;
                  if(c)
                  {
                        if(mp->split && endch && !mp->quote && !mp->lit)
                              mac_copy(mp,first,c);
                        else
                              sfwrite(stkp,first,c);
                  }
                  first = fcseek(c+1);
                  if(n==S_LIT)
                  {
                        if(mp->quote)
                              continue;
                        if(mp->lit)
                              mp->lit = ansi_c = 0;
                        else
                              mp->lit = 1;
                  }
                  else
                        mp->quote = !mp->quote;
                  mp->quoted++;
                  break;
                case S_BRACT:
                  if(mp->arith || (((mp->assign&1) || endch==RBRACT) &&
                        !(mp->quote || mp->lit)))
                  {
                        int offset=0,oldpat = mp->pattern;
                        int oldarith = mp->arith, oldsub=mp->subcopy;
                        sfwrite(stkp,first,++c);
                        if(mp->assign&1)
                        {
                              if(first[c-2]=='.')
                                    offset = stktell(stkp);
                              if(isastchar(*cp) && cp[1]==']')
                                    errormsg(SH_DICT,ERROR_exit(1),
e_badsubscript,*cp);
                        }
                        first = fcseek(c);
                        mp->pattern = 4;
                        mp->arith = 0;
                        mp->subcopy = 0;
                        copyto(mp,RBRACT,0);
                        mp->subcopy = oldsub;
                        mp->arith = oldarith;
                        mp->pattern = oldpat;
                        sfputc(stkp,RBRACT);
                        if(offset)
                        {
                              cp = stkptr(stkp,stktell(stkp));
                              if(sh_checkid(stkptr(stkp,offset),cp)!=cp)
                                    stkseek(stkp,stktell(stkp)-2);
                        }
                        cp = first = fcseek(0);
                        break;
                  }
                case S_PAT:
                  if(mp->pattern && !(mp->quote || mp->lit))
                  {
                        mp->patfound = mp->pattern;
                        if((n=cp[-1])==LPAREN)
                        {
                              paren++;
                              if((cp-first)>1 && cp[-2]=='~')
                              {
                                    char *p = cp;
                                    while((c=mbchar(p)) && c!=RPAREN)
                                          if(c=='A'||c=='E'||c=='K'||c=='P'||c=='X')
                                          {
                                                ere = 1;
                                                break;
                                          }
                              }
                        }
                        else if(n==RPAREN)
                              --paren;
                  }
                  goto pattern;
                case S_COM:
                  if(mp->pattern==4 && (mp->quote || mp->lit))
                  {
                        if(c)
                        {
                              sfwrite(stkp,first,c);
                              first = fcseek(c);
                        }
                        sfputc(stkp,ESCAPE);
                  }
                  break;
                case S_BRACE:
                  if(!(mp->quote || mp->lit))
                  {
                        mp->patfound = mp->split && sh_isoption(SH_BRACEEXPAND);
                        brace = 1;
                  }
                pattern:
                  if(!mp->pattern || !(mp->quote || mp->lit))
                  {
                        /* mark beginning of {a,b} */
                        if(n==S_BRACE && endch==0 && mp->pattern)
                              mp->pattern=4;
                        if(n==S_SLASH && mp->pattern==2)
                              mp->pattern=3;
                        break;
                  }
                  if(mp->pattern==3)
                        break;
                  if(c)
                        sfwrite(stkp,first,c);
                  first = fcseek(c);
                  sfputc(stkp,ESCAPE);
                  break;
                case S_EQ:
                  if(mp->assign==1)
                  {
                        if(*cp=='~' && !endch && !mp->quote && !mp->lit)
                              tilde = stktell(stkp)+(c+1);
                        mp->assign = 2;
                  }
                  break;
                case S_SLASH:
                case S_COLON:
                  if(tilde >=0)
                  {
                        if(c)
                              sfwrite(stkp,first,c);
                        first = fcseek(c);
                        tilde_expand2(mp->shp,tilde);
                        tilde = -1;
                        c=0;
                  }
                  if(n==S_COLON && mp->assign==2 && *cp=='~' && endch==0 && !mp->quote &&!mp->lit)
                        tilde = stktell(stkp)+(c+1);
                  else if(n==S_SLASH && mp->pattern==2)
#if 0
                        goto pattern;
#else
                  {
                        if(mp->quote || mp->lit)
                              goto pattern;
                        sfwrite(stkp,first,c+1);
                        first = fcseek(c+1);
                        c = stktell(stkp);
                        sh_lexskip(lp,RBRACE,0,ST_NESTED);
                        stkseek(stkp,c);
                        cp = fcseek(-1);
                        sfwrite(stkp,first,cp-first);
                        first=cp;
                  }
#endif
                  break;
                case S_DOT:
                  if(*cp=='.' && mp->subcopy==1)
                  {
                        sfwrite(stkp,first,c);
                        sfputc(stkp,0);
                        mp->dotdot = stktell(stkp);
                        cp = first = fcseek(c+2);
                  }
                  break;
            }
      }
done:
      mp->sp = sp;
      mp->quote = oldquote;
}

/*
 * copy <str> to stack performing sub-expression substitutions
 */
static void mac_substitute(Mac_t *mp, register char *cp,char *str,register int subexp[],int subsize)
{
      register int      c,n;
      register char *first=fcseek(0);
      char        *ptr;
      Mac_t       savemac;
      Stk_t       *stkp = mp->shp->stk;
      n = stktell(stkp);
      savemac = *mp;
      mp->pattern = 3;
      mp->split = 0;
      fcsopen(cp);
      copyto(mp,0,0);
      sfputc(stkp,0);
      ptr = cp = strdup(stkptr(stkp,n));
      stkseek(stkp,n);
      *mp = savemac;
      fcsopen(first);
      first = cp;
      while(1)
      {
            while((c= *cp++) && c!=ESCAPE);
            if(c==0)
                  break;
            if((n= *cp++)=='\\' || n==RBRACE || (n>='0' && n<='9' && (n-='0')<subsize))
            {
                  c = cp-first-2;
                  if(c)
                        mac_copy(mp,first,c);
                  first=cp;
                  if(n=='\\' || n==RBRACE)
                  {
                        first--;
                        continue;
                  }
                  if((c=subexp[2*n])>=0)
                  {
                        if((n=subexp[2*n+1]-c)>0)
                              mac_copy(mp,str+c,n);
                  }
            }
            else if(n==0)
                  break;
      }
      if(n=cp-first-1)
            mac_copy(mp,first,n);
      free(ptr);
}

#if  SHOPT_FILESCAN
#define     MAX_OFFSETS  (sizeof(shp->offsets)/sizeof(shp->offsets[0]))
#define MAX_ARGN  (32*1024)

/*
 * compute the arguments $1 ... $n and $# from the current line as needed
 * save line offsets in the offsets array.
 */
static char *getdolarg(Shell_t *shp, int n, int *size)
{
      register int c=S_DELIM, d=shp->ifstable['\\'];
      register unsigned char *first,*last,*cp = (unsigned char*)shp->cur_line;
      register int m=shp->offsets[0],delim=0;
      if(m==0)
            return(0);
      if(m<0)
            m = 0;
      else if(n<=m)
            m = n-1;
      else
            m--;
      if(m >= MAX_OFFSETS-1)
            m =  MAX_OFFSETS-2;
      cp += shp->offsets[m+1];
      n -= m;
      shp->ifstable['\\'] = 0;
      shp->ifstable[0] = S_EOF;
      while(1)
      {
            if(c==S_DELIM)
                  while(shp->ifstable[*cp++]==S_SPACE);
            first = --cp;
            if(++m < MAX_OFFSETS)
                  shp->offsets[m] = (first-(unsigned char*)shp->cur_line);
            while((c=shp->ifstable[*cp++])==0);
            last = cp-1;
            if(c==S_SPACE)
                  while((c=shp->ifstable[*cp++])==S_SPACE);
            if(--n==0 || c==S_EOF)
            {
                  if(last==first && c==S_EOF && (!delim || (m>1)))
                  {
                        n++;
                        m--;
                  }
                  break;
            }
            delim = (c==S_DELIM);
      }
      shp->ifstable['\\'] = d;
      if(m > shp->offsets[0])
            shp->offsets[0] = m;
      if(n)
            first = last = 0;
      if(size)
            *size = last-first;
      return((char*)first);
}
#endif /* SHOPT_FILESCAN */

/*
 * get the prefix after name reference resolution
 */
static char *prefix(Shell_t *shp, char *id)
{
      Namval_t *np;
      register char *sub=0, *cp = strchr(id,'.');
      if(cp)
      {
            *cp = 0;
            np = nv_search(id, shp->var_tree,0);
            *cp = '.';
            if(isastchar(cp[1]))
                  cp[1] = 0;
            if(np && nv_isref(np))
            {
                  int n;
                  char *sp;
                  shp->argaddr = 0;
                  while(nv_isref(np) && np->nvalue.cp)
                  {
                        sub = nv_refsub(np);
                        np = nv_refnode(np);
                        if(sub)
                              nv_putsub(np,sub,0L);
                  }
                  id = (char*)malloc(strlen(cp)+1+(n=strlen(sp=nv_name(np)))+ (sub?strlen(sub)+3:1));
                  memcpy(id,sp,n);
                  if(sub)
                  {
                        id[n++] = '[';
                        strcpy(&id[n],sub);
                        n+= strlen(sub)+1;
                        id[n-1] = ']';
                  }
                  strcpy(&id[n],cp);
                  return(id);
            }
      }
      return(strdup(id));
}

/*
 * copy to ']' onto the stack and return offset to it
 */
static int subcopy(Mac_t *mp, int flag)
{
      int split = mp->split;
      int xpattern = mp->pattern;
      int loc = stktell(mp->shp->stk);
      int xarith = mp->arith;
      int arrayok = mp->arrayok;
      mp->split = 0;
      mp->arith = 0;
      mp->pattern = flag?4:0;
      mp->arrayok=1;
      mp->subcopy++;
      mp->dotdot = 0;
      copyto(mp,RBRACT,0);
      mp->subcopy = 0;
      mp->pattern = xpattern;
      mp->split = split;
      mp->arith = xarith;
      mp->arrayok = arrayok;
      return(loc);
}

/*
 * if name is a discipline function, run the function and put the results
 * on the stack so that ${x.foo} behaves like ${ x.foo;}
 */
int sh_macfun(Shell_t *shp, const char *name, int offset)
{
      Namval_t    *np, *nq;
      np = nv_bfsearch(name,shp->fun_tree,&nq,(char**)0);
      if(np)
      {
            /* treat ${x.foo} as ${x.foo;} */
            Shnode_t *tp;
            char buff[sizeof(struct dolnod)+sizeof(char*)];
            struct comnod node;
            struct dolnod *dp = (struct dolnod*)buff;
            memset(&node,0,sizeof(node));
            memset(&buff,0,sizeof(buff));
            tp = (Shnode_t*)&node;
            tp->com.comarg = (struct argnod*)dp;
            tp->com.comline = shp->inlineno;
            dp->dolnum = 1;
            dp->dolval[0] = strdup(name);
            stkseek(shp->stk,offset);
            comsubst((Mac_t*)shp->mac_context,tp,2);
            free(dp->dolval[0]);
            return(1);
      }
      return(0);
}

static int namecount(Mac_t *mp,const char *prefix)
{
      int count = 0;
      mp->nvwalk = nv_diropen((Namval_t*)0,prefix);
      while(nv_dirnext(mp->nvwalk))
            count++;
      nv_dirclose(mp->nvwalk);
      return(count);
}

static char *nextname(Mac_t *mp,const char *prefix, int len)
{
      char *cp;
      if(len==0)
      {
            mp->nvwalk = nv_diropen((Namval_t*)0,prefix);
            return((char*)mp->nvwalk);
      }
      if(!(cp=nv_dirnext(mp->nvwalk)))
            nv_dirclose(mp->nvwalk);
      return(cp);
}

/*
 * This routine handles $param,  ${parm}, and ${param op word}
 * The input stream is assumed to be a string
 */
static int varsub(Mac_t *mp)
{
      register int      c;
      register int      type=0; /* M_xxx */
      register char     *v,*argp=0;
      register Namval_t *np = NIL(Namval_t*);
      register int      dolg=0, mode=0;
      Lex_t       *lp = (Lex_t*)mp->shp->lex_context;
      Namarr_t    *ap=0;
      int         dolmax=0, vsize= -1, offset= -1, nulflg, replen=0, bysub=0;
      char        idbuff[3], *id = idbuff, *pattern=0, *repstr, *arrmax=0;
      int         var=1,addsub=0,oldpat=mp->pattern,idnum=0,flag=0,d;
      Stk_t       *stkp = mp->shp->stk;
retry1:
      mp->zeros = 0;
      idbuff[0] = 0;
      idbuff[1] = 0;
      c = fcget();
      switch(isascii(c)?sh_lexstates[ST_DOL][c]:S_ALP)
      {
          case S_RBRA:
            if(type<M_SIZE)
                  goto nosub;
            /* This code handles ${#} */
            c = mode;
            mode = type = 0;
            /* FALL THRU */
          case S_SPC1:
            if(type==M_BRACE)
            {
                  if(isaletter(mode=fcpeek(0)) || mode=='.')
                  {
                        if(c=='#')
                              type = M_SIZE;
#ifdef SHOPT_TYPEDEF
                        else if(c=='@')
                        {
                              type = M_TYPE;
                              goto retry1;
                        }
#endif /* SHOPT_TYPEDEF */
                        else
                              type = M_VNAME;
                        mode = c;
                        goto retry1;
                  }
                  else if(c=='#' && (isadigit(mode)||fcpeek(1)==RBRACE))
                  {
                        type = M_SIZE;
                        mode = c;
                        goto retry1;
                  }
            }
            /* FALL THRU */
          case S_SPC2:
            var = 0;
            *id = c;
            v = special(mp->shp,c);
            if(isastchar(c))
            {
                  mode = c;
#if  SHOPT_FILESCAN
                  if(mp->shp->cur_line)
                  {
                        v = getdolarg(&sh,1,(int*)0);
                        dolmax = MAX_ARGN;
                  }
                  else
#endif  /* SHOPT_FILESCAN */
                  dolmax = mp->shp->st.dolc+1;
                  dolg = (v!=0);
            }
            break;
          case S_LBRA:
            if(type)
                  goto nosub;
            type = M_BRACE;
            goto retry1;
          case S_PAR:
            if(type)
                  goto nosub;
            comsubst(mp,(Shnode_t*)0,1);
            return(1);
          case S_DIG:
            var = 0;
            c -= '0';
            mp->shp->argaddr = 0;
            if(type)
            {
                  register int d;
                  while((d=fcget()),isadigit(d))
                        c = 10*c + (d-'0');
                  fcseek(-1);
            }
            idnum = c;
            if(c==0)
                  v = special(mp->shp,c);
#if  SHOPT_FILESCAN
            else if(mp->shp->cur_line)
            {
                  mp->shp->used_pos = 1;
                  v = getdolarg(&sh,c,&vsize);
            }
#endif  /* SHOPT_FILESCAN */
            else if(c <= mp->shp->st.dolc)
            {
                  mp->shp->used_pos = 1;
                  v = mp->shp->st.dolv[c];
            }
            else
                  v = 0;
            break;
          case S_ALP:
            if(c=='.' && type==0)
                  goto nosub;
            offset = stktell(stkp);
            do
            {
                  np = 0;
                  do
                        sfputc(stkp,c);
                  while(((c=fcget()),(!isascii(c)||isaname(c)))||type && c=='.');
                  while(c==LBRACT && (type||mp->arrayok))
                  {
                        mp->shp->argaddr=0;
                        if((c=fcget(),isastchar(c)) && fcpeek(0)==RBRACT)
                        {
                              if(type==M_VNAME)
                                    type = M_SUBNAME;
                              idbuff[0] = mode = c;
                              fcget();
                              c = fcget();
                              if(c=='.' || c==LBRACT)
                              {
                                    sfputc(stkp,LBRACT);
                                    sfputc(stkp,mode);
                                    sfputc(stkp,RBRACT);
                              }
                              else
                                    flag = NV_ARRAY;
                              break;
                        }
                        else
                        {
                              fcseek(-1);
                              c = stktell(stkp);
                              sfputc(stkp,LBRACT);
                              v = stkptr(stkp,subcopy(mp,1));
                              if(type && mp->dotdot)
                              {
                                    mode = '@';
                                    v[-1] = 0;
                                    if(type==M_VNAME)
                                          type = M_SUBNAME;
                                    else if(type==M_SIZE)
                                          goto nosub;
                              }
                              else
                                    sfputc(stkp,RBRACT);
                              c = fcget();
                              if(c==0 && type==M_VNAME)
                                    type = M_SUBNAME;
                        }
                  }
            }
            while(type && c=='.');
            if(c==RBRACE && type &&  fcpeek(-2)=='.')
            {
                  /* ${x.} or ${x..} */
                  if(fcpeek(-3) == '.')
                  {
                        stkseek(stkp,stktell(stkp)-2);
                        nv_local = 1;
                  }
                  else
                  {
                        stkseek(stkp,stktell(stkp)-1);
                        type = M_TREE;
                  }
            }
            sfputc(stkp,0);
            id=stkptr(stkp,offset);
            if(isastchar(c) && type)
            {
                  if(type==M_VNAME || type==M_SIZE)
                  {
                        idbuff[0] = mode = c;
                        if((d=fcpeek(0))==c)
                              idbuff[1] = fcget();
                        if(type==M_VNAME)
                              type = M_NAMESCAN;
                        else
                              type = M_NAMECOUNT;
                        break;
                  }
                  goto nosub;
            }
            flag |= NV_NOASSIGN|NV_VARNAME|NV_NOADD;
            if(c=='=' || c=='?' || (c==':' && ((d=fcpeek(0))=='=' || d=='?')))
            {
                  if(c=='=' || (c==':' && d=='='))
                        flag |= NV_ASSIGN;
                  flag &= ~NV_NOADD;
            }
#if  SHOPT_FILESCAN
            if(mp->shp->cur_line && *id=='R' && strcmp(id,"REPLY")==0)
            {
                  mp->shp->argaddr=0;
                  np = REPLYNOD;
            }
            else
#endif  /* SHOPT_FILESCAN */
            {
                  if(mp->shp->argaddr)
                        flag &= ~NV_NOADD;
                  np = nv_open(id,mp->shp->var_tree,flag|NV_NOFAIL);
            }
            if(isastchar(mode))
                  var = 0;
            if((!np || nv_isnull(np)) && type==M_BRACE && c==RBRACE && !(flag&NV_ARRAY) && strchr(id,'.'))
            {
                  if(sh_macfun(mp->shp,id,offset))
                  {
                        fcget();
                        return(1);
                  }
            }
            if(np && (flag&NV_NOADD) && nv_isnull(np))
            {
                  if(nv_isattr(np,NV_NOFREE))
                        nv_offattr(np,NV_NOFREE);
                  else
                        np = 0;
            }
            ap = np?nv_arrayptr(np):0;
            if(type)
            {
                  if(mp->dotdot)
                  {
                        if(ap)
                        {
                              nv_putsub(np,v,ARRAY_SCAN);
                              v = stkptr(stkp,mp->dotdot);
                              dolmax =1;
                              if(array_assoc(ap))
                                    arrmax = strdup(v);
                              else if((dolmax = (int)sh_arith(v))<0)
                                    dolmax += array_maxindex(np);
                              if(type==M_SUBNAME)
                                    bysub = 1;
                        }
                        else
                        {
                              if((int)sh_arith(v))
                                    np = 0;
                        }
                  }
                  else if(ap && (isastchar(mode)||type==M_TREE)  && !(ap->nelem&ARRAY_SCAN) && type!=M_SIZE)
                        nv_putsub(np,NIL(char*),ARRAY_SCAN);
                  if(!isbracechar(c))
                        goto nosub;
                  else
                        fcseek(-1);
            }
            else
                  fcseek(-1);
            if(type<=1 && np && nv_isvtree(np) && mp->pattern==1 && !mp->split)
            {
                  int peek=1,cc=fcget();
                  if(type && cc=='}')
                  {
                        cc = fcget();
                        peek = 2;
                  }
                  if(mp->quote && cc=='"')
                  {
                        cc = fcget();
                        peek++;
                  }
                  fcseek(-peek);
                  if(cc==0)
                        mp->assign = 1;
            }
            if((type==M_VNAME||type==M_SUBNAME)  && mp->shp->argaddr && strcmp(nv_name(np),id))
                  mp->shp->argaddr = 0;
            c = (type>M_BRACE && isastchar(mode));
            if(np && (type==M_TREE || !c || !ap))
            {
                  char *savptr;
                  c = *((unsigned char*)stkptr(stkp,offset-1));
                  savptr = stkfreeze(stkp,0);
                  if(type==M_VNAME || (type==M_SUBNAME && ap))
                  {
                        type = M_BRACE;
                        v = nv_name(np);
                        if(ap && !mp->dotdot && !(ap->nelem&ARRAY_UNDEF))
                              addsub = 1;
                  }
#ifdef SHOPT_TYPEDEF
                  else if(type==M_TYPE)
                  {
                        Namval_t *nq = nv_type(np);
                        type = M_BRACE;
                        if(nq)
                              nv_typename(nq,mp->shp->strbuf);
                        else
                              nv_attribute(np,mp->shp->strbuf,"typeset",1);
                        v = sfstruse(mp->shp->strbuf);
                  }
#endif /* SHOPT_TYPEDEF */
#if  SHOPT_FILESCAN
                  else if(mp->shp->cur_line && np==REPLYNOD)
                        v = mp->shp->cur_line;
#endif  /* SHOPT_FILESCAN */
                  else if(type==M_TREE)
                        v = nv_getvtree(np,(Namfun_t*)0);
                  else
                  {
                        if(type && fcpeek(0)=='+')
                        {
                              if(ap)
                                    v = nv_arrayisset(np,ap)?(char*)"x":0;
                              else
                                    v = nv_isnull(np)?0:(char*)"x";
                        }
                        else
                              v = nv_getval(np);
                        /* special case --- ignore leading zeros */  
                        if( (mp->arith||mp->let) && (np->nvfun || nv_isattr(np,(NV_LJUST|NV_RJUST|NV_ZFILL))) && !nv_isattr(np,NV_INTEGER) && (offset==0 || !isalnum(c)))
                              mp->zeros = 1;
                  }
                  if(savptr==stakptr(0))
                        stkseek(stkp,offset);
                  else
                        stkset(stkp,savptr,offset);
            }
            else
            {
                  v = 0;
                  if(type==M_VNAME)
                  {
                        v = id;
                        type = M_BRACE;
                  }
                  else if(type==M_TYPE)
                        type = M_BRACE;
            }
            stkseek(stkp,offset);
            if(ap)
            {
#if SHOPT_OPTIMIZE
                  if(mp->shp->argaddr)
                        nv_optimize(np);
#endif
                  if(isastchar(mode) && array_elem(ap)> !c)
                        dolg = -1;
                  else
                        dolg = 0;
            }
            break;
          case S_EOF:
            fcseek(-1);
          default:
            goto nosub;
      }
      c = fcget();
      if(type>M_TREE)
      {
            if(c!=RBRACE)
                  mac_error(np);
            if(type==M_NAMESCAN || type==M_NAMECOUNT)
            {
                  mp->shp->last_root = mp->shp->var_tree;
                  id = prefix(mp->shp,id);
                  stkseek(stkp,offset);
                  if(type==M_NAMECOUNT)
                  {
                        c = namecount(mp,id);
                        v = ltos(c);
                  }
                  else
                  {
                        dolmax = strlen(id);
                        dolg = -1;
                        nextname(mp,id,0);
                        v = nextname(mp,id,dolmax);
                  }
            }
            else if(type==M_SUBNAME)
            {
                  if(dolg<0)
                  {
                        v = nv_getsub(np);
                        bysub=1;
                  }
                  else if(v)
                  {
                        if(!ap || isastchar(mode))
                              v = "0";
                        else
                              v = nv_getsub(np);
                  }
            }
            else
            {
                  if(!isastchar(mode))
                        c = charlen(v,vsize);
                  else if(dolg>0)
                  {
#if  SHOPT_FILESCAN
                        if(mp->shp->cur_line)
                        {
                              getdolarg(&sh,MAX_ARGN,(int*)0);
                              c = mp->shp->offsets[0];
                        }
                        else
#endif  /* SHOPT_FILESCAN */
                        c = mp->shp->st.dolc;
                  }
                  else if(dolg<0)
                        c = array_elem(ap);
                  else
                        c = (v!=0);
                  dolg = dolmax = 0;
                  v = ltos(c);
            }
            c = RBRACE;
      }
      nulflg = 0;
      if(type && c==':')
      {
            c = fcget();
            if(sh_lexstates[ST_BRACE][c]==S_MOD1 && c!='*' && c!= ':')
                  nulflg=1;
            else if(c!='%' && c!='#')
            {
                  fcseek(-1);
                  c = ':';
            }
      }
      if(type)
      {
            if(!isbracechar(c))
            {
                  if(!nulflg)
                        mac_error(np);
                  fcseek(-1);
                  c = ':';
            }
            if(c!=RBRACE)
            {
                  int newops = (c=='#' || c == '%' || c=='/');
                  offset = stktell(stkp);
                  if(c=='/' ||c==':' || ((!v || (nulflg && *v==0)) ^ (c=='+'||c=='#'||c=='%')))
                  {
                        int newquote = mp->quote;
                        int split = mp->split;
                        int quoted = mp->quoted;
                        int arith = mp->arith;
                        int zeros = mp->zeros;
                        int assign = mp->assign;
                        if(newops)
                        {
                              type = fcget();
                              if(type=='%' || type=='#')
                              {
                                    int d = fcget();
                                    fcseek(-1);
                                    if(d=='(')
                                          type = 0;
                              }
                              fcseek(-1);
                              mp->pattern = 1+(c=='/');
                              mp->split = 0;
                              mp->quoted = 0;
                              mp->assign &= ~1;
                              mp->arith = mp->zeros = 0;
                              newquote = 0;
                        }
                        else if(c=='?' || c=='=')
                              mp->split = mp->pattern = 0;
                        copyto(mp,RBRACE,newquote);
                        if(!oldpat)
                              mp->patfound = 0;
                        mp->pattern = oldpat;
                        mp->split = split;
                        mp->quoted = quoted;
                        mp->arith = arith;
                        mp->zeros = zeros;
                        mp->assign = assign;
                        /* add null byte */
                        sfputc(stkp,0);
                        stkseek(stkp,stktell(stkp)-1);
                  }
                  else
                  {
                        sh_lexskip(lp,RBRACE,0,(!newops&&mp->quote)?ST_QUOTE:ST_NESTED);
                        stkseek(stkp,offset);
                  }
                  argp=stkptr(stkp,offset);
            }
      }
      else
      {
            fcseek(-1);
            c=0;
      }
      if(c==':')  /* ${name:expr1[:expr2]} */
      {
            char *ptr;
            type = (int)sh_strnum(argp,&ptr,1);
            if(isastchar(mode))
            {
                  if(id==idbuff)  /* ${@} or ${*} */
                  {
                        if(type<0 && (type+= dolmax)<0)
                              type = 0;
                        if(type==0)
                              v = special(mp->shp,dolg=0);
#if  SHOPT_FILESCAN
                        else if(mp->shp->cur_line)
                        {
                              v = getdolarg(&sh,dolg=type,&vsize);
                              if(!v)
                                    dolmax = type;
                        }
#endif  /* SHOPT_FILESCAN */
                        else if(type < dolmax)
                              v = mp->shp->st.dolv[dolg=type];
                        else
                              v =  0;
                  }
                  else if(ap)
                  {
                        if(type<0)
                        {
                              if(array_assoc(ap))
                                    type = -type;
                              else
                                    type += array_maxindex(np);
                        }
                        if(array_assoc(ap))
                        {
                              while(type-- >0 && (v=0,nv_nextsub(np)))
                                    v = nv_getval(np);
                        }
                        else if(type > 0)
                        {
                              if(nv_putsub(np,NIL(char*),type|ARRAY_SCAN))
                                    v = nv_getval(np);
                              else
                                    v = 0;
                        }
                  }
                  else if(type>0)
                        v = 0;
            }
            else if(v)
            {
                  vsize = charlen(v,vsize);
                  if(type<0 && (type += vsize)<0)
                        type = 0;
                  if(vsize < type)
                        v = 0;
#if SHOPT_MULTIBYTE
                  else if(mbwide())
                  {
                        mbinit();
                        while(type-->0)
                        {
                              if((c=mbsize(v))<1)
                                    c = 1;
                              v += c;
                        }
                        c = ':';
                  }
#endif /* SHOPT_MULTIBYTE */
                  else
                        v += type;
                  vsize -= type;
            }
            if(*ptr==':')
            {
                  if((type = (int)sh_strnum(ptr+1,&ptr,1)) <=0)
                        v = 0;
                  else if(isastchar(mode))
                  {
                        if(dolg>=0)
                        {
                              if(dolg+type < dolmax)
                                    dolmax = dolg+type;
                        }
                        else
                              dolmax = type;
                  }
                  else if(type < vsize)
                  {
#if SHOPT_MULTIBYTE
                        if(mbwide())
                        {
                              char *vp = v;
                              mbinit();
                              while(type-->0)
                              {
                                    if((c=mbsize(vp))<1)
                                          c = 1;
                                    vp += c;
                              }
                              type = vp-v;
                              c = ':';
                        }
#endif /* SHOPT_MULTIBYTE */
                        vsize = type;
                  }
            }
            if(*ptr)
                  mac_error(np);
            stkseek(stkp,offset);
            argp = 0;
      }
      /* check for substring operations */
      else if(c == '#' || c == '%' || c=='/')
      {
            if(c=='/')
            {
                  if(type=='/' || type=='#' || type=='%')
                  {
                        c = type;
                        type = '/';
                        argp++;
                  }
                  else
                        type = 0;
            }
            else
            {
                  if(type==c) /* ## or %% */
                        argp++;
                  else
                        type = 0;
            }
            pattern = strdup(argp);
            if((type=='/' || c=='/') && (repstr = mac_getstring(pattern)))
                  replen = strlen(repstr);
            if(v || c=='/' && offset>=0)
                  stkseek(stkp,offset);
      }
      /* check for quoted @ */
      if(mode=='@' && mp->quote && !v && c!='-')
            mp->quoted-=2;
retry2:
      if(v && (!nulflg || *v ) && c!='+')
      {
            register int d = (mode=='@'?' ':mp->ifs);
            int match[2*(MATCH_MAX+1)], nmatch, nmatch_prev, vsize_last;
            char *vlast;
            while(1)
            {
                  if(!v)
                        v= "";
                  if(c=='/' || c=='#' || c== '%')
                  {
                        flag = (type || c=='/')?(STR_GROUP|STR_MAXIMAL):STR_GROUP;
                        if(c!='/')
                              flag |= STR_LEFT;
                        nmatch = 0;
                        while(1)
                        {
                              vsize = strlen(v);
                              nmatch_prev = nmatch;
                              if(c=='%')
                                    nmatch=substring(v,pattern,match,flag&STR_MAXIMAL);
                              else
                                    nmatch=strgrpmatch(v,pattern,match,elementsof(match)/2,flag);
                              if(replen>0)
                                    sh_setmatch(v,vsize,nmatch,match);
                              if(nmatch)
                              {
                                    vlast = v;
                                    vsize_last = vsize;
                                    vsize = match[0];
                              }
                              else if(c=='#')
                                    vsize = 0;
                              if(vsize)
                                    mac_copy(mp,v,vsize);
                              if(nmatch && replen>0 && (match[1] || !nmatch_prev))
                                    mac_substitute(mp,repstr,v,match,nmatch);
                              if(nmatch==0)
                                    v += vsize;
                              else
                                    v += match[1];
                              if(*v &&  c=='/' && type)
                              {
                                    /* avoid infinite loop */
                                    if(nmatch && match[1]==0)
                                    {
                                          nmatch = 0;
                                          mac_copy(mp,v,1);
                                          v++;
                                    }
                                    continue;
                              }
                              vsize = -1;
                              break;
                        }
                        if(replen==0)
                              sh_setmatch(vlast,vsize_last,nmatch,match);
                  }
                  if(vsize)
                        mac_copy(mp,v,vsize>0?vsize:strlen(v));
                  if(addsub)
                  {
                        sfprintf(mp->shp->strbuf,"[%s]",nv_getsub(np));
                        v = sfstruse(mp->shp->strbuf);
                        mac_copy(mp, v, strlen(v));
                  }
                  if(dolg==0 && dolmax==0)
                         break;
                  if(mp->dotdot)
                  {
                        if(nv_nextsub(np) == 0)
                              break;
                        if(bysub)
                              v = nv_getsub(np);
                        else
                              v = nv_getval(np);
                        if(array_assoc(ap))
                        {
                              if(strcmp(bysub?v:nv_getsub(np),arrmax)>0)
                                    break;
                        }
                        else
                        {
                              if(nv_aindex(np) > dolmax)
                                    break;
                        }
                  }
                  else if(dolg>=0)
                  {
                        if(++dolg >= dolmax)
                              break;
#if  SHOPT_FILESCAN
                        if(mp->shp->cur_line)
                        {
                              if(dolmax==MAX_ARGN && isastchar(mode))
                                    break;
                              if(!(v=getdolarg(&sh,dolg,&vsize)))
                              {
                                    dolmax = dolg;
                                    break;
                              }
                        }
                        else
#endif  /* SHOPT_FILESCAN */
                        v = mp->shp->st.dolv[dolg];
                  }
                  else if(!np)
                  {
                        if(!(v = nextname(mp,id,dolmax)))
                              break;
                  }
                  else
                  {
                        if(dolmax &&  --dolmax <=0)
                        {
                              nv_putsub(np,NIL(char*),ARRAY_UNDEF);
                              break;
                        }
                        if(ap)
                              ap->nelem |= ARRAY_SCAN;
                        if(nv_nextsub(np) == 0)
                              break;
                        if(bysub)
                              v = nv_getsub(np);
                        else
                              v = nv_getval(np);
                  }
                  if(mp->split && (!mp->quote || mode=='@'))
                  {
                        if(!np)
                              mp->pattern = 0;
                        endfield(mp,mp->quoted);
                        mp->pattern = oldpat;
                  }
                  else if(d)
                  {
                        if(mp->sp)
                              sfputc(mp->sp,d);
                        else
                              sfputc(stkp,d);
                  }
            }
            if(arrmax)
                  free((void*)arrmax);
            if(pattern)
                  free((void*)pattern);
      }
      else if(argp)
      {
            if(c=='/' && replen>0 && pattern && strmatch("",pattern))
                  mac_substitute(mp,repstr,v,0,0);
            if(c=='?')
            {
                  if(np)
                        id = nv_name(np);
                  else if(idnum)
                        id = ltos(idnum);
                  if(*argp)
                  {
                        sfputc(stkp,0);
                        errormsg(SH_DICT,ERROR_exit(1),"%s: %s",id,argp);
                  }
                  else if(v)
                        errormsg(SH_DICT,ERROR_exit(1),e_nullset,id);
                  else
                        errormsg(SH_DICT,ERROR_exit(1),e_notset,id);
            }
            else if(c=='=')
            {
                  if(np)
                  {
                        if(mp->shp->subshell)
                              np = sh_assignok(np,1);
                        nv_putval(np,argp,0);
                        v = nv_getval(np);
                        nulflg = 0;
                        stkseek(stkp,offset);
                        goto retry2;
                  }
            else
                  mac_error(np);
            }
      }
      else if(var && sh_isoption(SH_NOUNSET) && type<=M_TREE && (!np  || nv_isnull(np) || (nv_isarray(np) && !np->nvalue.cp)))
      {
            if(np)
            {
                  if(nv_isarray(np))
                  {
                        sfprintf(mp->shp->strbuf,"%s[%s]\0",nv_name(np),nv_getsub(np));
                        id = sfstruse(mp->shp->strbuf);
                  }
                  else
                        id = nv_name(np);
                  nv_close(np);
            }
            errormsg(SH_DICT,ERROR_exit(1),e_notset,id);
      }
      if(np)
            nv_close(np);
      return(1);
nosub:
      if(type==M_BRACE && sh_lexstates[ST_NORM][c]==S_BREAK)
      {
            fcseek(-1);
            comsubst(mp,(Shnode_t*)0,2);
            return(1);
      }
      if(type)
            mac_error(np);
      fcseek(-1);
      nv_close(np);
      return(0);
}

/*
 * This routine handles command substitution
 * <type> is 0 for older `...` version
 */
static void comsubst(Mac_t *mp,register Shnode_t* t, int type)
{
      Sfdouble_t        num;
      register int            c;
      register char           *str;
      Sfio_t                  *sp;
      Stk_t             *stkp = mp->shp->stk;
      Fcin_t                  save;
      struct slnod            *saveslp = mp->shp->st.staklist;
      struct _mac_            savemac;
      int               savtop = stktell(stkp);
      char              lastc, *savptr = stkfreeze(stkp,0);
      int               was_history = sh_isstate(SH_HISTORY);
      int               was_verbose = sh_isstate(SH_VERBOSE);
      int               was_interactive = sh_isstate(SH_INTERACTIVE);
      int               newlines,bufsize,nextnewlines;
      Namval_t          *np;
      mp->shp->argaddr = 0;
      savemac = *mp;
      mp->shp->st.staklist=0;
      if(type)
      {
            sp = 0;
            fcseek(-1);
            if(!t)
                  t = sh_dolparen((Lex_t*)mp->shp->lex_context);
            if(t && t->tre.tretyp==TARITH)
            {
                  fcsave(&save);
                  if((t->ar.arexpr->argflag&ARG_RAW))
                        num = arith_exec(t->ar.arcomp);
                  else
                        num = sh_arith(sh_mactrim(mp->shp,t->ar.arexpr->argval,3));
            out_offset:
                  stkset(stkp,savptr,savtop);
                  *mp = savemac;
                  if((Sflong_t)num!=num)
                        sfprintf(mp->shp->strbuf,"%.*Lg",LDBL_DIG,num);
                  else if(num)
                        sfprintf(mp->shp->strbuf,"%lld",(Sflong_t)num);
                  else
                        sfprintf(mp->shp->strbuf,"%Lg",num);
                  str = sfstruse(mp->shp->strbuf);
                  mac_copy(mp,str,strlen(str));
                  mp->shp->st.staklist = saveslp;
                  fcrestore(&save);
                  return;
            }
      }
      else
      {
            while(fcgetc(c)!='`' && c)
            {
                  if(c==ESCAPE)
                  {
                        fcgetc(c);
                        if(!(isescchar(sh_lexstates[ST_QUOTE][c]) ||
                          (c=='"' && mp->quote)))
                              sfputc(stkp,ESCAPE);
                  }
                  sfputc(stkp,c);
            }
            c = stktell(stkp);
            str=stkfreeze(stkp,1);
            /* disable verbose and don't save in history file */
            sh_offstate(SH_HISTORY);
            sh_offstate(SH_VERBOSE);
            if(mp->sp)
                  sfsync(mp->sp);   /* flush before executing command */
            sp = sfnew(NIL(Sfio_t*),str,c,-1,SF_STRING|SF_READ);
            c = mp->shp->inlineno;
            mp->shp->inlineno = error_info.line+mp->shp->st.firstline;
            t = (Shnode_t*)sh_parse(mp->shp, sp,SH_EOF|SH_NL);
            mp->shp->inlineno = c;
            type = 1;
      }
#if KSHELL
      if(t)
      {
            fcsave(&save);
            sfclose(sp);
            if(t->tre.tretyp==0 && !t->com.comarg && !t->com.comset)
            {
                  /* special case $(<file) and $(<#file) */
                  register int fd;
                  int r;
                  struct checkpt buff;
                  struct ionod *ip=0;
                  sh_pushcontext(&buff,SH_JMPIO);
                  if((ip=t->tre.treio) && 
                        ((ip->iofile&IOLSEEK) || !(ip->iofile&IOUFD)) &&
                        (r=sigsetjmp(buff.buff,0))==0)
                        fd = sh_redirect(mp->shp,ip,3);
                  else
                        fd = sh_chkopen(e_devnull);
                  sh_popcontext(&buff);
                  if(r==0 && ip && (ip->iofile&IOLSEEK))
                  {
                        if(sp=mp->shp->sftable[fd])
                              num = sftell(sp);
                        else
                              num = lseek(fd, (off_t)0, SEEK_CUR);
                        goto out_offset;
                  }
                  sp = sfnew(NIL(Sfio_t*),(char*)malloc(IOBSIZE+1),IOBSIZE,fd,SF_READ|SF_MALLOC);
                  type = 3;
            }
            else
                  sp = sh_subshell(t,sh_isstate(SH_ERREXIT),type);
            fcrestore(&save);
      }
      else
            sp = sfopen(NIL(Sfio_t*),"","sr");
      sh_freeup(mp->shp);
      mp->shp->st.staklist = saveslp;
      if(was_history)
            sh_onstate(SH_HISTORY);
      if(was_verbose)
            sh_onstate(SH_VERBOSE);
#else
      sp = sfpopen(NIL(Sfio_t*),str,"r");
#endif
      *mp = savemac;
      np = sh_scoped(mp->shp,IFSNOD);
      nv_putval(np,mp->ifsp,NV_RDONLY);
      mp->ifsp = nv_getval(np);
      stkset(stkp,savptr,savtop);
      newlines = 0;
      lastc = 0;
      sfsetbuf(sp,(void*)sp,0);
      bufsize = sfvalue(sp);
      /* read command substitution output and put on stack or here-doc */
      sfpool(sp, NIL(Sfio_t*), SF_WRITE);
      sh_offstate(SH_INTERACTIVE);
      while((str=(char*)sfreserve(sp,SF_UNBOUND,0)) && (c=bufsize=sfvalue(sp))>0)
      {
#if SHOPT_CRNL
            /* eliminate <cr> */
            register char *dp;
            char *buff = str;
            while(c>1 && (*str !='\r'|| str[1]!='\n'))
            {
                  c--;
                  str++;
            }
            dp = str;
            while(c>1)
            {
                  str++;
                  c--;
                  while(c>1 && (*str!='\r' || str[1]!='\n'))
                  {
                        c--;
                        *dp++ = *str++;
                  }
            }
            if(c)
                  *dp++ = *str++;
            str = buff;
            c = dp-str;
#endif /* SHOPT_CRNL */
            /* delay appending trailing new-lines */
            for(nextnewlines=0; c-->0 && str[c]=='\n'; nextnewlines++);
            if(c < 0)
            {
                  newlines += nextnewlines;
                  continue;
            }
            if(newlines >0)
            {
                  if(mp->sp)
                        sfnputc(mp->sp,'\n',newlines);
                  else if(!mp->quote && mp->split && mp->shp->ifstable['\n'])
                        endfield(mp,0);
                  else
                        sfnputc(stkp,'\n',newlines);
            }
            else if(lastc)
            {
                  mac_copy(mp,&lastc,1);
                  lastc = 0;
            }
            newlines = nextnewlines;
            if(++c < bufsize)
                  str[c] = 0;
            else
            {
                  /* can't write past buffer so save last character */
                  lastc = str[--c];
                  str[c] = 0;
            }
            mac_copy(mp,str,c);
      }
      if(was_interactive)
            sh_onstate(SH_INTERACTIVE);
      if(--newlines>0 && mp->shp->ifstable['\n']==S_DELIM)
      {
            if(mp->sp)
                  sfnputc(mp->sp,'\n',newlines);
            else if(!mp->quote && mp->split)
                  while(newlines--)
                        endfield(mp,1);
            else
                  sfnputc(stkp,'\n',newlines);
      }
      if(lastc)
            mac_copy(mp,&lastc,1);
      sfclose(sp);
      return;
}

/*
 * copy <str> onto the stack
 */
static void mac_copy(register Mac_t *mp,register const char *str, register int size)
{
      register char           *state;
      register const char     *cp=str;
      register int            c,n,nopat,len;
      Stk_t             *stkp=mp->shp->stk;
      nopat = (mp->quote||mp->assign==1||mp->arith);
      if(mp->zeros)
      {
            /* prevent leading 0's from becomming octal constants */
            while(size>1 && *str=='0')
                  str++,size--;
            mp->zeros = 0;
            cp = str;
      }
      if(mp->sp)
            sfwrite(mp->sp,str,size);
      else if(mp->pattern>=2 || (mp->pattern && nopat))
      {
            state = sh_lexstates[ST_MACRO];
            /* insert \ before file expansion characters */
            while(size-->0)
            {
#if SHOPT_MULTIBYTE
                  if(mbwide() && (len=mbsize(cp))>1)
                  {
                        cp += len;
                        size -= (len-1);
                        continue;
                  }
#endif
                  c = state[n= *(unsigned char*)cp++];
                  if(nopat&&(c==S_PAT||c==S_ESC||c==S_BRACT||c==S_ENDCH) && mp->pattern!=3)
                        c=1;
                  else if(mp->pattern==4 && (c==S_ESC||c==S_BRACT||c==S_ENDCH || isastchar(n)))
                        c=1;
                  else if(mp->pattern==2 && c==S_SLASH)
                        c=1;
                  else if(mp->pattern==3 && c==S_ESC && (state[*(unsigned char*)cp]==S_DIG||(*cp==ESCAPE)))
                  {
                        if(!(c=mp->quote))
                              cp++;
                  }
                  else
                        c=0;
                  if(c)
                  {
                        if(c = (cp-1) - str)
                              sfwrite(stkp,str,c);
                        sfputc(stkp,ESCAPE);
                        str = cp-1;
                  }
            }
            if(c = cp-str)
                  sfwrite(stkp,str,c);
      }
      else if(!mp->quote && mp->split && (mp->ifs||mp->pattern))
      {
            /* split words at ifs characters */
            state = mp->shp->ifstable;
            if(mp->pattern)
            {
                  char *sp = "&|()";
                  while(c = *sp++)
                  {
                        if(state[c]==0)
                              state[c] = S_EPAT;
                  }
                  sp = "*?[{";
                  while(c = *sp++)
                  {
                        if(state[c]==0)
                              state[c] = S_PAT;
                  }
                  if(state[ESCAPE]==0)
                        state[ESCAPE] = S_ESC;
            }
            while(size-->0)
            {
                  n=state[c= *(unsigned char*)cp++];
#if SHOPT_MULTIBYTE
                  if(mbwide() && n!=S_MBYTE && (len=mbsize(cp-1))>1)
                  {
                        sfwrite(stkp,cp-1, len);
                        cp += --len;
                        size -= len;
                        continue;
                  }
#endif
                  if(n==S_ESC || n==S_EPAT)
                  {
                        /* don't allow extended patterns in this case */
                        mp->patfound = mp->pattern;
                        sfputc(stkp,ESCAPE);
                  }
                  else if(n==S_PAT)
                        mp->patfound = mp->pattern;
                  else if(n && mp->ifs)
                  {
#if SHOPT_MULTIBYTE
                        if(n==S_MBYTE)
                        {
                              if(sh_strchr(mp->ifsp,cp-1)<0)
                                    continue;
                              n = mbsize(cp-1) - 1;
                              if(n==-2)
                                    n = 0;
                              cp += n;
                              size -= n;
                              n= S_DELIM;
                        }
#endif /* SHOPT_MULTIBYTE */
                        if(n==S_SPACE || n==S_NL)
                        {
                              while(size>0 && ((n=state[c= *(unsigned char*)cp++])==S_SPACE||n==S_NL))
                                    size--;
#if SHOPT_MULTIBYTE
                              if(n==S_MBYTE && sh_strchr(mp->ifsp,cp-1)>=0)
                              {
                                    n = mbsize(cp-1) - 1;
                                    if(n==-2)
                                          n = 0;
                                    cp += n;
                                    size -= n;
                                    n=S_DELIM;
                              }
                              else
#endif /* SHOPT_MULTIBYTE */
                              if(n==S_DELIM)
                                    size--;
                        }
                        endfield(mp,n==S_DELIM||mp->quoted);
                        mp->patfound = 0;
                        if(n==S_DELIM)
                              while(size>0 && ((n=state[c= *(unsigned char*)cp++])==S_SPACE||n==S_NL))
                                    size--;
                        if(size<=0)
                              break;
                        cp--;
                        continue;

                  }
                  sfputc(stkp,c);
            }
            if(mp->pattern)
            {
                  cp = "&|()";
                  while(c = *cp++)
                  {
                        if(state[c]==S_EPAT)
                              state[c] = 0;
                  }
                  cp = "*?[{";
                  while(c = *cp++)
                  {
                        if(state[c]==S_PAT)
                              state[c] = 0;
                  }
                  if(mp->shp->ifstable[ESCAPE]==S_ESC)
                        mp->shp->ifstable[ESCAPE] = 0;
            }
      }
      else
            sfwrite(stkp,str,size);
}

/*
 * Terminate field.
 * If field is null count field if <split> is non-zero
 * Do filename expansion of required
 */
static void endfield(register Mac_t *mp,int split)
{
      register struct argnod  *argp;
      register int            count=0;
      Stk_t             *stkp = mp->shp->stk;
      if(stktell(stkp) > ARGVAL || split)
      {
            argp = (struct argnod*)stkfreeze(stkp,1);
            argp->argnxt.cp = 0;
            argp->argflag = 0;
            if(mp->patfound)
            {
                  mp->shp->argaddr = 0;
#if SHOPT_BRACEPAT
                  count = path_generate(argp,mp->arghead);
#else
                  count = path_expand(argp->argval,mp->arghead);
#endif /* SHOPT_BRACEPAT */
                  if(count)
                        mp->fields += count;
                  else if(split)    /* pattern is null string */
                        *argp->argval = 0;
                  else  /* pattern expands to nothing */
                        count = -1;
            }
            if(count==0)
            {
                  argp->argchn.ap = *mp->arghead;
                  *mp->arghead = argp;
                  mp->fields++;
            }
            if(count>=0)
            {
                  (*mp->arghead)->argflag |= ARG_MAKE;
                  if(mp->assign || sh_isoption(SH_NOGLOB))
                        argp->argflag |= ARG_RAW|ARG_EXP;
            }
            stkseek(stkp,ARGVAL);
      }
      mp->quoted = mp->quote;
}

/*
 * Finds the right substring of STRING using the expression PAT
 * the longest substring is found when FLAG is set.
 */
static int substring(register const char *string,const char *pat,int match[], int flag)
{
      register const char *sp=string;
      register int size,len,nmatch,n;
      int smatch[2*(MATCH_MAX+1)];
      if(flag)
      {
            if(n=strgrpmatch(sp,pat,smatch,elementsof(smatch)/2,STR_RIGHT|STR_MAXIMAL))
            {
                  memcpy(match,smatch,n*2*sizeof(smatch[0]));
                  return(n);
            }
            return(0);
      }
      size = len = strlen(sp);
      sp += size;
      while(sp>=string)
      {
#if SHOPT_MULTIBYTE
            if(mbwide())
                  sp = lastchar(string,sp);
#endif /* SHOPT_MULTIBYTE */
            if(n=strgrpmatch(sp,pat,smatch,elementsof(smatch)/2,STR_RIGHT|STR_LEFT|STR_MAXIMAL))
            {
                  nmatch = n;
                  memcpy(match,smatch,n*2*sizeof(smatch[0]));
                  size = sp-string;
                  break;
            }
            sp--;
      }
      if(size==len)
            return(0);
      if(nmatch)
      {
            nmatch *=2;
            while(--nmatch>=0)
                  match[nmatch] += size;
      }
      return(n);
}

#if SHOPT_MULTIBYTE
      static char *lastchar(const char *string, const char *endstring)
      {
            register char *str = (char*)string;
            register int c;
            mbinit();
            while(*str)
            {
                  if((c=mbsize(str))<0)
                        c = 1;
                  if(str+c > endstring)
                        break;
                  str += c;
            }
            return(str);
      }
#endif /* SHOPT_MULTIBYTE */
static int  charlen(const char *string,int len)
{
      if(!string)
            return(0);
#if SHOPT_MULTIBYTE
      if(mbwide())
      {
            register const char *str = string, *strmax=string+len;
            register int n=0;
            mbinit();
            if(len>0)
            {
                  while(str<strmax && mbchar(str))
                        n++;
            }
            else while(mbchar(str))
                  n++;
            return(n);
      }
      else
#endif /* SHOPT_MULTIBYTE */
      {
            if(len<0)
                  return(strlen(string));
            return(len);
      }
}

/*
 * This is the default tilde discipline function
 */
static int sh_btilde(int argc, char *argv[], void *context)
{
      Shell_t *shp = ((Shbltin_t*)context)->shp;
      char *cp = sh_tilde(shp,argv[1]);
      NOT_USED(argc);
      if(!cp)
            cp = argv[1];
      sfputr(sfstdout, cp, '\n');
      return(0);
}
 
/*
 * <offset> is byte offset for beginning of tilde string
 */
static void tilde_expand2(Shell_t *shp, register int offset)
{
      char        shtilde[10], *av[3], *ptr=stkfreeze(shp->stk,1);
      Sfio_t            *iop, *save=sfstdout;
      Namval_t    *np;
      static int  beenhere=0;
      strcpy(shtilde,".sh.tilde");
      np = nv_open(shtilde,shp->fun_tree, NV_VARNAME|NV_NOARRAY|NV_NOASSIGN|NV_NOFAIL);
      if(np && !beenhere)
      {
            beenhere = 1;
            sh_addbuiltin(shtilde,sh_btilde,0);
            nv_onattr(np,NV_EXPORT);
      }
      av[0] = ".sh.tilde";
      av[1] = &ptr[offset];
      av[2] = 0;
      iop = sftmp(IOBSIZE+1);;
      sfset(iop,SF_READ,0);
      sfstdout = iop;
      if(np)
            sh_fun(np, (Namval_t*)0, av);
      else
            sh_btilde(2, av, &shp->bltindata);
      sfstdout = save;
      stkset(shp->stk,ptr, offset);
      sfseek(iop,(Sfoff_t)0,SEEK_SET);
      sfset(iop,SF_READ,1);
      if(ptr = sfreserve(iop, SF_UNBOUND, -1))
      {
            Sfoff_t n = sfvalue(iop);
            while(ptr[n-1]=='\n')
                  n--;
            if(n==1 && fcpeek(0)=='/' && ptr[n-1])
                  n--;
            if(n)
                  sfwrite(shp->stk,ptr,n);
      }
      else
            sfputr(shp->stk,av[1],0);
      sfclose(iop);
}

/*
 * This routine is used to resolve ~ expansion.
 * A ~ by itself is replaced with the users login directory.
 * A ~- is replaced by the previous working directory in shell.
 * A ~+ is replaced by the present working directory in shell.
 * If ~name  is replaced with login directory of name.
 * If string doesn't start with ~ or ~... not found then 0 returned.
 */
                                                            
static char *sh_tilde(Shell_t *shp,register const char *string)
{
      register char           *cp;
      register int            c;
      register struct passwd  *pw;
      register Namval_t *np=0;
      static Dt_t *logins_tree;
      if(*string++!='~')
            return(NIL(char*));
      if((c = *string)==0)
      {
            if(!(cp=nv_getval(sh_scoped(shp,HOME))))
                  cp = getlogin();
            return(cp);
      }
      if((c=='-' || c=='+') && string[1]==0)
      {
            if(c=='+')
                  cp = nv_getval(sh_scoped(shp,PWDNOD));
            else
                  cp = nv_getval(sh_scoped(shp,OLDPWDNOD));
            return(cp);
      }
      if(logins_tree && (np=nv_search(string,logins_tree,0)))
            return(nv_getval(np));
      if(!(pw = getpwnam(string)))
            return(NIL(char*));
      if(!logins_tree)
            logins_tree = dtopen(&_Nvdisc,Dtbag);
      if(np=nv_search(string,logins_tree,NV_ADD))
            nv_putval(np, pw->pw_dir,0);
      return(pw->pw_dir);
}

/*
 * return values for special macros
 */
static char *special(Shell_t *shp,register int c)
{
      if(c!='$')
            shp->argaddr = 0;
      switch(c)
      {
          case '@':
          case '*':
            return(shp->st.dolc>0?shp->st.dolv[1]:NIL(char*));
          case '#':
#if  SHOPT_FILESCAN
            if(shp->cur_line)
            {
                  getdolarg(shp,MAX_ARGN,(int*)0);
                  return(ltos(shp->offsets[0]));
            }
#endif  /* SHOPT_FILESCAN */
            return(ltos(shp->st.dolc));
          case '!':
            if(shp->bckpid)
                  return(ltos(shp->bckpid));
            break;
          case '$':
            if(nv_isnull(SH_DOLLARNOD))
                  return(ltos(shp->pid));
            return(nv_getval(SH_DOLLARNOD));
          case '-':
            return(sh_argdolminus(shp->arg_context));
          case '?':
            return(ltos(shp->savexit));
          case 0:
            if(sh_isstate(SH_PROFILE) || shp->fn_depth==0 || !shp->st.cmdname)
                  return(shp->shname);
            else
                  return(shp->st.cmdname);
      }
      return(NIL(char*));
}

/*
 * Handle macro expansion errors
 */
static void mac_error(Namval_t *np)
{
      if(np)
            nv_close(np);
      errormsg(SH_DICT,ERROR_exit(1),e_subst,fcfirst());
}

/*
 * Given pattern/string, replace / with 0 and return pointer to string
 * \ characters are stripped from string.  The \ are stripped in the
 * replacement string unless followed by a digit or \.
 */ 
static char *mac_getstring(char *pattern)
{
      register char     *cp=pattern, *rep=0, *dp;
      register int      c;
      while(c = *cp++)
      {
            if(c==ESCAPE && (!rep || (*cp && strchr("&|()[]*?",*cp))))
            {
                  c = *cp++;
            }
            else if(!rep && c=='/')
            {
                  cp[-1] = 0;
                  rep = dp = cp;
                  continue;
            }
            if(rep)
                  *dp++ = c;
      }
      if(rep)
            *dp = 0;
      return(rep);
}

Generated by  Doxygen 1.6.0   Back to index