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

parse.c

/***********************************************************************
*                                                                      *
*               This software is part of the ast package               *
*          Copyright (c) 1984-2007 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                            *
*                                                                      *
*                 Glenn Fowler <gsf@research.att.com>                  *
*                                                                      *
***********************************************************************/
#pragma prototyped
/*
 * Glenn Fowler
 * AT&T Research
 *
 * makefile lexical scanner and parser
 *
 * interleaved recursive parse() and expand() nesting supported
 *
 * NOTE: readline() from file does double copy to pp->ip and sp
 *     it should be possible to go directly to sp
 */

#include "make.h"
#include "options.h"

#include <sig.h>

#define PARSEDEPTH      64          /* maximum parse stack depth  */

#define PS1       "make> "    /* primary query prompt       */
#define PS2       ">>>>> "    /* secondary query prompt     */

#define OP_ASSERT (1<<0)            /* assertion statement        */
#define OP_ASSIGN (1<<1)            /* assignment statement       */
#define OP_EMPTY  (1<<2)            /* no operator found          */
#define OP_STATEMENT    ((1<<3)-1)  /* statement type mask        */

#define OP_ACTION (1<<3)            /* operator takes action      */
#define OP_APPEND (1<<4)            /* append assignment          */
#define OP_AUXILIARY    (1<<5)            /* auxiliary assignment       */
#define OP_EXPAND (1<<6)            /* expand lhs and rhs         */
#define OP_STATE  (1<<7)            /* state assignment           */

#define CON_kept  (1<<0)            /* already kept part of block */
#define CON_skip  (1<<1)            /* skip this part of block    */
#define CON_stash (1<<2)            /* file loop body lines stashed     */

#define CON_if          (1<<3)            /* if block             */
#define CON_elif  (1<<4)            /* not used in flags          */
#define CON_else  (1<<5)            /* had else             */
#define CON_end         (1<<6)            /* not used in flags          */
#define CON_for         (1<<7)            /* for loop block       */
#define CON_while (1<<8)            /* while loop block           */
#define CON_eval  (1<<9)            /* eval block                 */

#define CON_break (1<<10)           /* loop break                 */
#define CON_continue    (2<<10)           /* loop continue        */
#define CON_error (3<<10)           /* error message output       */
#define CON_exit  (4<<10)           /* exit                       */
#define CON_let         (5<<10)           /* let expression       */
#define CON_local (6<<10)           /* local var declaration      */
#define CON_print (7<<10)           /* standard output message    */
#define CON_read  (8<<10)           /* read                       */
#define CON_return      (9<<10)           /* return               */
#define CON_rules (10<<10)    /* rules                */
#define CON_set         (11<<10)    /* set options                */

#define PUSHLOCAL(p)    do{for(p=pp->local;p;p=p->next)if(!(p->newv.property&V_scope))p->bucket->value=(char*)p->oldv;}while(0)
#define POPLOCAL(p)     do{for(p=pp->local;p;p=p->next)if(!(p->newv.property&V_scope))p->bucket->value=(char*)&p->newv;}while(0)

#define freelocal(x)    do{x->next=freelocals;freelocals=x;}while(0)
#define newlocal(x)     do{if(x=freelocals)freelocals=x->next;else x=newof(0,Local_t,1,0);}while(0)

struct Local_s; typedef struct Local_s Local_t;

struct Local_s                      /* local variable state       */
{
      Hash_bucket_t*    bucket;           /* table.var hash bucket      */
      Var_t*            oldv;       /* old variable value         */
      Var_t       newv;       /* new variable info          */
      Local_t*    next;       /* next in list               */
      int         line;       /* declaration line number    */
};

typedef struct Control_s            /* flow control block stack   */
{
      int         flags;            /* block flags                */
      union
      {
      char*       buffer;           /* loop body buffer           */
      size_t            offset;           /* loop body ip offset        */
      }           body;
      int         line;       /* loop line number           */
      union
      {
      struct
      {
      char*       test;       /* while loop test            */
      int         free;       /* free test                  */
      }           w;
      struct
      {
      Var_t*            var;        /* for loop variable          */
      char**            args;       /* for loop arg vector        */
      Sfio_t*           tmp;        /* for loop arg tmp           */
      Sfio_t*           vec;        /* for loop args tmp          */
      }           f;
      }           loop;
} Control_t;

typedef struct Parseinfo_s          /* recursive parse state stack      */
{
      Control_t*  cp;         /* control block pointer      */
      Control_t*  block;            /* control block lo water     */

      /* the rest are implicitly initialized */

      int         argc;       /* local argc                 */
      char*       name;       /* current level input name   */
      char*       here;       /* <<? termination string     */
      Sfio_t*           fp;         /* input file pointer         */
      Sfio_t*           ip;         /* readline write string      */
      Sfio_t*           scoped;           /* scoped options/assignments */
      char*       bp;         /* input buffer pointer       */
      char*       stashget;   /* loop body stash get        */
      char*       pushback;   /* line pushback pointer      */
      Local_t*    local;            /* local variables            */
      int         checkhere;  /* <<? offset                 */
      int         line;       /* prev level input line number     */
      int         splice;           /* splice line                */
      short       indent;           /* active indentation level   */
      unsigned char     eval;       /* eval block level           */
      unsigned char     status;           /* action return status       */
      unsigned int      newline:1;  /* \n at *bp replaced by 0    */
      unsigned int      prompt:1;   /* interactive input with prompt*/
      unsigned int      stashput:1; /* put lines in stash         */
} Parseinfo_t;

/*
 * WARNING: getline() uses the first keyword char
 */

static Namval_t         controls[] =      /* control keywords           */
{
      "break",    CON_break,
      "continue", CON_continue,
      "elif",           CON_elif,
      "else",           CON_else,
      "end",            CON_end,
      "error",    CON_error,
      "eval",           CON_eval,
      "exit",           CON_exit,
      "for",            CON_for,
      "if",       CON_if,
      "let",            CON_let,
      "local",    CON_local,
      "print",    CON_print,
      "read",           CON_read,
      "return",   CON_return,
      "rules",    CON_rules,
      "set",            CON_set,
      "while",    CON_while,
};

static Control_t  constack[PARSEDEPTH * 3];

static Parseinfo_t      parsestack[PARSEDEPTH] = { &constack[0], &constack[0] };
static Parseinfo_t*     pp = &parsestack[0];

static Local_t*         freelocals;

/*
 * unwind the parse stack to level on errors
 */

void
unparse(int level)
{
      register Local_t* lcl;
      register Local_t* olcl;
      register Control_t*     cp;
      register Rule_t*  r;

      if (pp >= &parsestack[elementsof(parsestack)])
            pp = &parsestack[elementsof(parsestack) - 1];
      while (pp > &parsestack[level])
      {
            if ((r = getrule(pp->name)) && r->active)
            {
                  r->active = r->active->previous;
                  if (r->status == UPDATE)
                        r->status = EXISTS;
            }
            if (pp->fp)
            {
                  if (pp->fp != sfstdin)
                        sfclose(pp->fp);
                  sfstrclose(pp->ip);
            }
            if (lcl = pp->local)
            {
                  pp->local = 0;
                  while (lcl)
                  {
                        lcl->bucket->value = (char*)lcl->oldv;
                        olcl = lcl;
                        lcl = lcl->next;
                        freelocal(olcl);
                  }
            }
            for (cp = pp->cp; cp > pp->block; cp--)
                  if (cp->flags & CON_for)
                  {
                        if (cp->loop.f.vec)
                        {
                              sfstrclose(cp->loop.f.vec);
                              sfstrclose(cp->loop.f.tmp);
                        }
                  }
                  else if (cp->flags & CON_while)
                  {
                        if (pp->cp->loop.w.free)
                              free(pp->cp->loop.w.test);
                  }
            pp--;
      }
}

/*
 * declare a single local variable t
 * redefinitions on same line>0 get value 1
 * if (flags & V_append) then old value is retained
 * if (flags & V_scope) then value survives PUSHLOCAL/POPLOCAL
 */

static void
declare(char* t, int line, long flags)
{
      register Var_t*         v;
      register Local_t* p;
      register char*          s;
      register char*          d;

      if (d = strchr(t, '='))
            *d = 0;
      if (!(v = getvar(t)))
      {
            for (s = t; *s; s++)
                  if (istype(*s, C_TERMINAL))
                        error(3, "%s: invalid local variable name", t);
            v = setvar(t, null, 0);
      }
      else if (!(flags & V_scope))
      {
            /*
             * check for duplicate declarations
             */

            for (p = pp->local; p; p = p->next)
                  if (v->name == p->newv.name)
                  {
                        if (d)
                              *d++ = '=';
                        else if (line && p->line == line)
                              d = "1";
                        else
                              d = null;
                        p->line = line;
                        setvar(v->name, d, 0);
                        return;
                  }
      }
      newlocal(p);
      p->next = pp->local;
      pp->local = p;
      p->oldv = v;
      p->newv.name = v->name;
      p->newv.property &= ~(V_readonly|V_scope);
      p->newv.property |= (v->property & (V_auxiliary|V_scan)) | (flags & V_scope);
      if (!p->newv.value || (p->newv.property & V_import))
      {
            p->newv.value = newof(0, char, MINVALUE + 1, 0);
            p->newv.length = MINVALUE;
            p->newv.property &= ~V_import;
            p->newv.property |= V_free;
      }
      p->bucket = hashlast(table.var);
      p->bucket->value = (char*)&p->newv;
      p->line = line;
      if (d)
      {
            *d++ = '=';
            setvar(v->name, d, 0);
      }
      else if (flags & V_append)
            setvar(v->name, v->value, 0);
      else
            *p->newv.value = 0;
}

/*
 * declare local variables using tmp string xp
 * s is first expanded
 * 2 funtion argument styles supported
 *    (formal ...) actual ...
 *    -[n] actual
 */

static void
local(Sfio_t* xp, char* v)
{
      register char*    t;
      register char*    a;
      long        top;
      int         argn;
      Control_t*  cp;
      Local_t*    p;
      int         argc = 0;
      int         optional = 0;
      char*       argv = 0;
      char*       formal = 0;
      Sfio_t*           ap = 0;

      for (cp = pp->cp; cp > pp->block; cp--)
            if (cp->flags & (CON_for|CON_while))
            {
                  for (p = pp->local; p; p = p->next)
                        p->line = 0;
                  break;
            }
      if (t = strchr(v, '\n'))
            *t = 0;
      top = sfstrtell(xp);
      expand(xp, v);
      sfputc(xp, 0);
      v = sfstrseek(xp, top, SEEK_SET);
      if (t)
            *t = '\n';
      while (t = getarg(&v, NiL))
      {
            if (!ap && *t == '-')
            {
                  ap = sfstropen();
                  if (!argc)
                        argc = 1;
                  argn = strtol(t + 1, NiL, 0);
            }
            else if (!ap && *t == '(')
            {
                  ap = sfstropen();
                  argv = t + strlen(t) - 1;
                  if (*argv != ')')
                        error(3, "%s: missing ) in formal argument list", t);
                  *argv = 0;
                  argv = t + 1;
                  argc = 1;
                  argn = 0;
                  formal = getarg(&argv, NiL);
            }
            else
            {
                  if (argv)
                  {
                        if (!formal)
                              error(3, "%s: only %d actual argument%s expected", t, argc - 1, argc == 2 ? null : "s");
                        if ((a = getarg(&argv, NiL)) && !optional && streq(a, "..."))
                        {
                              optional = 1;
                              if (*v)
                                    *(v - 1) = ' ';
                              if (!(a = getarg(&argv, NiL)))
                                    v = null;
                        }
                        argc++;
                        sfprintf(ap, "%s=%s", formal, t);
                        t = sfstruse(ap);
                        formal = a;
                  }
                  else if (argc)
                  {
                        sfprintf(ap, "%d=%s", argc++, t);
                        t = sfstruse(ap);
                  }
                  declare(t, error_info.line, 0);
            }
      }
      if (argc)
      {
            if (formal)
            {
                  a = getarg(&argv, NiL);
                  if (!optional && (!a || !streq(a, "...")))
                        error(3, "%s: actual argument expected", formal, argc - 1);
                  formal = a;
                  while (formal)
                  {
                        declare(formal, error_info.line, 0);
                        formal = getarg(&argv, NiL);
                  }
            }
            pp->argc = argc - 1;
            while (argc <= argn)
            {
                  sfprintf(ap, "%d", argc++);
                  declare(sfstruse(ap), error_info.line, 0);
            }
            sfstrclose(ap);
      }
}

/*
 * copy the local argc into internal.val
 */

void
argcount(void)
{
      sfprintf(internal.val, "%d", pp->argc);
}

/*
 * compute next for loop iteration variable
 */

static int
iterate(void)
{
      register char*          p;
      register Var_t*         v = pp->cp->loop.f.var;

      if (!(p = *pp->cp->loop.f.args++))
            return 0;

      /*
       * NOTE: this may bypass checks in setvar()
       */

      if (!(v->property & V_import) && v->value && strlen(p) <= v->length)
            strcpy(v->value, p);
      else
      {
            if (v->property & V_free)
            {
                  v->property &= ~V_free;
                  free(v->value);
            }
            v->value = p;
            v->property |= V_import;
      }
      debug((-6, "assignment: lhs=`%s' rhs=`%s'", v->name, v->value));
      return 1;
}

/*
 * check for pp directives
 */

static void
directive(register char* s)
{
      register char*    t;
      register int      n;
      Rule_t*           r;
      Stat_t            st;

      static Sfio_t*    file;

      while (isspace(*++s));
      if (isdigit(*s))
      {
            n = 0;
            while (isdigit(*s))
                  n = n * 10 + *s++ - '0';
            error_info.line = n - 1;
            while (isspace(*s))
                  s++;
            if (*s++ == '"')
            {
                  if (!file)
                        file = sfstropen();
                  while (*s && *s != '"')
                        sfputc(file, *s++);
                  t = sfstruse(file);
                  if (*s == '"')
                  {
                        if (!*t)
                              error_info.file = pp->name;
                        else
                        {
                              pathcanon(error_info.file = t, 0);
                              state.init++;
                              r = makerule(t);
                              state.init--;
                              if (!r->time && !(r->property & P_dontcare))
                              {
                                    if (stat(t, &st))
                                          r->property |= P_dontcare;
                                    else
                                    {
                                          r->time = tmxgetmtime(&st);
                                          compref(r, COMP_INCLUDE);
                                    }
                              }
                        }
                  }
            }
      }
      else
      {
            for (t = s; *s && !isspace(*s); s++);
            if (*s)
                  *s++ = 0;
            if (streq(t, "rules"))
                  rules(s);
            else if (!state.preprocess && strmatch(t, "assert|define|elif|else|endif|endmac|error|if|ifdef|ifndef|include|line|macdef|pragma|unassert|undef|warning"))
            {
                  state.preprocess = 1;
                  punt(0);
            }
      }
}

/*
 * read line from file or buffer
 *
 * `<newline>' splices the current and next lead line
 * `#...<newline>' comments (preceded by space) are stripped from files
 *
 * return value for file input placed in pp->ip
 */

static char*
readline(int lead)
{
      register char*    s;
      register char*    t;
      register char*    f;
      register int      c;
      register int      n;
      register int      q;
      Rule_t*           r;
      int         start;
      int         here;
      int         m;
      long        line;
      Sfio_t*           sps[2];

      trap();
      if (s = pp->pushback)
      {
            pp->pushback = 0;
            if (pp->fp)
            {
                  if (lead > 0 && (t = sfstrseek(pp->ip, 0, SEEK_CUR) - 1) > s && *(t - 1) == '\\')
                  {
                        sfstrseek(pp->ip, -2, SEEK_CUR);
                        line = s - sfstrbase(pp->ip);
                        goto pushback;
                  }
                  return s;
            }
            else if (t = pp->bp = strchr(s, '\n'))
            {
                  if (lead > 0 && t > s && *(t - 1) == '\\')
                        *(t - 1) = *t = ' ';
                  else
                  {
                        pp->newline = 1;
                        *pp->bp = 0;
                        return s;
                  }
            }
            else
                  return s;
      }
      else
            s = pp->bp;
      if (pp->fp)
      {
            if (s = pp->stashget)
            {
                  *s++ = '\n';
                  if (t = strchr(s, '\n'))
                        *(pp->stashget = t) = 0;
                  else
                        pp->stashget = 0;
                  return s;
            }
            if (pp->stashput)
            {
                  sfstrseek(pp->ip, -1, SEEK_CUR);
                  sfputc(pp->ip, '\n');
            }
            else
                  sfstrseek(pp->ip, 0, SEEK_SET);
            line = sfstrtell(pp->ip);
      pushback:
            do
            {
                  trap();
                  if (r = getrule(external.makeprompt))
                        maketop(r, P_dontcare|P_foreground, NiL);
                  if (pp->prompt)
                        error(ERROR_PROMPT, lead > 0 && pp->cp == pp->block ? PS1 : PS2);
                  if (pp->fp != sfstdin || !state.coshell || !state.coshell->outstanding)
                        break;
                  sps[0] = pp->fp;
                  sps[1] = state.coshell->msgfp;
                  if ((n = sfpoll(sps, 2, -1)) <= 0)
                        continue;
                  if (sps[0] == state.coshell->msgfp || n > 1 && sps[1] == state.coshell->msgfp)
                        while (block(1));
            } while (sps[0] != pp->fp && (n <= 1 || sps[1] != pp->fp));
            here = pp->checkhere = 0;
            n = q = 0;
            for (;;)
            {
                  switch (c = sfgetc(pp->fp))
                  {
                  case EOF:
                  eof:
                        if (q == COMMENT)
                              error(lead > 0 ? 2 : 1, "EOF in %c comment", q);
                        else if (q)
                              error(lead > 0 ? 2 : 1, "EOF in %c...%c quote starting at line %d", q, q, start);
                        else if (sfstrtell(pp->ip) > line)
                              error(lead > 0 ? 2 : 1, "file does not end with newline");
                        if (sfstrtell(pp->ip) > line)
                        {
                              sfputc(pp->ip, 0);
                              return sfstrbase(pp->ip) + line;
                        }
                        return 0;
                  case '\r':
                        if ((c = sfgetc(pp->fp)) != '\n')
                        {
                              if (c != EOF)
                                    sfungetc(pp->fp, c);
                              c = '\r';
                              break;
                        }
                        /*FALLTHROUGH*/
                  case '\n':
                  newline:
                        error_info.line++;
                        if (!q || q == COMMENT)
                        {
                              t = sfstrseek(pp->ip, 0, SEEK_CUR);
                              s = sfstrbase(pp->ip) + line;
                              while (t > s && (*(t - 1) == ' ' || *(t - 1) == '\t'))
                                    t--;
                              sfstrseek(pp->ip, t - sfstrbase(pp->ip), SEEK_SET);
                              sfputc(pp->ip, 0);
                              s = sfstrbase(pp->ip) + line;
                              if (*s == COMMENT)
                              {
                                    directive(s);
                                    *s = 0;
                                    sfstrseek(pp->ip, line + 1, SEEK_SET);
                              }
                              return s;
                        }
                        if (pp->prompt)
                              error(ERROR_PROMPT, PS2);
                        break;
                  case '\\':
                        switch (c = sfgetc(pp->fp))
                        {
                        case EOF:
                              sfputc(pp->ip, '\\');
                              goto eof;
                        case '\r':
                              if ((c = sfgetc(pp->fp)) != '\n')
                              {
                                    if (c != EOF)
                                          sfungetc(pp->fp, c);
                                    sfputc(pp->ip, '\\');
                                    c = '\r';
                                    break;
                              }
                              /*FALLTHROUGH*/
                        case '\n':
                              if (lead > 0)
                              {
                                    error_info.line++;
                                    if (pp->prompt)
                                          error(ERROR_PROMPT, PS2);
                                    continue;
                              }
                              pp->splice = error_info.line + 2;
                              sfungetc(pp->fp, '\n');
                              c = '\\';
                              break;
                        default:
                              sfputc(pp->ip, '\\');
                              break;
                        }
                        break;
                  case '"':
                  case '\'':
                        if (c == q)
                              q = 0;
                        else if (!q)
                        {
                              q = c;
                              start = error_info.line;
                        }
                        break;
                  case '/':
                        if (!q && (sfstrtell(pp->ip) == line || isspace(*(sfstrseek(pp->ip, 0, SEEK_CUR) - 1))))
                              switch (c = sfgetc(pp->fp))
                              {
                              case EOF:
                                    sfputc(pp->ip, '/');
                                    goto eof;
                              case '*':
                                    start = ++error_info.line;
                              mid:
                                    for (;;) switch (sfgetc(pp->fp))
                                    {
                                    case EOF:
                                    comeof:
                                          error(2, "EOF in /*...*/ comment starting at line %d", start);
                                          goto end;
                                    case '\n':
                                          error_info.line++;
                                          sfputc(pp->ip, '\n');
                                          if (pp->prompt)
                                                error(ERROR_PROMPT, PS2);
                                          break;
                                    case '/':
                                          for (;;) switch (c = sfgetc(pp->fp))
                                          {
                                          case EOF:
                                                goto comeof;
                                          case '*':
                                                if (error_info.line > start)
                                                      error(2, "/* in /*...*/ comment starting at line %d", start);
                                                break;
                                          case '/':
                                                break;
                                          default:
                                                sfungetc(pp->fp, c);
                                                goto mid;
                                          }
                                    case '*':
                                          for (;;) switch (c = sfgetc(pp->fp))
                                          {
                                          case EOF:
                                                goto comeof;
                                          case '*':
                                                break;
                                          case '/':
                                                goto end;
                                          default:
                                                sfungetc(pp->fp, c);
                                                goto mid;
                                          }
                                    }
                              end:
                                    error_info.line--;
                                    c = ' ';
                                    break;
                              default:
                                    sfungetc(pp->fp, c);
                                    c = '/';
                                    break;
                              }
                        break;
                  case COMMENT:
                        if (!q && sfstrtell(pp->ip) > line && isspace(*(sfstrseek(pp->ip, 0, SEEK_CUR) - 1)))
                              for (q = c;;)
                                    switch (sfgetc(pp->fp))
                                    {
                                    case EOF:
                                          goto eof;
                                    case '\\':
                                          switch (sfgetc(pp->fp))
                                          {
                                          case EOF:
                                                goto eof;
                                          case '\\':
                                                sfungetc(pp->fp, '\\');
                                                break;
                                          case '\n':
                                                if (!lead)
                                                      goto newline;
                                                error_info.line++;
                                                sfputc(pp->ip, '\n');
                                                if (pp->prompt)
                                                      error(ERROR_PROMPT, PS2);
                                                break;
                                          }
                                          continue;
                                    case '\n':
                                          goto newline;
                                    }
                        break;
                  case '(':
                  case '[':
                  case '{':
                        if (!q)
                              n++;
                        break;
                  case ')':
                  case ']':
                  case '}':
                        if (!q && n)
                              n--;
                        break;
                  case '<':
                        if (!q && !n && !pp->checkhere && (m = sfstrtell(pp->ip) - line))
                        {
                              if (m == here)
                                    pp->checkhere = 1;
                              else
                                    here = m + 1;
                        }
                        break;
                  }
                  sfputc(pp->ip, c);
            }
      }
      else if (s)
      {
            error_info.line++;
            if (pp->newline)
            {
                  pp->newline = 0;
                  *s++ = '\n';
            }
            if (f = strchr(s, '\n'))
            {
                  if (lead > 0 && f > s && *(f - 1) == '\\')
                  {
                        /*
                         * NOTE: `\\n' are permanently eliminated *each pass*
                         *     line counts are preserved by adding newlines
                         *     after sliding the spliced segment(s)
                         */

                        n = 1;
                        t = f - 1;
                        for (;;)
                        {
                              if (!(c = *++f))
                              {
                                    f = 0;
                                    *t = 0;
                                    break;
                              }
                              else if (c != '\n')
                                    *t++ = c;
                              else if (*(f - 1) != '\\')
                              {
                                    f = t + 1;
                                    while (n--)
                                    {
                                          *t++ = ' ';
                                          *t++ = '\n';
                                    }
                                    break;
                              }
                              else
                              {
                                    n++;
                                    t--;
                              }
                        }
                  }
            }
            if (pp->bp = f)
            {
                  *f = 0;
                  pp->newline = 1;
            }
            return s;
      }
      else
            return 0;
}

/*
 * structured flow control line input into sp
 *
 * lead identifies a lead line (as opposed to an action line)
 * term is the line terminator char placed in sp
 * 0 return on EOF
 */

static int
getline(Sfio_t* sp, int lead, int term)
{
      register int      c;
      register char*    s;
      register char*    t;
      register int      indent;
      Time_t            tm;
      long        n;
      int         i;
      char*       e;
      char*       lin;
      char*       tok;
      Control_t*  cp;
      Namval_t*   nv;

      if (pp->here && !lead)
      {
            t = pp->here;
            pp->here = 0;
            while ((s = sfgetr(pp->fp, '\n', 1)) && !streq(s, t))
                  sfputr(sp, s, term);
            if (!s)
                  error(3, "here document terminator \"%s\" not found", t);
            free(t);
            return sfstrtell(sp) != 0;
      }
 again:
      while (s = lin = readline(lead))
      {
            indent = 0;
            if (lead > 0 && !(pp->cp->flags & CON_skip))
                  pp->indent = SHRT_MAX;
            while (indent <= pp->indent)
            {
                  if (*s == '\t')
                  {
                        s++;
                        indent += state.tabstops - indent % state.tabstops;
                  }
                  else if (*s == ' ')
                  {
                        static int  warned;

                        for (t = s; isspace(*t); t++);
                        if (!*t)
                              goto again;
                        if (!warned)
                        {
                              error(1, "<space> indentation may be non-portable");
                              warned = 1;
                        }
                        while (*s == ' ')
                        {
                              s++;
                              indent++;
                        }
                  }
                  else
                        break;
            }
            if (lead > 0 && indent <= pp->indent && (c = *s))
            {
                  pp->indent = indent;
                  if (c == 'b' || c == 'c' || c == 'e' || c == 'f' || c == 'i' || c == 'l' || c == 'p' || c == 'r' || c == 's' || c == 'w')
                  {
                        for (t = s; istype(*t, C_VARIABLE2); t++);
                        if (istype(c = *t, C_SEP))
                        {
                              *t = 0;
                              i = (nv = (Namval_t*)strsearch(controls, elementsof(controls), sizeof(*controls), stracmp, s, NiL)) ? nv->value : 0;
                              for (*t = c; isspace(*t); t++);
                              c = pp->cp->flags;
                              /*UNDENT*/
      if (i)
      {
            debug((-7, "%s:%d:test: `%s'", error_info.file, error_info.line, s));
            switch (i)
            {

            case CON_if:
                  if (++pp->cp >= &constack[elementsof(constack)])
                        error(3, "if nesting too deep");
                  pp->cp->flags = CON_if;
                  if (c & CON_skip)
                        pp->cp->flags |= CON_kept | CON_skip;
                  else if (expr(sp, t))
                        pp->cp->flags |= CON_kept;
                  else
                        pp->cp->flags |= CON_skip;
                  continue;

            case CON_else:
                  if (!(c & CON_if))
                        error(3, "no matching if for else");
                  if (*t != 'i' || *(t + 1) != 'f' || !isspace(*(t + 2)))
                  {
                        if (c & CON_else)
                              error(1, "only one else per if");
                        if (*t)
                              error(1, "%s: tokens after else ignored", t);
                        pp->cp->flags |= CON_else;
                        if (c & CON_kept)
                              pp->cp->flags |= CON_skip;
                        else
                        {
                              pp->cp->flags |= CON_kept;
                              pp->cp->flags &= ~CON_skip;
                        }
                        continue;
                  }
                  t++;
                  while (isspace(*++t));
                  /*FALLTHROUGH*/

            case CON_elif:
                  if (!(c & CON_if))
                        error(3, "no matching if for elif");
                  if (c & CON_else)
                        error(1, "elif after else");
                  if ((c & CON_kept) || !expr(sp, t))
                        pp->cp->flags |= CON_skip;
                  else
                  {
                        pp->cp->flags |= CON_kept;
                        pp->cp->flags &= ~CON_skip;
                  }
                  continue;

            case CON_end:
                  if (pp->cp <= pp->block)
                        error(3, "unmatched end");
                  if (*t)
                        error(1, "%s: tokens after end ignored", t);
                  if (c & CON_eval)
                        pp->eval--;
                  else if (!(c & CON_skip) || (c & (CON_for | CON_while)) && !(c & CON_kept))
                  {
                        pp->cp->flags &= ~(CON_kept | CON_skip);
                        if ((c & CON_while) && expr(sp, pp->cp->loop.w.test) || (c & CON_for) && iterate())
                        {
                              if (pp->newline)
                              {
                                    pp->newline = 0;
                                    *pp->bp = '\n';
                              }
                              if (pp->fp)
                              {
                                    if (pp->stashget)
                                          *pp->stashget = '\n';
                                    pp->stashget = sfstrbase(pp->ip) + pp->cp->body.offset;
                              }
                              else
                                    pp->bp = pp->cp->body.buffer;
                              error_info.line = pp->cp->line;
                              continue;
                        }
                  }
                  if (c & CON_for)
                  {
                        if (pp->cp->loop.f.vec)
                        {
                              sfstrclose(pp->cp->loop.f.vec);
                              sfstrclose(pp->cp->loop.f.tmp);
                        }
                  }
                  if (c & CON_while)
                  {
                        if (pp->cp->loop.w.free)
                              free(pp->cp->loop.w.test);
                  }
                  if (c & CON_stash)
                        pp->stashput = 0;
                  pp->cp--;
                  continue;

            case CON_for:
                  if (++pp->cp >= &constack[elementsof(constack)])
                        error(3, "for nesting too deep");
                  pp->cp->flags = CON_for;
                  pp->cp->loop.f.vec = 0;
                  if (c & CON_skip)
                        pp->cp->flags |= CON_kept | CON_skip;
                  else
                  {
                        Sfio_t*     tp;

                        pp->cp->loop.f.tmp = tp = sfstropen();
                        expand(tp, t);
                        tok = sfstruse(tp);
                        if (!(s = getarg(&tok, NiL)))
                              error(3, "for loop variable omitted");
                        pp->cp->loop.f.vec = tp = sfstropen();
                        do putptr(tp, t = getarg(&tok, NiL)); while (t);
                        if (*(pp->cp->loop.f.args = (char**)sfstrseek(tp, 0, SEEK_SET)))
                        {
                              pp->cp->loop.f.var = setvar(s, null, 0);
                              if (!iterate())
                                    pp->cp->flags |= CON_kept | CON_skip;
                              else
                              {
                                    if (pp->fp)
                                    {
                                          if (!pp->stashput)
                                          {
                                                pp->stashput = 1;
                                                pp->cp->flags |= CON_stash;
                                                sfstrseek(pp->ip, 0, SEEK_SET);
                                                sfputc(pp->ip, 0);
                                          }
                                          pp->cp->body.offset = pp->stashget ? (pp->stashget - sfstrbase(pp->ip)) : (sfstrtell(pp->ip) - 1);
                                    }
                                    else if (!pp->bp)
                                          error(3, "for loop body expected");
                                    else
                                          pp->cp->body.buffer = pp->bp + 1;
                                    pp->cp->line = error_info.line;
                              }
                        }
                        else
                              pp->cp->flags |= CON_kept | CON_skip;
                  }
                  continue;

            case CON_while:
                  if (++pp->cp >= &constack[elementsof(constack)])
                        error(3, "while nesting too deep");
                  pp->cp->flags = CON_while;
                  pp->cp->loop.w.free = 0;
                  if ((c & CON_skip) || !expr(sp, t))
                        pp->cp->flags |= CON_kept | CON_skip;
                  else
                  {
                        pp->cp->loop.w.test = t;
                        if (pp->fp)
                        {
                              if (!pp->stashput)
                              {
                                    pp->stashput = 1;
                                    pp->cp->flags |= CON_stash;
                                    sfstrseek(pp->ip, 0, SEEK_SET);
                                    sfputc(pp->ip, 0);
                                    pp->cp->loop.w.test = strdup(t);
                                    pp->cp->loop.w.free = 1;
                              }
                              pp->cp->body.offset = pp->stashget ? (pp->stashget - sfstrbase(pp->ip)) : (sfstrtell(pp->ip) - 1);
                        }
                        else if (!pp->bp)
                              error(3, "while loop body expected");
                        else
                              pp->cp->body.buffer = pp->bp + 1;
                        pp->cp->line = error_info.line;
                  }
                  continue;

            case CON_break:
            case CON_continue:
                  if (!(pp->cp->flags & CON_skip))
                  {
                        c = *t ? expr(sp, t) : 1;
                        for (cp = pp->cp; cp > pp->block; cp--)
                        {
                              if (cp <= pp->block)
                                    error(3, "%s outside of loop", s);
                              cp->flags |= CON_skip | CON_kept;
                              if ((cp->flags & (CON_for | CON_while)) && --c <= 0)
                              {
                                    if (i == CON_continue)
                                          cp->flags &= ~CON_kept;
                                    break;
                              }
                        }
                  }
                  continue;

            case CON_return:
                  if (!(pp->cp->flags & CON_skip))
                  {
                        if (state.frame->target->property & P_functional)
                        {
                              n = sfstrtell(sp);
                              expand(sp, t);
                              s = sfstrbase(sp) + n;
                              t = sfstrseek(sp, 0, SEEK_CUR);
                              while (t > s && isspace(*(t - 1)))
                                    t--;
                              sfstrseek(sp, t - sfstrbase(sp), SEEK_SET);
                              sfputc(sp, 0);
                              setvar(state.frame->target->name, sfstrseek(sp, n, SEEK_SET), 0);
                              debug((-5, "%s returns `%s'", state.frame->target->name, sfstrseek(sp, 0, SEEK_CUR)));
                        }
                        else if (*t)
                        {
                              if ((tm = timenum(t, &e)) != TMX_NOTIME && *e)
                              {
                                    if ((n = expr(sp, t)) == -1)
                                          tm = TMX_NOTIME;
                                    else
                                          tm = tmxsns(n, 0);
                              }
                              if (tm == TMX_NOTIME)
                              {
                                    pp->status = FAILED;
                                    debug((-5, "return fail"));
                              }
                              else if (tm)
                              {
                                    pp->status = TOUCH;
                                    internal.internal->time = tm;
                                    debug((-5, "return [%s]", timestr(internal.internal->time)));
                              }
                              else
                              {
                                    pp->status = EXISTS;
                                    debug((-5, "return no update"));
                              }
                        }
                        else
                              debug((-5, "return normal"));
                        for (cp = pp->cp; cp >= pp->block; cp--)
                              cp->flags |= CON_skip | CON_kept;
                  }
                  continue;

            case CON_eval:
                  if (++pp->cp >= &constack[elementsof(constack)])
                        error(3, "eval nesting too deep");
                  pp->cp->flags = CON_eval | (c & (CON_kept | CON_skip));
                  pp->eval++;
                  continue;

            case CON_rules:
                  if (!(pp->cp->flags & CON_skip))
                        rules(t);
                  continue;

            case CON_let:
                  if (!(pp->cp->flags & CON_skip))
                        expr(sp, t);
                  continue;

            case CON_local:
                  if (!(pp->cp->flags & CON_skip))
                        local(sp, t);
                  continue;

            case CON_error:
                  if (!(pp->cp->flags & CON_skip))
                  {
                        n = sfstrtell(sp);
                        expand(sp, t);
                        sfputc(sp, 0);
                        i = strtol(sfstrseek(sp, n, SEEK_SET), &tok, 0);
                        for (t = tok; isspace(*t); t++);
                        c = error_info.line;
                        if (i > 0 && !(i & 040))
                              error_info.line = 0;
                        error(i, "%s", t);
                        error_info.line = c;
                  }
                  continue;

            case CON_exit:
                  if (!(pp->cp->flags & CON_skip))
                  {
                        n = sfstrtell(sp);
                        expand(sp, t);
                        sfputc(sp, 0);
                        i = strtol(sfstrseek(sp, n, SEEK_SET), &tok, 0);
                        finish(i);
                  }
                  continue;

            case CON_print:
            case CON_read:
                  if (!(pp->cp->flags & CON_skip))
                  {
                        int         d;
                        int         m;
                        int         x;
                        char*       a;
                        char*       f;

                        n = sfstrtell(sp);
                        expand(sp, t);
                        sfputc(sp, 0);
                        a = sfstrseek(sp, n, SEEK_SET);
                        d = i == CON_print;
                        f = 0;
                        n = '\n';
                        /*UNDENT...*/

      for (;;)
      {
            for (t = a; *t == ' '; t++);
            if ((x = *t) != '-' && x != '+')
                  break;
            a = t;
            t = getarg(&a, NiL) + 1;
            if (*(t - 1) == *t && !*(t + 1))
            {
                  for (t = a; *t == ' '; t++);
                  break;
            }
            for (;;)
            {
                  switch (m = *t++)
                  {
                  case 0:
                        break;
                  case 'f':
                        if (*t)
                              f = t;
                        else if (!(f = getarg(&a, NiL)))
                              error(3, "-f: format argument expected");
                        break;
                  case 'i':
                  case 'o':
                  case 'p':
                        if (!*t && !(t = getarg(&a, NiL)))
                        {
                              error(3, "-%c: file argument expected", m);
                              t = "-";
                        }
                        if (state.io[d])
                        {
                              if (state.io[d] != sfstdin && state.io[d] != sfstdout && state.io[d] != sfstderr)
                                    sfclose(state.io[d]);
                              state.io[d] = 0;
                        }
                        if (!streq(t, "-"))
                              switch (m)
                              {
                              case 'i':
                                    if (!(state.io[d] = sfopen(NiL, t, x == '+' ? "rw" : "r")))
                                          error(ERROR_SYSTEM|2, "%s: cannot read", t);
                                    break;
                              case 'o':
                                    if (!(state.io[d] = sfopen(NiL, t, x == '+' ? "a" : "w")))
                                          error(ERROR_SYSTEM|2, "%s: cannot write", t);
                                    break;
                              case 'p':
                                    if (!(state.io[d] = sfpopen(NiL, t, "rw")))
                                          error(ERROR_SYSTEM|2, "%s: cannot connect to coprocess", t);
                                    else
                                          sfdcslow(state.io[d]);
                                    break;
                              }
                        if (!state.io[d])
                              switch (d)
                              {
                              case 0:
                                    state.io[d] = sfstdin;
                                    break;
                              case 1:
                                    state.io[d] = sfstdout;
                                    break;
                              case 2:
                                    state.io[d] = sfstderr;
                                    break;
                              }
                        else
                        {
                              if (m != 'p')
                                    sfsetbuf(state.io[d], NiL, 0);
                              fcntl(sffileno(state.io[d]), F_SETFD, FD_CLOEXEC);
                        }
                        break;
                  case 'n':
                        n = -1;
                        continue;
                  case 'u':
                        if (!*t && !(t = getarg(&a, NiL)))
                              error(3, "-u: unit argument expected");
                        switch (c = *t++)
                        {
                        case 'm':
                              if ((state.io[d = elementsof(state.io) - 1] = state.mam.out) && *state.mam.label)
                                    sfputr(state.io[d], state.mam.label, -1);
                              break;
                        case '0': case '1': case '2': case '3': case '4':
                        case '5': case '6': case '7': case '8': case '9':
                              d = c - '0';
                              break;
                        default:
                              error(2, "-u: unit [0-9m] expected");
                              break;
                        }
                        continue;
                  default:
                        error(3, "-%c: unknown option", *(t - 1));
                        continue;
                  }
                  break;
            }
      }

                        /*...INDENT*/
                        if (i == CON_print)
                        {
                              if (state.io[d] && (sfset(state.io[d], 0, 0) & SF_WRITE))
                              {
                                    if (f)
                                          strprintf(state.io[d], f, t, 0, n);
                                    else
                                          sfputr(state.io[d], t, n);
                              }
                              else if (*t || n != -1)
                                    error(2, "unit %d not open for writing", d);
                        }
                        else if (n != -1 || *t)
                        {
                              if (state.io[d] && (sfset(state.io[d], 0, 0) & SF_READ))
                              {
                                    if (!*(a = t) || !(t = getarg(&a, NiL)) || getarg(&a, NiL))
                                          error(2, "one variable argument expected");
                                    else
                                    {
                                          if (f)
                                                error(1, "read format ignored");
                                          sfset(state.io[d], SF_IOINTR, 1);
                                          if (!(f = sfgetr(state.io[d], '\n', 1)))
                                                f = null;
                                          setvar(t, f, 0);
                                    }
                              }
                              else
                                    error(2, "unit %d not open for reading", d);
                        }
                  }
                  continue;

            case CON_set:
                  if (!(pp->cp->flags & CON_skip))
                  {
                        n = sfstrtell(sp);
                        expand(sp, t);
                        sfputc(sp, 0);
                        set(sfstrseek(sp, n, SEEK_SET), 1, pp->scoped);
                  }
                  continue;
            }
      }
                              /*INDENT*/
                        }
                  }
            }
#if DEBUG
            if (pp->cp->flags & CON_skip)
                  debug((-8, "%s:%d:skip: `%s'", error_info.file, error_info.line, s));
            else
                  debug((-7, "%s:%d:data: `%s'", error_info.file, error_info.line, s));
#endif
            if (!(pp->cp->flags & CON_skip))
            {
                  for (t = s; isspace(*t); t++);
                  if (*t)
                  {
                        if (!lead && indent <= pp->indent)
                        {
                              if (pp->newline)
                              {
                                    pp->newline = 0;
                                    *pp->bp = '\n';
                              }
                              pp->pushback = lin;
                              return 0;
                        }
                        break;
                  }
                  else if (!lead && pp->fp == sfstdin)
                        return 0;
                  else if (pp->splice == error_info.line)
                        break;
                  else if (!lead)
                  {
                        sfputr(sp, s, term);
                        return 1;
                  }
            }
      }
      if (!s)
            return 0;
      switch (i = pp->eval)
      {
      case 0:
            sfputr(sp, s, term);
            break;
      case 1:
            expand(sp, s);
            sfputc(sp, term);
            break;
      default:
            {
                  Sfio_t*     tp;
                  Sfio_t*     xp;

                  tp = sfstropen();
                  for (;;)
                  {
                        xp = (i & 1) ? sp : tp;
                        expand(xp, s);
                        if (--i <= 0)
                              break;
                        s = sfstruse(xp);
                  }
                  sfstrclose(tp);
                  sfputc(sp, term);
            }
            break;
      }
      return 1;
}

/*
 * makefile statement parser
 *
 * the statement grammar is
 *
 *    <lhs> <op> <rhs> <act>
 *
 * <lhs>, <rhs> and <act> are placed in sp
 * <op> determines which components are expanded when read
 *
 *    <op>  <lhs> <rhs> <act>
 *    -----------------------------
 *     =      0     0     -
 *     :=     1     1     -
 *     +=     1     1     -
 *     :      1     1     0
 *    :op:    0     0     0
 *
 * an action <act> is not expanded when read
 * actions are indented by a number of <tab> chars
 * the indent tabs are stripped from each line in the action
 * the first line not indented by this amount terminates the action
 * ideally only '\t' should be used although ' ' are ok using state.tabstops
 * the statement operator flags OP_* are returned
 */

static int
statement(Sfio_t* sp, char** lhs, Rule_t** opr, char** rhs, char** act)
{
      register int      c;
      register char*    s;
      register char*    t;
      char*       b;
      char*       p;
      char*       brace = 0;
      char*       ecarb = 0;
      int         item = 0;
      int         op = 0;
      int         nest = 0;
      int         paren = 0;
      int         quote = 0;
      long        rhs_pos = -1;
      long        act_pos = -1;
      long        lin_pos;

      if (!getline(sp, 1, 0))
            return op;
      *opr = 0;
      b = s = sfstrbase(sp);
      while (c = *s++)
      {
            if (c == '\\')
            {
                  if (*s)
                        s++;
            }
            else if (c == quote)
                  quote = 0;
            else if (c == '"' || c == '\'')
                  quote = c;
            else if (!quote)
            {
                  if (brace)
                  {
                        if (c == '{')
                              nest++;
                        else if (c == '}' && !--nest)
                        {
                              for (p = s; isspace(*p); p++);
                              if (*p || brace > b && *(brace - 1) == '$')
                              {
                                    brace = 0;
                                    continue;
                              }
                              ecarb = s - 1;
                        }
                  }
                  else if (c == '(')
                        paren++;
                  else if (c == ')' && !paren--)
                        error(3, "to many )'s");
                  else if (!paren)
                  {
                        if (c == '{')
                        {
                              brace = s - 1;
                              nest++;
                        }
                        else if (c == '}')
                              error(3, "unbalanced {...}");
                        else if (c == ':')
                        {
                              if (item < 2 && *s == '=')
                              {
                                    t = s + 1;
                                    op = OP_ASSIGN|OP_EXPAND;
                                    break;
                              }
                              t = s;
                              if (istype(*t, C_ID1))
                                    while (istype(*++t, C_ID2));
                              if (*t++ == ':')
                              {
                                    c = *t;
                                    *t = 0;
                                    if (!(*opr = getrule(s - 1)) || !((*opr)->property & P_operator))
                                    {
                                          Sfio_t*     tmp;

                                          tmp = sfstropen();
                                          *(t - 1) = 0;
                                          sfprintf(tmp, "%s%s", s, external.source);
                                          *(t - 1) = ':';
                                          op = readfile(sfstruse(tmp), COMP_INCLUDE|COMP_DONTCARE, NiL);
                                          sfstrclose(tmp);
                                          if (!op || !(*opr = getrule(s - 1)) || !((*opr)->property & P_operator))
                                          {
                                                *opr = internal.op;
                                                error(1, "operator %s not defined", s - 1);
                                          }
                                    }
                                    *t = c;
                                    op = OP_ASSERT|OP_ACTION|OP_EXPAND;
                                    if (((*opr)->property & P_ignore))
                                          op &= ~OP_EXPAND;
                                    break;
                              }
                              t = s;
                              op = OP_ASSERT|OP_ACTION|OP_EXPAND;
                              break;
                        }
                        else if (item < 2)
                        {
                              if (isspace(c))
                              {
                                    item++;
                                    while (isspace(*s))
                                          s++;
                                    if ((c = *s) != ':' && c != '+' && c != '&' && c != '=')
                                          item++;
                              }
                              else if (c == '+')
                              {
                                    if (*s == '=')
                                    {
                                          t = s + 1;
                                          op = OP_ASSIGN|OP_APPEND|OP_EXPAND;
                                          break;
                                    }
                              }
                              else if (c == '&')
                              {
                                    if (*s == '=')
                                    {
                                          t = s + 1;
                                          op = OP_ASSIGN|OP_EXPAND|OP_AUXILIARY;
                                          break;
                                    }
                              }
                              else if (c == '=')
                              {
                                    if (*s == '=')
                                    {
                                          t = s + 1;
                                          op = OP_ASSIGN|OP_STATE;
                                          break;
                                    }
                                    t = s;
                                    op = OP_ASSIGN;
                                    break;
                              }
                        }
                  }
            }
      }
      if (quote)
            error(1, "missing closing %c quote", quote);
      else if (paren)
            error(1, "%d closing )%s missing from line", paren, paren == 1 ? null : "'s");
      if (op)
      {
            if (op & OP_EXPAND)
            {
                  Sfio_t*     tmp;

                  tmp = sfstropen();
                  *(s - 1) = 0;
                  sfputr(tmp, sfstrbase(sp), 0);
                  rhs_pos = sfstrtell(tmp);
                  sfputr(tmp, t, 0);
                  sfstrseek(sp, 0, SEEK_SET);
                  expand(sp, sfstrseek(tmp, 0, SEEK_SET));
                  sfputc(sp, 0);
                  sfstrseek(tmp, rhs_pos, SEEK_SET);
                  rhs_pos = sfstrtell(sp);
                  expand(sp, sfstrseek(tmp, 0, SEEK_CUR));
                  sfputc(sp, 0);
                  sfstrclose(tmp);
                  s = t = sfstrbase(sp) + rhs_pos;
                  p = sfstrseek(sp, 0, SEEK_CUR) - 1;
            }
            else
                  p = t + strlen(t);
            while (p > t && isspace(*(p - 1)))
                  p--;
            *p = 0;
            while (isspace(*t))
                  t++;
            rhs_pos = t - sfstrbase(sp);
            t = sfstrbase(sp);
            for (s--; s > t && isspace(*(s - 1)); s--);
            *s = 0;
      }
      else
      {
            op = OP_EMPTY;
            p = s - 1;
            s = sfstrbase(sp);
            while (p > s && isspace(*(p - 1)))
                  p--;
            *p = 0;
      }
      if (brace)
      {
            for (t = brace; t > s && isspace(*(t - 1)); t--);
            *t = 0;
            if (ecarb)
            {
                  *ecarb = 0;
                  nest = 0;
            }
            if (p > ++brace)
                  *p++ = '\n';
            for (;;)
            {
                  if (!*brace)
                  {
                        *brace = ' ';
                        *(brace + 1) = 0;
                        break;
                  }
                  else if (!isspace(*brace) || *brace == '\n')
                        break;
                  brace++;
            }
            act_pos = brace - sfstrbase(sp);
            sfstrseek(sp, p - sfstrbase(sp), SEEK_SET);
            quote = 0;
            while (nest)
            {
                  lin_pos = sfstrtell(sp);
                  if (!getline(sp, -1, '\n'))
                  {
                        error(2, "unbalanced {...} action");
                        break;
                  }
                  s = sfstrbase(sp) + lin_pos;
                  t = sfstrseek(sp, 0, SEEK_CUR);
                  while (s < t)
                  {
                        if ((c = *s++) == '\\')
                        {
                              if (s < t)
                                    s++;
                        }
                        else if (c == quote)
                              quote = 0;
                        else if (c == '"' || c == '\'')
                              quote = c;
                        else if (!quote)
                        {
                              if (c == '{')
                                    nest++;
                              else if (c == '}' && !--nest)
                              {
                                    p = s - 1;
                                    while (s < t && isspace(*s))
                                          s++;
                                    if (s < t)
                                          error(2, "tokens after closing } ignored");
                                    t = sfstrbase(sp) + act_pos;
                                    while (p > t && isspace(*(p - 1)))
                                          p--;
                                    *p = 0;
                                    if (!*t)
                                          act_pos = -1;
                                    break;
                              }
                        }
                  }
            }
      }
      else if (op & OP_ACTION)
      {
            if (pp->checkhere)
            {
                  t = b = sfstrbase(sp) + rhs_pos;
                  nest = quote = 0;
                  for (;;)
                  {
                        switch (*t++)
                        {
                        case 0:
                              break;
                        case '"':
                        case '\'':
                              if (*(t - 1) == quote)
                                    quote = 0;
                              else if (!quote)
                                    quote = *(t - 1);
                              continue;
                        case '(':
                        case '[':
                        case '{':
                              if (!quote)
                                    nest++;
                              continue;
                        case ')':
                        case ']':
                        case '}':
                              if (!quote)
                                    nest--;
                              continue;
                        case '<':
                              if (!nest && !quote && *t == '<')
                              {
                                    s = t - 1;
                                    while (isspace(*++t));
                                    if (*t)
                                    {
                                          p = t;
                                          while (*++t)
                                                if (isspace(*t))
                                                {
                                                      *t = 0;
                                                      break;
                                                }
                                          pp->here = strdup(p);
                                          while (--s >= b && isspace(*s));
                                          *(s + 1) = 0;
                                    }
                                    break;
                              }
                              continue;
                        default:
                              continue;
                        }
                        break;
                  }
                  if (!*b)
                        rhs_pos = -1;
            }
            act_pos = ++p - sfstrbase(sp);
            sfstrseek(sp, act_pos, SEEK_SET);
            while (getline(sp, 0, '\n'));
            t = sfstrbase(sp) + act_pos;
            s = sfstrseek(sp, 0, SEEK_CUR);
            for (;;)
            {
                  if (s <= t)
                  {
                        act_pos = -1;
                        break;
                  }
                  if (*--s != '\n')
                  {
                        *(s + 1) = 0;
                        break;
                  }
            }
      }
      *lhs = sfstrseek(sp, 0, SEEK_SET);
      *rhs = (rhs_pos >= 0) ? sfstrbase(sp) + rhs_pos : null;
      *act = (act_pos >= 0) ? sfstrbase(sp) + act_pos : null;
      return op;
}

static const Namval_t   nametypes[] =
{
      "altstate", NAME_altstate,
      "assignment",     NAME_assignment,
      "context",  NAME_context,
      "dynamic",  NAME_dynamic,
      "glob",           NAME_glob,
      "identifier",     NAME_identifier,
      "intvar",   NAME_intvar,
      "option",   NAME_option,
      "path",           NAME_path,
      "staterule",      NAME_staterule,
      "statevar", NAME_statevar,
      "variable", NAME_variable,
};

/*
 * parse a basic assertion statement
 */

static void
assertion(char* lhs, Rule_t* opr, char* rhs, char* act, int op)
{
      register char*          s;
      register Rule_t*  r;
      register List_t*  p;
      register List_t*  q;
      int               c;
      int               i;
      int               n;
      int               isactive;
      Rule_t*                 x;
      Rule_t*                 joint;
      Var_t*                  v;
      List_t*                 jointail;
      List_t*                 prereqs;
      char*             name;
      struct                              /* prereq attributes    */
      {
            Rule_t            rule;       /* rule attributes      */
            int         op;         /* assertion op         */
      }                 *att, clr, set;

      if (opr)
      {
            debug((-6, "operator: lhs=`%s' %s rhs=`%s' act=`%-.1024s'", lhs, opr->name, rhs, act));
            apply(opr, lhs, rhs, act, CO_ALWAYS|CO_LOCAL|CO_URGENT);
            return;
      }
      if (internal.assert_p->prereqs && (opr = associate(internal.assert_p, NiL, lhs, NiL)) && opr->prereqs && (opr = opr->prereqs->rule) && (opr->property & P_operator) && !opr->uname)
      {
            s = opr->uname = opr->name;
            opr->name = lhs;
            apply(opr, lhs, rhs, act, CO_ALWAYS|CO_LOCAL|CO_URGENT);
            opr->name = s;
            opr->uname = 0;
            return;
      }
      debug((-6, "assertion: lhs=`%s' rhs=`%-.1024s' act=`%-.1024s'", lhs, rhs, act));

      /*
       * special check for internal.query
       */

      if (getrule(lhs) == internal.query)
      {
            c = 0;
            if (!(s = getarg(&rhs, NiL)))
                  interpreter(NiL);
            else if (*s == '-' && !*(s + 1))
                  while (s = getarg(&rhs, NiL))
            {
                  if (streq(s, "blocked"))
                  {
#if DEBUG
                        c = 1;
                        error(0, null);
                        dumpjobs(2, JOB_blocked);
#else
                        error(2, "%s: implemented in DEBUG==1 version", s);
#endif
                  }
                  else if (streq(s, "buckets"))
                  {
                        c = 0;
                        hashdump(NiL, HASH_BUCKET);
                  }
                  else if (streq(s, "hash"))
                  {
                        c = 0;
                        hashdump(NiL, 0);
                  }
                  else if (streq(s, "jobs"))
                  {
#if DEBUG
                        c = 1;
                        error(0, null);
                        dumpjobs(2, JOB_status);
#else
                        error(2, "%s: implemented in DEBUG==1 version", s);
#endif
                  }
                  else if (streq(s, "nametype"))
                  {
                        while (s = getarg(&rhs, NiL))
                        {
                              n = nametype(s, NiL);
                              sfprintf(sfstdout, "%16s", s);
                              for (i = 0; i < elementsof(nametypes); i++)
                                    if (n & nametypes[i].value)
                                          sfprintf(sfstdout, " %s", nametypes[i].name);
                              sfputc(sfstdout, '\n');
                        }
                        break;
                  }
                  else if (streq(s, "rules"))
                  {
                        c = 1;
                        state.ruledump = 1;
                        dump(sfstderr, 0);
                        state.ruledump = 0;
                  }
                  else if (streq(s, "stack"))
                  {
                        Parseinfo_t*      sp = pp;
                        Local_t*    lp;

                        c = 1;
                        while (--sp > &parsestack[0])
                        {
                              sfprintf(sfstderr, "\n%s:\n", sp->name);
                              for (lp = sp->local; lp; lp = lp->next)
                                    sfprintf(sfstderr, " %15s=%s [%s]\n", lp->oldv->name, lp->newv.value, lp->oldv->value);
                        }
                  }
                  else if (streq(s, "variables"))
                  {
                        c = 1;
                        state.vardump = 1;
                        dump(sfstderr, 0);
                        state.vardump = 0;
                  }
                  else if (streq(s, "view"))
                  {
                        if (state.maxview)
                        {
                              sfprintf(sfstderr, "\n");
                              for (c = 0; c <= state.maxview; c++)
                                    sfprintf(sfstderr, "[%d] %2d %s\n", c, state.view[c].pathlen, state.view[c].path);
                              c = 1;
                        }
                  }
                  else
                        error(1, "%s: options are {blocked,buckets,hash,jobs,nametype,rules,stack,variables,view}", s);
            }
            else
                  while (s)
                  {
                        if (r = getrule(s))
                        {
                              c = 1;
                              dumprule(sfstderr, r);
                        }
                        if (v = getvar(s))
                        {
                              c = 1;
                              sfprintf(sfstderr, "\n");
                              dumpvar(sfstderr, v);
                              if (r = staterule(VAR, NiL, s, 0))
                                    dumprule(sfstderr, r);
                        }
                        s = getarg(&rhs, NiL);
                  }
            if (c)
                  sfprintf(sfstderr, "\n");
            return;
      }

      /*
       * construct the prerequsite list and attributes
       */

      x = 0;
      zero(clr);
      zero(set);
      set.op = op;
      if (!*rhs)
            set.op |= A_target;
      p = q = 0;
      while (s = getarg(&rhs, &set.op))
      {
            /*
             * <ATTRNAME><attribute> names (and sets) the attribute
             * <ATTRSET><attribute> sets the attribute
             * <ATTRCLEAR><attribute> clears the attribute
             */

            if (((c = *s) == ATTRSET || c == ATTRCLEAR) && *(s + 1))
            {
                  *s = ATTRNAME;
                  r = getrule(s);
                  *s = c;
                  if (!r)
                        r = getrule(s + 1);
                  if (!r || !(r->property & P_attribute))
                  {
                        if (r)
                        {
                              Flags_t           m = 0;

                              /*
                               * user controlled dynamic
                               * staterule attributes
                               */

                              if (r == internal.entries)
                                    m = D_entries;
                              else if (r == internal.member)
                                    m = D_member;
                              else if (r == internal.regular)
                                    m = D_regular;
                              else if (r == internal.scanned)
                                    m = D_scanned;
                              if (m)
                              {
                                    if (c == ATTRCLEAR)
                                    {
                                          set.op |= A_negate;
                                          att = &clr;
                                    }
                                    else
                                          att = &set;
                                    att->rule.dynamic |= m;
                                    continue;
                              }
                        }
                        r = makerule(s);
                  }
            }
            else
                  r = makerule(s);
            if (r->property & P_attribute)
            {
                  if (c == ATTRCLEAR)
                  {
                        set.op |= A_negate;
                        att = &clr;
                  }
                  else
                        att = &set;

                  /*
                   * assertion attributes
                   */

                  if (r == internal.clear)
                        att->op |= A_clear;
                  else if (r == internal.copy)
                        att->op |= A_copy;
                  else if (r == internal.delete)
                        att->op |= A_delete;
                  else if (r == internal.insert)
                        att->op |= A_insert;
                  else if (r == internal.null)
                        att->op |= A_null;
                  else if (r == internal.special)
                        att->op |= A_special;

                  /*
                   * attributes not propagated by merge()
                   *
                   * NOTE: internal.make->make is cleared in immediate()
                   */

                  else if (r == internal.attribute)
                        att->rule.property |= P_attribute;
                  else if (r == internal.immediate)
                        att->rule.property |= P_immediate;
                  else if (r == internal.make)
                        att->rule.property |= P_make;
                  else if (r == internal.op)
                        att->rule.property |= P_operator;
                  else if (r == internal.readonly)
                        att->rule.property |= P_readonly;
                  else if (r == internal.scan)
                        att->op |= A_scan;
                  else if (r == internal.semaphore)
                  {
                        if (att->rule.semaphore < UCHAR_MAX)
                              att->rule.semaphore++;
                        else
                              error(1, "%s: maximum semaphore count is %d", r->name, UCHAR_MAX - 1);
                  }
                  else if (r == internal.state)
                        att->rule.property |= P_state;
                  else if (r == internal.target)
                        att->rule.property |= P_target;
                  else if (r == internal.use)
                        att->rule.property |= P_use;

                  /*
                   * merge() handles the rest
                   */

                  else
                        merge(r, &att->rule, MERGE_ATTR|MERGE_FORCE);
            }
            else
            {
                  if ((set.op & (A_metarule|A_special)) == A_metarule || !*s)
                  {
                        if (!x)
                              x = r;
                        else if (!*s)
                              error(1, "multiple prerequisite patterns in metarule assertion");
                        else
                        {
                              while (c = *s++)
                              {
                                    if (c == '%')
                                          sfputr(internal.tmp, "$(%)", -1);
                                    else
                                          sfputc(internal.tmp, c);
                              }
                              r = makerule(sfstruse(internal.tmp));
                        }
                  }
                  else if (set.op & A_scope)
                  {
                        r->dynamic |= D_scope;
                        set.rule.dynamic |= D_hasscope;
                        if (state.user <= 1 && state.reading && state.makefile && (s = strchr(r->name, '=')) && *(s + 1) == '=')
                        {
                              *s = 0;
                              if (nametype(r->name, NiL) & NAME_identifier)
                                    setvar(r->name, NiL, V_scan);
                              *s = '=';
                        }
                  }
                  if (!(set.rule.dynamic & D_dynamic) && !(r->dynamic & D_scope) && isdynamic(r->name))
                        set.rule.dynamic |= D_dynamic;
                  if (r->property & P_use)
                        merge(r, &set.rule, MERGE_ATTR);
                  if (!p)
                        p = q = cons(r, NiL);
                  else
                        q = q->next = cons(r, NiL);
                  if (!(r->dynamic & D_scope))
                        set.op |= A_target;
            }
      }
      prereqs = p;
      if (*act || (set.op & (A_null|A_target)) || (set.rule.property & (P_make|P_local)) == (P_make|P_local))
            set.rule.property |= P_target;
      joint = (set.rule.property & P_joint) ? internal.joint : 0;

      /*
       * assert each target
       */

      name = getarg(&lhs, &set.op);
      while (name)
      {
            r = makerule(name);
            if (joint)
            {
                  if (joint == internal.joint)
                  {
                        joint = catrule(internal.joint->name, ".", name, 1);
                        joint->property |= P_joint|P_readonly|P_virtual;
                        jointail = joint->prereqs = cons(r, NiL);
                  }
                  else
                        jointail = jointail->next = cons(r, NiL);
            }
            if ((set.op & (A_metarule|A_special)) == A_metarule)
            {
                  Rule_t*           in;
                  Rule_t*           out;

                  in = 0;
                  if (*name == ATTRNAME)
                  {
                        for (s = name + 1; istype(*s, C_ID1|C_ID2); s++);
                        if (*s == ATTRNAME && s > (name + 1) && (c = *++s))
                        {
                              *s = 0;
                              in = getrule(name);
                              *s = c;
                        }
                  }
                  if (in)
                  {
                        /*
                         * pattern association rule
                         */

                        if (*s != '%' || *(s + 1) != ATTRNAME || !(x = getrule(s + 1)) || !(x->property & P_attribute))
                              x = makerule(s);
                        addprereq(in, x, ((set.op & A_insert) || *(s + strlen(s) - 1) != '%') ? PREREQ_INSERT : PREREQ_APPEND);
                        if (set.op & A_negate)
                        {
                              *name = ATTRCLEAR;
                              merge(&clr.rule, makerule(name), MERGE_ATTR|MERGE_FORCE);
                              *name = ATTRNAME;
                        }
                        name = getarg(&lhs, &set.op);
                  }
                  else
                  {
                        /*
                         * metarule assertion
                         */

                        for (p = prereqs, prereqs = q = 0; p; q = p, p = p->next)
                        {
                              if (p->rule == x)
                              {
                                    /*
                                     * rhs pattern
                                     */

                                    in = p->rule;
                                    if (q)
                                          q->next = p->next;
                              }
                              else if (!prereqs)
                                    prereqs = p;
                        }
                        c = ((set.op & A_clear) && !prereqs && !*act) ? PREREQ_DELETE : PREREQ_APPEND;
                        out = *(r->name + 1) ? r : 0;

                        /*
                         * update the metarule intermediate prerequisite graph
                         */

                        if (in)
                        {
                              r = metarule(in->name, r->name, 1);
                              if (out)
                              {
                                    metaclose(in, out, c);
                                    if (name = getarg(&lhs, &set.op))
                                    {
                                          addprereq(metainfo('S', in->name, out->name, 1), out, c);
                                          do
                                          {
                                                x = makerule(name);
                                                addprereq(metainfo('P', in->name, x->name, 1), out, c);
                                                addprereq(metainfo('S', in->name, out->name, 1), x, c);
                                                metaclose(in, x, c);
                                          } while (name = getarg(&lhs, &set.op));
                                    }
                              }
                              else
                                    addprereq(metainfo((set.rule.property & P_terminal) ? 'T' : 'N', NiL, NiL, 1), in, c);
                        }
                        else
                        {
                              if (out && c != PREREQ_DELETE)
                                    addprereq(internal.metarule, out, PREREQ_LENGTH);
                              name = getarg(&lhs, &set.op);
                        }
                  }
            }
            else
            {
                  name = getarg(&lhs, &set.op);
                  if (!internal.main->prereqs && !state.global && !(set.rule.property & P_operator) && !(set.op & A_special) && !special(r) && !special(&set.rule))
                  {
                        internal.main->prereqs = cons(r, NiL);
                        internal.main->dynamic &= ~D_compiled;
                  }
            }
            if ((r->property & P_readonly) || (r->property & P_staterule) && !istype(*(r->name + 1), C_ID1))
            {
                  if (r == internal.readonly)
                        continue; /* drop this in 2000 */
                  if (pp->fp != sfstdin)
                  {
                        error(2, "%s: %s atom cannot appear as target", r->name, (r->property & P_readonly) ? "readonly" : "staterule");
                        continue;
                  }
                  else if (r->property & P_readonly)
                        error(1, "%s: modifying readonly atom", r->name);
            }
            if (!((r->property|set.rule.property) & P_immediate) && (r->status == UPDATE || r->status == MAKING))
            {
                  if (*act || (set.op & A_null))
                  {
                        error(2, "%s: cannot reset active target action", r->name);
                        continue;
                  }
                  if (set.op & (A_clear|A_copy|A_delete|A_insert))
                  {
                        error(2, "%s: cannot reorder active target prerequisites", r->name);
                        continue;
                  }
                  isactive = 1;
            }
            else
                  isactive = 0;
            if (set.op & (A_clear|A_copy))
            {
                  int   dynamic;
                  int   property;

                  if (r->property & P_attribute)
                  {
                        error(2, "%s: atom cannot be cleared", r->name);
                        continue;
                  }
                  dynamic = r->dynamic & (D_compiled);
                  property = r->property & (P_state|P_staterule|P_statevar);
                  if ((r->property & P_metarule) && (prereqs || *act || (set.op & A_null)))
                        property |= r->property & (P_metarule|P_use);
                  s = r->uname && !(r->property & P_state) ? r->uname : r->name;
                  if (!(x = (r->property & P_state) ? rulestate(r, 0) : staterule(RULE, r, NiL, 0)) || r->prereqs != x->prereqs)
                        freelist(r->prereqs);
                  if (set.op & A_copy)
                  {
                        if (!prereqs || prereqs->next)
                        {
                              error(2, "%s: 1-1 copy only", r->name);
                              continue;
                        }
                        x = prereqs->rule;
                        *r = *x;
                        r->name = s;
                        r->uname = 0;
                        r->prereqs = listcopy(x->prereqs);
                        r->dynamic &= ~D_cached;
                        r->dynamic |= dynamic;
                        r->property |= property;
                        continue;
                  }
                  zero(*r);
                  r->name = s;
                  r->dynamic = dynamic;
                  r->property = property;
                  if (r->property & P_state)
                        state.savestate = 1;
            }
            if (set.op & A_delete)
            {
                  for (p = prereqs; p; p = p->next)
                        addprereq(r, p->rule, PREREQ_DELETE);
                  negate(&set.rule, r);
                  r->dynamic &= ~D_cached;
                  continue;
            }
            if (set.op & A_null)
                  r->action = null;
            if (prereqs)
            {
                  p = name ? listcopy(prereqs) : prereqs;
                  if ((set.op & A_insert) && (r->property & (P_joint|P_target)) == (P_joint|P_target))
                        r->prereqs->next = append(p, r->prereqs->next);
                  else
                  {
                        if (isactive && (set.rule.dynamic & D_dynamic))
                        {
                              r->dynamic |= D_dynamic;
                              q = r->prereqs;
                              r->prereqs = p;
                              dynamic(r);
                              p = r->prereqs;
                              r->prereqs = q;
                        }
                        r->prereqs = (set.op & A_insert) ? append(p, r->prereqs) : append(r->prereqs, p);
                  }
                  remdup(r->prereqs);
                  if (r->property & P_state)
                        state.savestate = 1;
            }

            /*
             * check action
             */

            if (*act)
            {
                  if (!(s = r->action) || !streq(act, s))
                        r->action = strdup(act); /* XXX: possible leak */
                  if (s && r->action != s && !state.user && !(set.rule.property & P_operator) && !(set.op & A_special) && !special(r) && !special(&set.rule))
                        error(1, "multiple actions for %s", r->name);
            }

            /*
             * assign attributes
             */

            merge(&set.rule, r, MERGE_ATTR|MERGE_FORCE);
            if (set.op & A_negate)
                  negate(&clr.rule, r);

            /*
             * attributes not handled by merge()
             */

            if (!isactive && ((set.rule.dynamic & D_dynamic) || isdynamic(r->name)))
                  r->dynamic |= D_dynamic;
            if (((set.rule.property|clr.rule.property) & P_functional) && !(r->property & P_state) && ((v = getvar(r->name)) || (v = setvar(r->name, null, 0))))
            {
                  if (set.rule.property & P_functional)
                        v->property |= V_functional;
                  else
                        v->property &= ~V_functional;
            }
            if (set.rule.property & P_immediate)
                  r->property |= (P_ignore|P_immediate);
            if (set.rule.property & P_operator)
            {
                  s = r->name;
                  if (*s == ':' && istype(*++s, C_ID1))
                        while (istype(*++s, C_ID2));
                  if (*s == ':' && !*++s)
                        r->property |= P_operator;
                  else
                        error(2, "%s: invalid operator name", r->name);
            }
            if (set.rule.property & P_readonly)
                  r->property |= P_readonly;
            if (set.rule.semaphore)
                  r->semaphore = set.rule.semaphore + 1;
            if ((set.rule.property & P_target) && !((clr.rule.property | r->property) & P_target))
            {
                  r->property |= P_target;
                  if (state.targetcontext && (s = strrchr(r->name, '/')))
                        makerule(s + 1)->dynamic |= D_context;
            }
            if ((set.rule.property & P_use) && (!(r->property & P_attribute) || !r->attribute))
                  r->property |= P_use;

            /*
             * user controlled dynamic staterule attributes
             */

            if ((set.rule.dynamic | clr.rule.dynamic) & ~D_dynamic)
            {
                  r->dynamic |= (set.rule.dynamic & ~D_dynamic);
                  r->dynamic &= ~(clr.rule.dynamic & ~D_dynamic);
                  if (x = staterule(RULE, r, NiL, 0))
                  {
                        x->dynamic |= (set.rule.dynamic & ~D_dynamic);
                        x->dynamic &= ~(clr.rule.dynamic & ~D_dynamic);
                        if (clr.rule.dynamic & D_scanned)
                              x->property |= P_force;
                  }
                  if (x = staterule(PREREQS, r, NiL, 0))
                  {
                        x->dynamic |= (set.rule.dynamic & ~D_dynamic);
                        x->dynamic &= ~(clr.rule.dynamic & ~D_dynamic);
                        if (clr.rule.dynamic & D_scanned)
                              x->property |= P_force;
                  }
            }

            /*
             * these are done after attributes have been assigned
             */

            if ((set.rule.property & P_attribute) && !(r->property & P_attribute))
            {
                  r->property |= P_attribute;
                  if (!(r->property & P_use))
                  {
                        if (internal.attribute->attribute << 1)
                        {
                              r->dynamic |= D_index;
                              r->attribute = internal.attribute->attribute;
                              internal.attribute->attribute <<= 1;
                              addprereq(internal.attribute, r, PREREQ_APPEND);
                        }
                        else
                              error(1, "%s: too many named attributes", r->name);
                  }
            }
            if (joint)
                  r->prereqs = cons(joint, r->prereqs);
            if ((set.op & A_scan) && !r->scan)
            {
                  if (internal.scan->scan == SCAN_MAX)
                        error(1, "%s: too many scan strategies", r->name);
                  else
                  {
                        r->dynamic |= D_index;
                        r->property |= P_attribute;
                        r->scan = internal.scan->scan++;
                        addprereq(internal.scan, r, PREREQ_APPEND);
                  }
            }
            if (!state.init && !state.readonly && (!state.op && state.reading || !(r->property & P_immediate)))
                  r->dynamic &= ~D_compiled;
            r->dynamic &= ~D_cached;

            /*
             * do immediate actions right away
             */

            if (r->property & P_immediate)
                  immediate(r);
      }
}

/*
 * parse an assignment statement
 */

static void
assignment(char* lhs, int op, char* rhs)
{
      register Rule_t*  r;
      register char*          s;
      register int            n;
      Var_t*                  v;

      if (internal.assign_p->prereqs && (r = associate(internal.assign_p, NiL, lhs, NiL)) && r->prereqs && (r = r->prereqs->rule) && (r->property & P_operator) && !r->uname)
      {
            s = r->uname = r->name;
            r->name = (op & OP_APPEND) ? "+=" : (op & OP_AUXILIARY) ? "&=" : (op & OP_STATE) ? "==" : "=";
            apply(r, lhs, rhs, NiL, CO_ALWAYS|CO_LOCAL|CO_URGENT);
            r->name = s;
            r->uname = 0;
            return;
      }
      debug((-6, "assignment: lhs=`%s' %s%srhs=`%-.1024s'", lhs, (op & OP_APPEND) ? "[append] " : null, (op & OP_STATE) ? "[state] " : null, rhs));
      if (!(s = getarg(&lhs, NiL)))
            error(1, "variable name missing in assignment");
      else
      {
            if (getarg(&lhs, NiL))
                  error(1, "only one variable per assignment");
            n = 0;
            if (op & OP_APPEND)
                  n |= V_append;
            if (op & OP_AUXILIARY)
                  n |= V_auxiliary;
            if (op & OP_STATE)
                  n |= V_scan;
            if (pp->scoped)
            {
                  declare(s, NiL, n|V_scope);
                  v = setvar(s, rhs, n);
                  if (state.localview && (!(r = staterule(VAR, NiL, s, 0)) || !(r->property & P_parameter)))
                  {
                        Sfio_t*     tmp;

                        tmp = sfstropen();
                        expand(tmp, rhs);
                        localvar(NiL, v, sfstruse(tmp), V_local_D);
                        sfstrclose(tmp);
                  }
            }
            else
                  setvar(s, rhs, n);
      }
}

/*
 * invoke or verify rules s
 */

void
rules(char* s)
{
      register char*    t;
      register char*    e;

      if (e = strchr(s, '\n'))
            *e = 0;
      sfputr(internal.tmp, s, 0);
      if (e)
            *e = '\n';
      s = sfstruse(internal.tmp);
      if (!(t = getarg(&s, NiL)))
            t = null;
      if (state.rules)
      {
            edit(internal.nam, t, DELETE, KEEP, DELETE);
            edit(internal.wrk, state.rules, DELETE, KEEP, DELETE);
            if (strcmp(sfstruse(internal.nam), sfstruse(internal.wrk)))
                  error(3, "%s: incompatible with current base rules %s", t, state.rules);
      }
      else if (t == null)
            state.rules = null;
      else
            state.rules = makerule(t)->name;
      if (t != null && (t = getarg(&s, NiL)))
            error(3, "%s: invalid base rule argument", t);
      state.explicitrules = 1;
}

/*
 * external PUSHLOCAL()
 */

void*
pushlocal(void)
{
      register Local_t* p;

      PUSHLOCAL(p);
      return (void*)pp->local;
}

/*
 * external POPLOCAL()
 * pos is return value of previous pushlocal()
 */

void
poplocal(void* pos)
{
      register Local_t* p;
      register Local_t* t;

      p = (Local_t*)pos;
      while (pp->local != p)
      {
            pp->local->bucket->value = (char*)pp->local->oldv;
            t = pp->local;
            pp->local = pp->local->next;
            freelocal(t);
      }
      POPLOCAL(p);
}

static long makeexpr(const char*, char**, void*);

/*
 * <var>
 * <var> = <expression>
 * [ <var> = ] <quote> ... <quote>
 */

static char*
nextarg(char* s, char** p, char** end, long* val)
{
      register char*    arg;
      register int      c;
      char*       var;
      char*       varend;
      char        buf[10];
      long        n;

      if ((c = *s) && c != MARK_QUOTE && c != '"')
      {
            if (!istype(*s, C_VARIABLE1))
                  error(3, "argument expected in expression [%s]", s);
            for (var = s++; istype(*s, C_VARIABLE2); s++);
            varend = s;
            while (isspace(*s))
                  s++;
            if (*s != '=' || *(s + 1) == '=')
            {
                  c = *varend;
                  *varend = 0;
                  arg = getval(var, VAL_PRIMARY|VAL_AUXILIARY);
                  *varend = c;
                  *p = s;

                  /*
                   * determine if in string or numeric context
                   */

                  if ((*s == '!' || *s == '=') && *(s + 1) == '=')
                  {
                        s++;
                        while (isspace(*++s));
                        if (*s == MARK_QUOTE)
                        {
                              *end = s;
                              return arg;
                        }
                  }
                  if (!*arg)
                        *val = 0;
                  else
                  {
                        *val = strtol(arg, &s, 0);
                        if (*s)
                              *val = 1;
                  }
                  return 0;
            }
            while (isspace(*++s));
      }
      else
            var = 0;
      if ((c = *s) == MARK_QUOTE)
      {
            for (arg = ++s; *s && *s != c; s++);
            *end = s;
            if (*s)
                  while (isspace(*++s));
      }
      else if (c == '"')
      {
            for (arg = ++s; *s && *s != c; s++)
                  if (*s == '\\' && *(s + 1))
                        s++;
            *end = s;
            if (*s)
                  while (isspace(*++s));
      }
      else if (var)
      {
            sfsprintf(arg = buf, sizeof(buf), "%ld", *val = strexpr(s, &s, makeexpr, NiL));
            end = 0;
      }
      else
            error(3, "string argument expected in expression");
      if (var)
      {
            c = *varend;
            *varend = 0;
            if (end)
            {
                  n = **end;
                  **end = 0;

                  /*
                   * XXX: this handles the symptom but not the bug
                   */

                  if (*arg == '"' && !*(arg + 1))
                        arg++;
            }
            debug((-6, "assignment: lhs=`%s' rhs=`%s'", var, arg));
            setvar(var, arg, 0);
            *varend = c;
            if (end)
                  **end = n;
      }
      *p = s;
      return end ? arg : 0;
}

/*
 * supplementary make expression evaluator for strexpr()
 *
 *    "..." == "..."          strmatch()
 *    "..." != "..."          strmatch()
 *    "..." <  "..."          strcoll()
 *    "..." <= "..."          strcoll()
 *    "..." >  "..."          strcoll()
 *    "..." >= "..."          strcoll()
 *    <var>             "<value>"
 *    <var> = "..."           "..."
 *    ( <var> = "..." ) "..."
 *    <var> = <expression>    <expression> ? "1" : ""
 *
 * NOTE: '"' translated to MARK_QUOTE by expr()
 */

static long
makeexpr(const char* cs, char** p, void* handle)
{
      char* s = (char*)cs;
      int   c;
      int   q;
      int   c1;
      int   c2;
      int   m1;
      int   m2;
      char* paren;
      char* s1;
      char* s2;
      char* e1;
      char* e2;
      long  n;

      NoP(handle);
      if (!s)
            error(3, "%s in expression", *p);
      else if (!(s1 = nextarg(s, &s, &e1, &n)))
            /* n == expression value */;
      else
      {
            if ((c = *s) == ')')
            {
                  paren = s;
                  e2 = s;
                  while (isspace(*++s));
            }
            else
                  paren = 0;
            if (!(c = *s) || c != '<' && c != '>' && (*(s + 1) != '=' || c != '!' && c != '='))
            {
                  n = e1 > s1;
                  if (paren)
                        s = paren;
            }
            else
            {
                  q = *++s != '=';
                  while (isspace(*++s));
                  if (!(s2 = nextarg(s, &s, &e2, &n)))
                        n = 0;
                  else
                  {
                        c1 = *e1;
                        *e1 = 0;
                        c2 = *e2;
                        *e2 = 0;
                        if (state.context)
                        {
                              if (*s1 == MARK_CONTEXT && *(e1 - 1) == MARK_CONTEXT)
                              {
                                    *(e1 - 1) = 0;
                                    m1 = 1;
                              }
                              else
                                    m1 = 0;
                              if (*s2 == MARK_CONTEXT && *(e2 - 1) == MARK_CONTEXT)
                              {
                                    *(e2 - 1) = 0;
                                    m2 = 1;
                              }
                              else
                                    m2 = 0;
                        }
                        switch (c)
                        {
                        case '>':
                              n = strcoll(s1, s2) >= q;
                              break;
                        case '<':
                              n = strcoll(s2, s1) >= q;
                              break;
                        case '!':
                              n = !strmatch(s1, s2);
                              break;
                        default:
                              n = strmatch(s1, s2);
                              break;
                        }
                        if (state.context)
                        {
                              if (m1)
                                    *(e1 - 1) = MARK_CONTEXT;
                              if (m2)
                                    *(e2 - 1) = MARK_CONTEXT;
                        }
                        *e1 = c1;
                        *e2 = c2;
                  }
            }
            if (paren)
            {
                  if (!*s)
                        s--;
                  *s = ')';
            }
      }
      *p = s;
      return n;
}

/*
 * expression evaluation on s using temporary string xp
 * '"' temporarily converted to MARK_QUOTE
 * s is first expanded
 */

long
expr(Sfio_t* xp, register char* s)
{
      register char*    t;
      register int      p;
      register char**   v;
      int         c;
      long        top;
      char*       restore[PARSEDEPTH];

      v = restore;
      t = s;
      p = 0;
      for (;;)
            switch (*t++)
            {
            case '(':
                  if (p)
                        p++;
                  break;
            case ')':
                  if (p)
                        p--;
                  break;
            case '"':
                  if (p <= 1)
                  {
                        p = !p;
                        if (v < &restore[elementsof(restore)])
                              *(*v++ = t - 1) = MARK_QUOTE;
                  }
                  break;
            case '\\':
                  if (*t++)
                        break;
                  /*FALLTHROUGH*/
            case 0:
            case '\n':
                  c = *--t;
                  *t = 0;
                  top = sfstrtell(xp);
                  expand(xp, s);
                  sfputc(xp, 0);
                  while (v > restore)
                        **--v = '"';
                  *t = c;
                  return strexpr(sfstrseek(xp, top, SEEK_SET), NiL, makeexpr, NiL);
            }
}

/*
 * error exit during interpreter()
 */

static void
exit_interpreter(int code)
{
      NoP(code);
      sfsync(sfstdout);
      sfsync(sfstderr);
      longjmp(state.resume.label, 1);
      finish(code);
}

/*
 * interactive query loop
 */

void
interpreter(char* msg)
{
      int         level;
      void        (*errexit)(int);
      Frame_t           frame;
      Label_t           resume;

      if (msg)
            error(0, "\n%s\n", msg);
      level = pp - &parsestack[0];
      errexit = error_info.exit;
      error_info.exit = exit_interpreter;
      zero(frame);
      frame.target = internal.query;
      frame.parent = state.frame;
      frame.previous = frame.target->active;
      state.frame = frame.target->active = &frame;
      state.keepgoing |= 2;
      resume = state.resume;
      if (setjmp(state.resume.label))
      {
            unparse(level);
            state.hold = 0;
            state.frame = frame.target->active = &frame;
            sfclrlock(sfstdin);
      }
      else
            state.interpreter++;
      parse(sfstdin, NiL, "query", NiL);
      state.interpreter--;
      state.resume = resume;
      state.keepgoing &= 1;
      frame.target->active = frame.previous;
      state.frame = frame.parent;
      sfclrlock(sfstdin);
      error_info.exit = errexit;
      if (msg)
            error(0, "\n");
}

/*
 * read and parse file fp or line buffer bp
 * non-zero returned if target to be updated
 */

int
parse(Sfio_t* fp, char* bp, char* name, Sfio_t* scoped)
{
      register int            op;
      register Local_t* lcl;
      char*             lhs;
      char*             rhs;
      char*             act;
      char*             alt;
      Rule_t*                 opr;
      Local_t*          olcl;
      Sfio_t*                 buf;
      Sfio_t*                 tmp;

      if (pp->newline)
            *pp->bp = '\n';
      else if (!pp->fp && pp->bp && !*pp->bp)
      {
            error(1, "parse: early pop");
            return 0;
      }
      PUSHLOCAL(lcl);
      if (++pp >= &parsestack[elementsof(parsestack)])
            error(3, "input nesting too deep");
      if ((pp->block = (pp - 1)->cp + 1) >= &constack[elementsof(constack)])
            error(3, "control block nesting too deep");
#if DEBUG
      message((fp ? -2 : -7, "reading %s", name));
#else
      if (fp)
            message((-2, "reading %s", name));
#endif

      /*
       * push the parse stack
       */

      if (pp->fp = fp)
      {
            pp->ip = sfstropen();
            pp->prompt = fp == sfstdin && isatty(sffileno(fp)) && isatty(sffileno(sfstderr));
            if (fp == sfstdin)
                  sfdcslow(fp);
      }
      else
      {
            pp->bp = bp;
            pp->prompt = 0;
      }
      pp->name = error_info.file = name;
      pp->line = error_info.line;
      error_info.line = 0;
      pp->argc = 0;
      pp->stashget = 0;
      pp->stashput = 0;
      pp->pushback = 0;
      pp->eval = 0;
      pp->indent = 0;
      pp->newline = 0;
      pp->splice = 0;
      pp->status = UPDATE;
      pp->cp = pp->block;
      pp->cp->flags = 0;
      if (pp->scoped = scoped)
            pp->local = (pp - 1)->local;

      /*
       * statement parse loop
       */

      buf = sfstropen();
      for (;;)
      {
            op = statement(buf, &lhs, &opr, &rhs, &act);
            if (trap() && !op)
                  continue;
            if (!op)
                  break;
            switch (op & OP_STATEMENT)
            {

            case OP_ASSERT:
                  assertion(lhs, opr, rhs, act, 0);
                  break;

            case OP_ASSIGN:
                  assignment(lhs, op, rhs);
                  break;

            case OP_EMPTY:
                  if (*lhs || *act)
                  {
                        tmp = sfstropen();
                        if (*lhs)
                        {
                              expand(tmp, lhs);
                              rhs = lhs = sfstruse(tmp);
                              sfputc(internal.nam, '.');
                              while ((op = *rhs++) && !isspace(op))
                                    sfputc(internal.nam, islower(op) ? toupper(op) : isupper(op) ? tolower(op) : op);
                              rhs--;
                              alt = sfstruse(internal.nam);
                        }
                        else
                        {
                              sfputr(tmp, internal.always->name, -1);
                              rhs = lhs = alt = sfstruse(tmp);
                        }
                        if (!(opr = getrule(alt)) || !(opr->property & (P_attribute|P_functional|P_immediate)))
                        {
                              if (pp->fp == sfstdin)
                              {
                                    if ((op = *++alt) == 'P' && (!*(alt + 1) || !strcmp(alt, "PRINT")))
                                    {
                                          while (isspace(*rhs))
                                                rhs++;
                                          sfputr(sfstdout, rhs, '\n');
                                          sfstrclose(tmp);
                                          break;
                                    }
                                    if (op == 'Q' && (!*(alt + 1) || !strcmp(alt, "QUIT")))
                                    {
                                          if (*rhs)
                                                finish(strtol(rhs, NiL, 0));
                                          sfstrclose(tmp);
                                          goto quit;
                                    }
                              }
                              else if (!*act)
                                    error(3, "no operator on line");
                              if (*act)
                              {
                                    if (*rhs)
                                    {
                                          *rhs++ = 0;
                                          while (isspace(*rhs))
                                                rhs++;
                                    }
                                    opr = 0;
                              }
                              else
                              {
                                    opr = internal.query;
                                    rhs = lhs;
                              }
                        }
                        if (opr)
                        {
                              if (opr->property & P_functional)
                              {
                                    while (isspace(*rhs))
                                          rhs++;
                                    maketop(opr, 0, rhs);
                                    sfstrclose(tmp);
                                    break;
                              }
                              sfputr(internal.nam, opr->name, -1);
                              alt = sfstruse(internal.nam);
                              if (opr->property & P_immediate)
                                    lhs = alt;
                              else
                              {
                                    lhs = rhs;
                                    rhs = alt;
                              }
                        }
                        assertion(lhs, NiL, rhs, act, A_special);
                        sfstrclose(tmp);
                  }
                  break;

#if DEBUG
            default:
                  error(PANIC, "invalid statement type %d", op);
#endif
            }
      }
 quit:
      sfstrclose(buf);

      /*
       * pop the parse stack
       */

      if (pp->cp > pp->block)
            error(3, "missing %d closing end statement%s", pp->cp - pp->block, pp->cp - pp->block == 1 ? null : "s");
      if (pp->fp)
            sfstrclose(pp->ip);
      else if (pp->newline)
            *pp->bp = '\n';
      if (scoped)
      {
            (pp - 1)->local = pp->local;
            pp->local = 0;
      }
      else if (lcl = pp->local)
      {
            pp->local = 0;
            while (lcl)
            {
                  lcl->bucket->value = (char*)lcl->oldv;
                  olcl = lcl;
                  lcl = lcl->next;
                  freelocal(olcl);
            }
      }
      error_info.line = pp->line;
      pp--;
      if (pp->newline)
            *pp->bp = 0;
      POPLOCAL(lcl);
      error_info.file = pp->name;
#if DEBUG
      message((fp ? -2 : -7, "popping %s", name));
#else
      if (fp)
            message((-2, "popping %s", name));
#endif
      return (pp + 1)->status;
}

char*
parsefile(void)
{
      register Parseinfo_t*   pi;

      if (state.loading)
            return state.loading;
      for (pi = pp; pi >= &parsestack[0]; pi--)
            if (pi->fp)
                  return pi->name;
      return error_info.file;
}

Generated by  Doxygen 1.6.0   Back to index