/* $RCSfile: toke.c,v $$Revision: 4.0.1.9 $$Date: 1993/02/05 19:48:43 $
 *
 *    Copyright (c) 1991, Larry Wall
 *
 *    You may distribute under the terms of either the GNU General Public
 *    License or the Artistic License, as specified in the README file.
 *
 * $Log:	toke.c,v $
 * Revision 4.0.1.9  1993/02/05  19:48:43  lwall
 * patch36: now detects ambiguous use of filetest operators as well as unary
 * patch36: fixed ambiguity on - within tr///
 *
 * Revision 4.0.1.8  92/06/23  12:33:45  lwall
 * patch35: bad interaction between backslash and hyphen in tr///
 * 
 * Revision 4.0.1.7  92/06/11  21:16:30  lwall
 * patch34: expectterm incorrectly set to indicate start of program or block
 * 
 * Revision 4.0.1.6  92/06/08  16:03:49  lwall
 * patch20: an EXPR may now start with a bareword
 * patch20: print $fh EXPR can now expect term rather than operator in EXPR
 * patch20: added ... as variant on ..
 * patch20: new warning on spurious backslash
 * patch20: new warning on missing $ for foreach variable
 * patch20: "foo"x1024 now legal without space after x
 * patch20: new warning on print accidentally used as function
 * patch20: tr/stuff// wasn't working right
 * patch20: 2. now eats the dot
 * patch20: <@ARGV> now notices @ARGV
 * patch20: tr/// now lets you say \-
 * 
 * Revision 4.0.1.5  91/11/11  16:45:51  lwall
 * patch19: default arg for shift was wrong after first subroutine definition
 * 
 * Revision 4.0.1.4  91/11/05  19:02:48  lwall
 * patch11: \x and \c were subject to double interpretation in regexps
 * patch11: prepared for ctype implementations that don't define isascii()
 * patch11: nested list operators could miscount parens
 * patch11: once-thru blocks didn't display right in the debugger
 * patch11: sort eval "whatever" didn't work
 * patch11: underscore is now allowed within literal octal and hex numbers
 * 
 * Revision 4.0.1.3  91/06/10  01:32:26  lwall
 * patch10: m'$foo' now treats string as single quoted
 * patch10: certain pattern optimizations were botched
 * 
 * Revision 4.0.1.2  91/06/07  12:05:56  lwall
 * patch4: new copyright notice
 * patch4: debugger lost track of lines in eval
 * patch4: //o and s///o now optimize themselves fully at runtime
 * patch4: added global modifier for pattern matches
 * 
 * Revision 4.0.1.1  91/04/12  09:18:18  lwall
 * patch1: perl -de "print" wouldn't stop at the first statement
 * 
 * Revision 4.0  91/03/20  01:42:14  lwall
 * 4.0 baseline.
 * 
 */

#include "EXTERN.h"
#include "perl.h"
#include "perly.h"

static void set_csh();

#ifdef I_FCNTL
#include <fcntl.h>
#endif
#ifdef I_SYS_FILE
#include <sys/file.h>
#endif

#ifdef f_next
#undef f_next
#endif

/* which backslash sequences to keep in m// or s// */

static char *patleave = "\\.^$@dDwWsSbB+*?|()-nrtfeaxc0123456789[{]}";

char *reparse;		/* if non-null, scanident found ${foo[$bar]} */

void checkcomma();

#ifdef CLINE
#undef CLINE
#endif
#define CLINE (cmdline = (curcmd->c_line < cmdline ? curcmd->c_line : cmdline))

#ifdef atarist
#define PERL_META(c) ((c) | 128)
#else
#define META(c) ((c) | 128)
#endif

#define RETURN(retval) return (bufptr = s,(int)retval)
#define OPERATOR(retval) return (expectterm = TRUE,bufptr = s,(int)retval)
#define TERM(retval) return (CLINE, expectterm = FALSE,bufptr = s,(int)retval)
#define LOOPX(f) return(yylval.ival=f,expectterm = FALSE,bufptr = s,(int)LOOPEX)
#define FTST(f) return(yylval.ival=f,expectterm = TRUE,bufptr = s,(int)FILETEST)
#define FUN0(f) return(yylval.ival = f,expectterm = FALSE,bufptr = s,(int)FUNC0)
#define FUN1(f) return(yylval.ival = f,expectterm = FALSE,bufptr = s,(int)FUNC1)
#define FUN2(f) return(yylval.ival = f,expectterm = FALSE,bufptr = s,(int)FUNC2)
#define FUN2x(f) return(yylval.ival = f,expectterm = FALSE,bufptr = s,(int)FUNC2x)
#define FUN3(f) return(yylval.ival = f,expectterm = FALSE,bufptr = s,(int)FUNC3)
#define FUN4(f) return(yylval.ival = f,expectterm = FALSE,bufptr = s,(int)FUNC4)
#define FUN5(f) return(yylval.ival = f,expectterm = FALSE,bufptr = s,(int)FUNC5)
#define FL(f) return(yylval.ival=f,expectterm = FALSE,bufptr = s,(int)FLIST)
#define FL2(f) return(yylval.ival=f,expectterm = FALSE,bufptr = s,(int)FLIST2)
#define HFUN(f) return(yylval.ival=f,expectterm = TRUE,bufptr = s,(int)HSHFUN)
#define HFUN3(f) return(yylval.ival=f,expectterm = FALSE,bufptr = s,(int)HSHFUN3)
#define LFUN(f) return(yylval.ival=f,expectterm = TRUE,bufptr = s,(int)LVALFUN)
#define AOP(f) return(yylval.ival=f,expectterm = TRUE,bufptr = s,(int)ADDOP)
#define MOP(f) return(yylval.ival=f,expectterm = TRUE,bufptr = s,(int)MULOP)
#define EOP(f) return(yylval.ival=f,expectterm = TRUE,bufptr = s,(int)EQOP)
#define ROP(f) return(yylval.ival=f,expectterm = TRUE,bufptr = s,(int)RELOP)
#define FOP(f) return(yylval.ival=f,expectterm = FALSE,bufptr = s,(int)FILOP)
#define FOP2(f) return(yylval.ival=f,expectterm = FALSE,bufptr = s,(int)FILOP2)
#define FOP3(f) return(yylval.ival=f,expectterm = FALSE,bufptr = s,(int)FILOP3)
#define FOP4(f) return(yylval.ival=f,expectterm = FALSE,bufptr = s,(int)FILOP4)
#define FOP22(f) return(yylval.ival=f,expectterm = FALSE,bufptr = s,(int)FILOP22)
#define FOP25(f) return(yylval.ival=f,expectterm = FALSE,bufptr = s,(int)FILOP25)

static char *last_uni;

/* This bit of chicanery makes a unary function followed by
 * a parenthesis into a function with one argument, highest precedence.
 */
#define UNI(f) return(yylval.ival = f, \
	expectterm = TRUE, \
	bufptr = s, \
	last_uni = oldbufptr, \
	(*s == '(' || (s = skipspace(s), *s == '(') ? (int)FUNC1 : (int)UNIOP) )

/* This does similarly for list operators, merely by pretending that the
 * paren came before the listop rather than after.
 */
#ifdef atarist
#define LOP(f) return(CLINE, *s == '(' || (s = skipspace(s), *s == '(') ? \
	(*s = (char) PERL_META('('), bufptr = oldbufptr, '(') : \
	(yylval.ival=f,expectterm = TRUE,bufptr = s,(int)LISTOP))
#else
#define LOP(f) return(CLINE, *s == '(' || (s = skipspace(s), *s == '(') ? \
	(*s = (char) META('('), bufptr = oldbufptr, '(') : \
	(yylval.ival=f,expectterm = TRUE,bufptr = s,(int)LISTOP))
#endif
/* grandfather return to old style */
#define OLDLOP(f) return(yylval.ival=f,expectterm = TRUE,bufptr = s,(int)LISTOP)

char *
skipspace(s)
register char *s;
{
    while (s < bufend && isSPACE(*s))
	s++;
    return s;
}

void
check_uni() {
    char *s;
    char ch;

    if (oldoldbufptr != last_uni)
	return;
    while (isSPACE(*last_uni))
	last_uni++;
    for (s = last_uni; isALNUM(*s) || *s == '-'; s++) ;
    ch = *s;
    *s = '\0';
    warn("Warning: Use of \"%s\" without parens is ambiguous", last_uni);
    *s = ch;
}

#ifdef CRIPPLED_CC

#undef UNI
#undef LOP
#define UNI(f) return uni(f,s)
#define LOP(f) return lop(f,s)

int
uni(f,s)
int f;
char *s;
{
    yylval.ival = f;
    expectterm = TRUE;
    bufptr = s;
    last_uni = oldbufptr;
    if (*s == '(')
	return FUNC1;
    s = skipspace(s);
    if (*s == '(')
	return FUNC1;
    else
	return UNIOP;
}

int
lop(f,s)
int f;
char *s;
{
    CLINE;
    if (*s != '(')
	s = skipspace(s);
    if (*s == '(') {
#ifdef atarist
	*s = PERL_META('(');
#else
	*s = META('(');
#endif
	bufptr = oldbufptr;
	return '(';
    }
    else {
	yylval.ival=f;
	expectterm = TRUE;
	bufptr = s;
	return LISTOP;
    }
}

#endif /* CRIPPLED_CC */

int
yylex()
{
    register char *s = bufptr;
    register char *d;
    register int tmp;
    static bool in_format = FALSE;
    static bool firstline = TRUE;
    extern int yychar;		/* last token */

    oldoldbufptr = oldbufptr;
    oldbufptr = s;

  retry:
#ifdef YYDEBUG
    if (debug & 1)
	if (index(s,'\n'))
	    fprintf(stderr,"Tokener at %s",s);
	else
	    fprintf(stderr,"Tokener at %s\n",s);
#endif
#ifdef BADSWITCH
    if (*s & 128) {
	if ((*s & 127) == '(') {
	    *s++ = '(';
	    oldbufptr = s;
	}
	else if ((*s & 127) == '}') {
	    *s++ = '}';
	    RETURN('}');
	}
	else
	    warn("Unrecognized character \\%03o ignored", *s++ & 255);
	goto retry;
    }
#endif
    switch (*s) {
    default:
	if ((*s & 127) == '(') {
	    *s++ = '(';
	    oldbufptr = s;
	}
	else if ((*s & 127) == '}') {
	    *s++ = '}';
	    RETURN('}');
	}
	else
	    warn("Unrecognized character \\%03o ignored", *s++ & 255);
	goto retry;
    case 4:
    case 26:
	goto fake_eof;			/* emulate EOF on ^D or ^Z */
    case 0:
	if (!rsfp)
	    RETURN(0);
	if (s++ < bufend)
	    goto retry;			/* ignore stray nulls */
	last_uni = 0;
	if (firstline) {
	    firstline = FALSE;
	    if (minus_n || minus_p || perldb) {
		str_set(linestr,"");
		if (perldb) {
		    char *getenv();
		    char *pdb = getenv("PERLDB");

		    str_cat(linestr, pdb ? pdb : "require 'perldb.pl'");
		    str_cat(linestr, ";");
		}
		if (minus_n || minus_p) {
		    str_cat(linestr,"line: while (<>) {");
		    if (minus_l)
			str_cat(linestr,"chop;");
		    if (minus_a)
			str_cat(linestr,"@F=split(' ');");
		}
		oldoldbufptr = oldbufptr = s = str_get(linestr);
		bufend = linestr->str_ptr + linestr->str_cur;
		goto retry;
	    }
	}
	if (in_format) {
	    bufptr = bufend;
	    yylval.formval = load_format();
	    in_format = FALSE;
	    oldoldbufptr = oldbufptr = s = str_get(linestr) + 1;
	    bufend = linestr->str_ptr + linestr->str_cur;
	    OPERATOR(FORMLIST);
	}
	curcmd->c_line++;
#ifdef CRYPTSCRIPT
	cryptswitch();
#endif /* CRYPTSCRIPT */
	do {
	    if ((s = str_gets(linestr, rsfp, 0)) == Nullch) {
	      fake_eof:
		if (rsfp) {
		    if (preprocess)
			(void)mypclose(rsfp);
		    else if ((FILE*)rsfp == stdin)
			clearerr(stdin);
		    else
			(void)fclose(rsfp);
		    rsfp = Nullfp;
		}
		if (minus_n || minus_p) {
		    str_set(linestr,minus_p ? ";}continue{print" : "");
		    str_cat(linestr,";}");
		    oldoldbufptr = oldbufptr = s = str_get(linestr);
		    bufend = linestr->str_ptr + linestr->str_cur;
		    minus_n = minus_p = 0;
		    goto retry;
		}
		oldoldbufptr = oldbufptr = s = str_get(linestr);
		str_set(linestr,"");
		RETURN(';');	/* not infinite loop because rsfp is NULL now */
	    }
	    if (doextract && *linestr->str_ptr == '#')
		doextract = FALSE;
	} while (doextract);
	oldoldbufptr = oldbufptr = bufptr = s;
	if (perldb) {
	    STR *str = Str_new(85,0);

	    str_sset(str,linestr);
	    astore(stab_xarray(curcmd->c_filestab),(int)curcmd->c_line,str);
	}
#ifdef DEBUG
	if (firstline) {
	    char *showinput();
	    s = showinput();
	}
#endif
	bufend = linestr->str_ptr + linestr->str_cur;
	if (curcmd->c_line == 1) {
	    if (*s == '#' && s[1] == '!') {
		if (!in_eval && !instr(s,"perl") && instr(origargv[0],"perl")) {
		    char **newargv;
		    char *cmd;

		    s += 2;
		    if (*s == ' ')
			s++;
		    cmd = s;
		    while (s < bufend && !isSPACE(*s))
			s++;
		    *s++ = '\0';
		    while (s < bufend && isSPACE(*s))
			s++;
		    if (s < bufend) {
			Newz(899,newargv,origargc+3,char*);
			newargv[1] = s;
			while (s < bufend && !isSPACE(*s))
			    s++;
			*s = '\0';
			Copy(origargv+1, newargv+2, origargc+1, char*);
		    }
		    else
			newargv = origargv;
		    newargv[0] = cmd;
		    execv(cmd,newargv);
		    fatal("Can't exec %s", cmd);
		}
	    }
	    else {
		while (s < bufend && isSPACE(*s))
		    s++;
		if (*s == ':')	/* for csh's that have to exec sh scripts */
		    s++;
	    }
	}
	goto retry;
    case ' ': case '\t': case '\f': case '\r': case 013:
	s++;
	goto retry;
    case '#':
	if (preprocess && s == str_get(linestr) &&
	       s[1] == ' ' && (isDIGIT(s[2]) || strnEQ(s+2,"line ",5)) ) {
	    while (*s && !isDIGIT(*s))
		s++;
	    curcmd->c_line = atoi(s)-1;
	    while (isDIGIT(*s))
		s++;
	    d = bufend;
	    while (s < d && isSPACE(*s)) s++;
	    s[strlen(s)-1] = '\0';	/* wipe out newline */
	    if (*s == '"') {
		s++;
		s[strlen(s)-1] = '\0';	/* wipe out trailing quote */
	    }
	    if (*s)
		curcmd->c_filestab = fstab(s);
	    else
		curcmd->c_filestab = fstab(origfilename);
	    oldoldbufptr = oldbufptr = s = str_get(linestr);
	}
	/* FALL THROUGH */
    case '\n':
	if (in_eval && !rsfp) {
	    d = bufend;
	    while (s < d && *s != '\n')
		s++;
	    if (s < d)
		s++;
	    if (in_format) {
		bufptr = s;
		yylval.formval = load_format();
		in_format = FALSE;
		oldoldbufptr = oldbufptr = s = bufptr + 1;
		TERM(FORMLIST);
	    }
	    curcmd->c_line++;
	}
	else {
	    *s = '\0';
	    bufend = s;
	}
	goto retry;
    case '-':
	if (s[1] && isALPHA(s[1]) && !isALPHA(s[2])) {
	    s++;
	    last_uni = oldbufptr;
	    switch (*s++) {
	    case 'r': FTST(O_FTEREAD);
	    case 'w': FTST(O_FTEWRITE);
	    case 'x': FTST(O_FTEEXEC);
	    case 'o': FTST(O_FTEOWNED);
	    case 'R': FTST(O_FTRREAD);
	    case 'W': FTST(O_FTRWRITE);
	    case 'X': FTST(O_FTREXEC);
	    case 'O': FTST(O_FTROWNED);
	    case 'e': FTST(O_FTIS);
	    case 'z': FTST(O_FTZERO);
	    case 's': FTST(O_FTSIZE);
	    case 'f': FTST(O_FTFILE);
	    case 'd': FTST(O_FTDIR);
	    case 'l': FTST(O_FTLINK);
	    case 'p': FTST(O_FTPIPE);
	    case 'S': FTST(O_FTSOCK);
	    case 'u': FTST(O_FTSUID);
	    case 'g': FTST(O_FTSGID);
	    case 'k': FTST(O_FTSVTX);
	    case 'b': FTST(O_FTBLK);
	    case 'c': FTST(O_FTCHR);
	    case 't': FTST(O_FTTTY);
	    case 'T': FTST(O_FTTEXT);
	    case 'B': FTST(O_FTBINARY);
	    case 'M': stabent("\024",TRUE); FTST(O_FTMTIME);
	    case 'A': stabent("\024",TRUE); FTST(O_FTATIME);
	    case 'C': stabent("\024",TRUE); FTST(O_FTCTIME);
	    default:
		s -= 2;
		break;
	    }
	}
	tmp = *s++;
	if (*s == tmp) {
	    s++;
	    RETURN(DEC);
	}
	if (expectterm) {
	    if (isSPACE(*s) || !isSPACE(*bufptr))
		check_uni();
	    OPERATOR('-');
	}
	else
	    AOP(O_SUBTRACT);
    case '+':
	tmp = *s++;
	if (*s == tmp) {
	    s++;
	    RETURN(INC);
	}
	if (expectterm) {
	    if (isSPACE(*s) || !isSPACE(*bufptr))
		check_uni();
	    OPERATOR('+');
	}
	else
	    AOP(O_ADD);

    case '*':
	if (expectterm) {
	    check_uni();
	    s = scanident(s,bufend,tokenbuf);
	    yylval.stabval = stabent(tokenbuf,TRUE);
	    TERM(STAR);
	}
	tmp = *s++;
	if (*s == tmp) {
	    s++;
	    OPERATOR(POW);
	}
	MOP(O_MULTIPLY);
    case '%':
	if (expectterm) {
	    if (!isALPHA(s[1]))
		check_uni();
	    s = scanident(s,bufend,tokenbuf);
	    yylval.stabval = hadd(stabent(tokenbuf,TRUE));
	    TERM(HSH);
	}
	s++;
	MOP(O_MODULO);

    case '^':
    case '~':
    case '(':
    case ',':
    case ':':
    case '[':
	tmp = *s++;
	OPERATOR(tmp);
    case '{':
	tmp = *s++;
	yylval.ival = curcmd->c_line;
	if (isSPACE(*s) || *s == '#')
	    cmdline = NOLINE;   /* invalidate current command line number */
	expectterm = 2;
	RETURN(tmp);
    case ';':
	if (curcmd->c_line < cmdline)
	    cmdline = curcmd->c_line;
	tmp = *s++;
	OPERATOR(tmp);
    case ')':
    case ']':
	tmp = *s++;
	TERM(tmp);
    case '}':
	*s |= 128;
	RETURN(';');
    case '&':
	s++;
	tmp = *s++;
	if (tmp == '&')
	    OPERATOR(ANDAND);
	s--;
	if (expectterm) {
	    d = bufend;
	    while (s < d && isSPACE(*s))
		s++;
	    if (isALPHA(*s) || *s == '_' || *s == '\'')
		*(--s) = '\\';	/* force next ident to WORD */
	    else
		check_uni();
	    OPERATOR(AMPER);
	}
	OPERATOR('&');
    case '|':
	s++;
	tmp = *s++;
	if (tmp == '|')
	    OPERATOR(OROR);
	s--;
	OPERATOR('|');
    case '=':
	s++;
	tmp = *s++;
	if (tmp == '=')
	    EOP(O_EQ);
	if (tmp == '~')
	    OPERATOR(MATCH);
	s--;
	OPERATOR('=');
    case '!':
	s++;
	tmp = *s++;
	if (tmp == '=')
	    EOP(O_NE);
	if (tmp == '~')
	    OPERATOR(NMATCH);
	s--;
	OPERATOR('!');
    case '<':
	if (expectterm) {
	    if (s[1] != '<' && !index(s,'>'))
		check_uni();
	    s = scanstr(s, SCAN_DEF);
	    TERM(RSTRING);
	}
	s++;
	tmp = *s++;
	if (tmp == '<')
	    OPERATOR(LS);
	if (tmp == '=') {
	    tmp = *s++;
	    if (tmp == '>')
		EOP(O_NCMP);
	    s--;
	    ROP(O_LE);
	}
	s--;
	ROP(O_LT);
    case '>':
	s++;
	tmp = *s++;
	if (tmp == '>')
	    OPERATOR(RS);
	if (tmp == '=')
	    ROP(O_GE);
	s--;
	ROP(O_GT);

#define SNARFWORD \
	d = tokenbuf; \
	while (isALNUM(*s) || *s == '\'') \
	    *d++ = *s++; \
	while (d[-1] == '\'') \
	    d--,s--; \
	*d = '\0'; \
	d = tokenbuf;

    case '$':
	if (s[1] == '#' && (isALPHA(s[2]) || s[2] == '_')) {
	    s++;
	    s = scanident(s,bufend,tokenbuf);
	    yylval.stabval = aadd(stabent(tokenbuf,TRUE));
	    TERM(ARYLEN);
	}
	d = s;
	s = scanident(s,bufend,tokenbuf);
	if (reparse) {		/* turn ${foo[bar]} into ($foo[bar]) */
	  do_reparse:
	    s[-1] = ')';
	    s = d;
	    s[1] = s[0];
	    s[0] = '(';
	    goto retry;
	}
	yylval.stabval = stabent(tokenbuf,TRUE);
	expectterm = FALSE;
	if (isSPACE(*s) && oldoldbufptr && oldoldbufptr < bufptr) {
	    s++;
	    while (isSPACE(*oldoldbufptr))
		oldoldbufptr++;
	    if (*oldoldbufptr == 'p' && strnEQ(oldoldbufptr,"print",5)) {
		if (index("&*<%", *s) && isALPHA(s[1]))
		    expectterm = TRUE;		/* e.g. print $fh &sub */
		else if (*s == '.' && isDIGIT(s[1]))
		    expectterm = TRUE;		/* e.g. print $fh .3 */
		else if (index("/?-+", *s) && !isSPACE(s[1]))
		    expectterm = TRUE;		/* e.g. print $fh -1 */
	    }
	}
	RETURN(REG);

    case '@':
	d = s;
	s = scanident(s,bufend,tokenbuf);
	if (reparse)
	    goto do_reparse;
	yylval.stabval = aadd(stabent(tokenbuf,TRUE));
	TERM(ARY);

    case '/':			/* may either be division or pattern */
    case '?':			/* may either be conditional or pattern */
	if (expectterm) {
	    check_uni();
	    s = scanpat(s);
	    TERM(PATTERN);
	}
	tmp = *s++;
	if (tmp == '/')
	    MOP(O_DIVIDE);
	OPERATOR(tmp);

    case '.':
	if (!expectterm || !isDIGIT(s[1])) {
	    tmp = *s++;
	    if (*s == tmp) {
		s++;
		if (*s == tmp) {
		    s++;
		    yylval.ival = 0;
		}
		else
		    yylval.ival = AF_COMMON;
		OPERATOR(DOTDOT);
	    }
	    if (expectterm)
		check_uni();
	    AOP(O_CONCAT);
	}
	/* FALL THROUGH */
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
    case '\'': case '"': case '`':
	s = scanstr(s, SCAN_DEF);
	TERM(RSTRING);

    case '\\':	/* some magic to force next word to be a WORD */
	s++;	/* used by do and sub to force a separate namespace */
	if (!isALPHA(*s) && *s != '_' && *s != '\'') {
	    warn("Spurious backslash ignored");
	    goto retry;
	}
	/* FALL THROUGH */
    case '_':
	SNARFWORD;
	if (d[1] == '_') {
	    if (strEQ(d,"__LINE__") || strEQ(d,"__FILE__")) {
		ARG *arg = op_new(1);

		yylval.arg = arg;
		arg->arg_type = O_ITEM;
		if (d[2] == 'L')
		    (void)sprintf(tokenbuf,"%ld",(long)curcmd->c_line);
		else
		    strcpy(tokenbuf, stab_val(curcmd->c_filestab)->str_ptr);
		arg[1].arg_type = A_SINGLE;
		arg[1].arg_ptr.arg_str = str_make(tokenbuf,strlen(tokenbuf));
		TERM(RSTRING);
	    }
	    else if (strEQ(d,"__END__")) {
		STAB *stab;
		int fd;

		/*SUPPRESS 560*/
		if (!in_eval && (stab = stabent("DATA",FALSE))) {
		    stab->str_pok |= SP_MULTI;
		    if (!stab_io(stab))
			stab_io(stab) = stio_new();
		    stab_io(stab)->ifp = rsfp;
#if defined(HAS_FCNTL) && defined(F_SETFD)
		    fd = fileno(rsfp);
		    fcntl(fd,F_SETFD,fd >= 3);
#endif
		    if (preprocess)
			stab_io(stab)->type = '|';
		    else if ((FILE*)rsfp == stdin)
			stab_io(stab)->type = '-';
		    else
			stab_io(stab)->type = '<';
		    rsfp = Nullfp;
		}
		goto fake_eof;
	    }
	}
	break;
    case 'a': case 'A':
	SNARFWORD;
	if (strEQ(d,"alarm"))
	    UNI(O_ALARM);
	if (strEQ(d,"accept"))
	    FOP22(O_ACCEPT);
	if (strEQ(d,"atan2"))
	    FUN2(O_ATAN2);
	break;
    case 'b': case 'B':
	SNARFWORD;
	if (strEQ(d,"bind"))
	    FOP2(O_BIND);
	if (strEQ(d,"binmode"))
	    FOP(O_BINMODE);
	break;
    case 'c': case 'C':
	SNARFWORD;
	if (strEQ(d,"chop"))
	    LFUN(O_CHOP);
	if (strEQ(d,"continue"))
	    OPERATOR(CONTINUE);
	if (strEQ(d,"chdir")) {
	    (void)stabent("ENV",TRUE);	/* may use HOME */
	    UNI(O_CHDIR);
	}
	if (strEQ(d,"close"))
	    FOP(O_CLOSE);
	if (strEQ(d,"closedir"))
	    FOP(O_CLOSEDIR);
	if (strEQ(d,"cmp"))
	    EOP(O_SCMP);
	if (strEQ(d,"caller"))
	    UNI(O_CALLER);
	if (strEQ(d,"crypt")) {
#ifdef FCRYPT
	    static int cryptseen = 0;

	    if (!cryptseen++)
		init_des();
#endif
	    FUN2(O_CRYPT);
	}
	if (strEQ(d,"chmod"))
	    LOP(O_CHMOD);
	if (strEQ(d,"chown"))
	    LOP(O_CHOWN);
	if (strEQ(d,"connect"))
	    FOP2(O_CONNECT);
	if (strEQ(d,"cos"))
	    UNI(O_COS);
	if (strEQ(d,"chroot"))
	    UNI(O_CHROOT);
	break;
    case 'd': case 'D':
	SNARFWORD;
	if (strEQ(d,"do")) {
	    d = bufend;
	    while (s < d && isSPACE(*s))
		s++;
	    if (isALPHA(*s) || *s == '_')
		*(--s) = '\\';	/* force next ident to WORD */
	    OPERATOR(DO);
	}
	if (strEQ(d,"die"))
	    LOP(O_DIE);
	if (strEQ(d,"defined"))
	    LFUN(O_DEFINED);
	if (strEQ(d,"delete"))
	    OPERATOR(DELETE);
	if (strEQ(d,"dbmopen"))
	    HFUN3(O_DBMOPEN);
	if (strEQ(d,"dbmclose"))
	    HFUN(O_DBMCLOSE);
	if (strEQ(d,"dump"))
	    LOOPX(O_DUMP);
	break;
    case 'e': case 'E':
	SNARFWORD;
	if (strEQ(d,"else"))
	    OPERATOR(ELSE);
	if (strEQ(d,"elsif")) {
	    yylval.ival = curcmd->c_line;
	    OPERATOR(ELSIF);
	}
	if (strEQ(d,"eq") || strEQ(d,"EQ"))
	    EOP(O_SEQ);
	if (strEQ(d,"exit"))
	    UNI(O_EXIT);
	if (strEQ(d,"eval")) {
	    allstabs = TRUE;		/* must initialize everything since */
	    UNI(O_EVAL);		/* we don't know what will be used */
	}
	if (strEQ(d,"eof"))
	    FOP(O_EOF);
	if (strEQ(d,"exp"))
	    UNI(O_EXP);
	if (strEQ(d,"each"))
	    HFUN(O_EACH);
	if (strEQ(d,"exec")) {
	    set_csh();
	    LOP(O_EXEC_OP);
	}
	if (strEQ(d,"endhostent"))
	    FUN0(O_EHOSTENT);
	if (strEQ(d,"endnetent"))
	    FUN0(O_ENETENT);
	if (strEQ(d,"endservent"))
	    FUN0(O_ESERVENT);
	if (strEQ(d,"endprotoent"))
	    FUN0(O_EPROTOENT);
	if (strEQ(d,"endpwent"))
	    FUN0(O_EPWENT);
	if (strEQ(d,"endgrent"))
	    FUN0(O_EGRENT);
	break;
    case 'f': case 'F':
	SNARFWORD;
	if (strEQ(d,"for") || strEQ(d,"foreach")) {
	    yylval.ival = curcmd->c_line;
	    while (s < bufend && isSPACE(*s))
		s++;
	    if (isALPHA(*s))
		fatal("Missing $ on loop variable");
	    OPERATOR(FOR);
	}
	if (strEQ(d,"format")) {
	    d = bufend;
	    while (s < d && isSPACE(*s))
		s++;
	    if (isALPHA(*s) || *s == '_')
		*(--s) = '\\';	/* force next ident to WORD */
	    in_format = TRUE;
	    allstabs = TRUE;		/* must initialize everything since */
	    OPERATOR(FORMAT);		/* we don't know what will be used */
	}
	if (strEQ(d,"fork"))
	    FUN0(O_FORK);
	if (strEQ(d,"fcntl"))
	    FOP3(O_FCNTL);
	if (strEQ(d,"fileno"))
	    FOP(O_FILENO);
	if (strEQ(d,"flock"))
	    FOP2(O_FLOCK);
	break;
    case 'g': case 'G':
	SNARFWORD;
	if (strEQ(d,"gt") || strEQ(d,"GT"))
	    ROP(O_SGT);
	if (strEQ(d,"ge") || strEQ(d,"GE"))
	    ROP(O_SGE);
	if (strEQ(d,"grep"))
	    FL2(O_GREP);
	if (strEQ(d,"goto"))
	    LOOPX(O_GOTO);
	if (strEQ(d,"gmtime"))
	    UNI(O_GMTIME);
	if (strEQ(d,"getc"))
	    FOP(O_GETC);
	if (strnEQ(d,"get",3)) {
	    d += 3;
	    if (*d == 'p') {
		if (strEQ(d,"ppid"))
		    FUN0(O_GETPPID);
		if (strEQ(d,"pgrp"))
		    UNI(O_GETPGRP);
		if (strEQ(d,"priority"))
		    FUN2(O_GETPRIORITY);
		if (strEQ(d,"protobyname"))
		    UNI(O_GPBYNAME);
		if (strEQ(d,"protobynumber"))
		    FUN1(O_GPBYNUMBER);
		if (strEQ(d,"protoent"))
		    FUN0(O_GPROTOENT);
		if (strEQ(d,"pwent"))
		    FUN0(O_GPWENT);
		if (strEQ(d,"pwnam"))
		    FUN1(O_GPWNAM);
		if (strEQ(d,"pwuid"))
		    FUN1(O_GPWUID);
		if (strEQ(d,"peername"))
		    FOP(O_GETPEERNAME);
	    }
	    else if (*d == 'h') {
		if (strEQ(d,"hostbyname"))
		    UNI(O_GHBYNAME);
		if (strEQ(d,"hostbyaddr"))
		    FUN2(O_GHBYADDR);
		if (strEQ(d,"hostent"))
		    FUN0(O_GHOSTENT);
	    }
	    else if (*d == 'n') {
		if (strEQ(d,"netbyname"))
		    UNI(O_GNBYNAME);
		if (strEQ(d,"netbyaddr"))
		    FUN2(O_GNBYADDR);
		if (strEQ(d,"netent"))
		    FUN0(O_GNETENT);
	    }
	    else if (*d == 's') {
		if (strEQ(d,"servbyname"))
		    FUN2(O_GSBYNAME);
		if (strEQ(d,"servbyport"))
		    FUN2(O_GSBYPORT);
		if (strEQ(d,"servent"))
		    FUN0(O_GSERVENT);
		if (strEQ(d,"sockname"))
		    FOP(O_GETSOCKNAME);
		if (strEQ(d,"sockopt"))
		    FOP3(O_GSOCKOPT);
	    }
	    else if (*d == 'g') {
		if (strEQ(d,"grent"))
		    FUN0(O_GGRENT);
		if (strEQ(d,"grnam"))
		    FUN1(O_GGRNAM);
		if (strEQ(d,"grgid"))
		    FUN1(O_GGRGID);
	    }
	    else if (*d == 'l') {
		if (strEQ(d,"login"))
		    FUN0(O_GETLOGIN);
	    }
	    d -= 3;
	}
	break;
    case 'h': case 'H':
	SNARFWORD;
	if (strEQ(d,"hex"))
	    UNI(O_HEX);
	break;
    case 'i': case 'I':
	SNARFWORD;
	if (strEQ(d,"if")) {
	    yylval.ival = curcmd->c_line;
	    OPERATOR(IF);
	}
	if (strEQ(d,"index"))
	    FUN2x(O_INDEX);
	if (strEQ(d,"int"))
	    UNI(O_INT);
	if (strEQ(d,"ioctl"))
	    FOP3(O_IOCTL);
	break;
    case 'j': case 'J':
	SNARFWORD;
	if (strEQ(d,"join"))
	    FL2(O_JOIN);
	break;
    case 'k': case 'K':
	SNARFWORD;
	if (strEQ(d,"keys"))
	    HFUN(O_KEYS);
	if (strEQ(d,"kill"))
	    LOP(O_KILL);
	break;
    case 'l': case 'L':
	SNARFWORD;
	if (strEQ(d,"last"))
	    LOOPX(O_LAST);
	if (strEQ(d,"local"))
	    OPERATOR(LOCAL);
	if (strEQ(d,"length"))
	    UNI(O_LENGTH);
	if (strEQ(d,"lt") || strEQ(d,"LT"))
	    ROP(O_SLT);
	if (strEQ(d,"le") || strEQ(d,"LE"))
	    ROP(O_SLE);
	if (strEQ(d,"localtime"))
	    UNI(O_LOCALTIME);
	if (strEQ(d,"log"))
	    UNI(O_LOG);
	if (strEQ(d,"link"))
	    FUN2(O_LINK);
	if (strEQ(d,"listen"))
	    FOP2(O_LISTEN);
	if (strEQ(d,"lstat"))
	    FOP(O_LSTAT);
	break;
    case 'm': case 'M':
	if (s[1] == '\'') {
	    d = "m";
	    s++;
	}
	else {
	    SNARFWORD;
	}
	if (strEQ(d,"m")) {
	    s = scanpat(s-1);
	    if (yylval.arg)
		TERM(PATTERN);
	    else
		RETURN(1);	/* force error */
	}
	switch (d[1]) {
	case 'k':
	    if (strEQ(d,"mkdir"))
		FUN2(O_MKDIR);
	    break;
	case 's':
	    if (strEQ(d,"msgctl"))
		FUN3(O_MSGCTL);
	    if (strEQ(d,"msgget"))
		FUN2(O_MSGGET);
	    if (strEQ(d,"msgrcv"))
		FUN5(O_MSGRCV);
	    if (strEQ(d,"msgsnd"))
		FUN3(O_MSGSND);
	    break;
	}
	break;
    case 'n': case 'N':
	SNARFWORD;
	if (strEQ(d,"next"))
	    LOOPX(O_NEXT);
	if (strEQ(d,"ne") || strEQ(d,"NE"))
	    EOP(O_SNE);
	break;
    case 'o': case 'O':
	SNARFWORD;
	if (strEQ(d,"open"))
	    OPERATOR(OPEN);
	if (strEQ(d,"ord"))
	    UNI(O_ORD);
	if (strEQ(d,"oct"))
	    UNI(O_OCT);
	if (strEQ(d,"opendir"))
	    FOP2(O_OPEN_DIR);
	break;
    case 'p': case 'P':
	SNARFWORD;
	if (strEQ(d,"print")) {
	    checkcomma(s,d,"filehandle");
	    LOP(O_PRINT);
	}
	if (strEQ(d,"printf")) {
	    checkcomma(s,d,"filehandle");
	    LOP(O_PRTF);
	}
	if (strEQ(d,"push")) {
	    yylval.ival = O_PUSH;
	    OPERATOR(PUSH);
	}
	if (strEQ(d,"pop"))
	    OPERATOR(POP);
	if (strEQ(d,"pack"))
	    FL2(O_PACK);
	if (strEQ(d,"package"))
	    OPERATOR(PACKAGE);
	if (strEQ(d,"pipe"))
	    FOP22(O_PIPE_OP);
	break;
    case 'q': case 'Q':
	SNARFWORD;
	if (strEQ(d,"q")) {
	    s = scanstr(s-1, SCAN_DEF);
	    TERM(RSTRING);
	}
	if (strEQ(d,"qq")) {
	    s = scanstr(s-2, SCAN_DEF);
	    TERM(RSTRING);
	}
	if (strEQ(d,"qx")) {
	    s = scanstr(s-2, SCAN_DEF);
	    TERM(RSTRING);
	}
	break;
    case 'r': case 'R':
	SNARFWORD;
	if (strEQ(d,"return"))
	    OLDLOP(O_RETURN);
	if (strEQ(d,"require")) {
	    allstabs = TRUE;		/* must initialize everything since */
	    UNI(O_REQUIRE);		/* we don't know what will be used */
	}
	if (strEQ(d,"reset"))
	    UNI(O_RESET);
	if (strEQ(d,"redo"))
	    LOOPX(O_REDO);
	if (strEQ(d,"rename"))
	    FUN2(O_RENAME);
	if (strEQ(d,"rand"))
	    UNI(O_RAND);
	if (strEQ(d,"rmdir"))
	    UNI(O_RMDIR);
	if (strEQ(d,"rindex"))
	    FUN2x(O_RINDEX);
	if (strEQ(d,"read"))
	    FOP3(O_READ);
	if (strEQ(d,"readdir"))
	    FOP(O_READDIR);
	if (strEQ(d,"rewinddir"))
	    FOP(O_REWINDDIR);
	if (strEQ(d,"recv"))
	    FOP4(O_RECV);
	if (strEQ(d,"reverse"))
	    LOP(O_REVERSE);
	if (strEQ(d,"readlink"))
	    UNI(O_READLINK);
	break;
    case 's': case 'S':
	if (s[1] == '\'') {
	    d = "s";
	    s++;
	}
	else {
	    SNARFWORD;
	}
	if (strEQ(d,"s")) {
	    s = scansubst(s);
	    if (yylval.arg)
		TERM(SUBST);
	    else
		RETURN(1);	/* force error */
	}
	switch (d[1]) {
	case 'a':
	case 'b':
	    break;
	case 'c':
	    if (strEQ(d,"scalar"))
		UNI(O_SCALAR);
	    break;
	case 'd':
	    break;
	case 'e':
	    if (strEQ(d,"select"))
		OPERATOR(SSELECT);
	    if (strEQ(d,"seek"))
		FOP3(O_SEEK);
	    if (strEQ(d,"semctl"))
		FUN4(O_SEMCTL);
	    if (strEQ(d,"semget"))
		FUN3(O_SEMGET);
	    if (strEQ(d,"semop"))
		FUN2(O_SEMOP);
	    if (strEQ(d,"send"))
		FOP3(O_SEND);
	    if (strEQ(d,"setpgrp"))
		FUN2(O_SETPGRP);
	    if (strEQ(d,"setpriority"))
		FUN3(O_SETPRIORITY);
	    if (strEQ(d,"sethostent"))
		FUN1(O_SHOSTENT);
	    if (strEQ(d,"setnetent"))
		FUN1(O_SNETENT);
	    if (strEQ(d,"setservent"))
		FUN1(O_SSERVENT);
	    if (strEQ(d,"setprotoent"))
		FUN1(O_SPROTOENT);
	    if (strEQ(d,"setpwent"))
		FUN0(O_SPWENT);
	    if (strEQ(d,"setgrent"))
		FUN0(O_SGRENT);
	    if (strEQ(d,"seekdir"))
		FOP2(O_SEEKDIR);
	    if (strEQ(d,"setsockopt"))
		FOP4(O_SSOCKOPT);
	    break;
	case 'f':
	case 'g':
	    break;
	case 'h':
	    if (strEQ(d,"shift"))
		TERM(SHIFT);
	    if (strEQ(d,"shmctl"))
		FUN3(O_SHMCTL);
	    if (strEQ(d,"shmget"))
		FUN3(O_SHMGET);
	    if (strEQ(d,"shmread"))
		FUN4(O_SHMREAD);
	    if (strEQ(d,"shmwrite"))
		FUN4(O_SHMWRITE);
	    if (strEQ(d,"shutdown"))
		FOP2(O_SHUTDOWN);
	    break;
	case 'i':
	    if (strEQ(d,"sin"))
		UNI(O_SIN);
	    break;
	case 'j':
	case 'k':
	    break;
	case 'l':
	    if (strEQ(d,"sleep"))
		UNI(O_SLEEP);
	    break;
	case 'm':
	case 'n':
	    break;
	case 'o':
	    if (strEQ(d,"socket"))
		FOP4(O_SOCKET);
	    if (strEQ(d,"socketpair"))
		FOP25(O_SOCKPAIR);
	    if (strEQ(d,"sort")) {
		checkcomma(s,d,"subroutine name");
		d = bufend;
		while (s < d && isSPACE(*s)) s++;
		if (*s == ';' || *s == ')')		/* probably a close */
		    fatal("sort is now a reserved word");
		if (isALPHA(*s) || *s == '_') {
		    /*SUPPRESS 530*/
		    for (d = s; isALNUM(*d); d++) ;
		    strncpy(tokenbuf,s,d-s);
		    tokenbuf[d-s] = '\0';
		    if (strNE(tokenbuf,"keys") &&
			strNE(tokenbuf,"values") &&
			strNE(tokenbuf,"split") &&
			strNE(tokenbuf,"grep") &&
			strNE(tokenbuf,"readdir") &&
			strNE(tokenbuf,"unpack") &&
			strNE(tokenbuf,"do") &&
			strNE(tokenbuf,"eval") &&
			(d >= bufend || isSPACE(*d)) )
			*(--s) = '\\';	/* force next ident to WORD */
		}
		LOP(O_SORT);
	    }
	    break;
	case 'p':
	    if (strEQ(d,"split"))
		TERM(SPLIT);
	    if (strEQ(d,"sprintf"))
		FL(O_SPRINTF);
	    if (strEQ(d,"splice")) {
		yylval.ival = O_SPLICE;
		OPERATOR(PUSH);
	    }
	    break;
	case 'q':
	    if (strEQ(d,"sqrt"))
		UNI(O_SQRT);
	    break;
	case 'r':
	    if (strEQ(d,"srand"))
		UNI(O_SRAND);
	    break;
	case 's':
	    break;
	case 't':
	    if (strEQ(d,"stat"))
		FOP(O_STAT);
	    if (strEQ(d,"study")) {
		sawstudy++;
		LFUN(O_STUDY);
	    }
	    break;
	case 'u':
	    if (strEQ(d,"substr"))
		FUN2x(O_SUBSTR);
	    if (strEQ(d,"sub")) {
		yylval.ival = savestack->ary_fill; /* restore stuff on reduce */
		savelong(&subline);
		saveitem(subname);

		subline = curcmd->c_line;
		d = bufend;
		while (s < d && isSPACE(*s))
		    s++;
		if (isALPHA(*s) || *s == '_' || *s == '\'') {
		    str_sset(subname,curstname);
		    str_ncat(subname,"'",1);
		    for (d = s+1; isALNUM(*d) || *d == '\''; d++)
			/*SUPPRESS 530*/
			;
		    if (d[-1] == '\'')
			d--;
		    str_ncat(subname,s,d-s);
		    *(--s) = '\\';	/* force next ident to WORD */
		}
		else
		    str_set(subname,"?");
		OPERATOR(SUB);
	    }
	    break;
	case 'v':
	case 'w':
	case 'x':
	    break;
	case 'y':
	    if (strEQ(d,"system")) {
		set_csh();
		LOP(O_SYSTEM);
	    }
	    if (strEQ(d,"symlink"))
		FUN2(O_SYMLINK);
	    if (strEQ(d,"syscall"))
		LOP(O_SYSCALL);
	    if (strEQ(d,"sysread"))
		FOP3(O_SYSREAD);
	    if (strEQ(d,"syswrite"))
		FOP3(O_SYSWRITE);
	    break;
	case 'z':
	    break;
	}
	break;
    case 't': case 'T':
	SNARFWORD;
	if (strEQ(d,"tr")) {
	    s = scantrans(s);
	    if (yylval.arg)
		TERM(TRANS);
	    else
		RETURN(1);	/* force error */
	}
	if (strEQ(d,"tell"))
	    FOP(O_TELL);
	if (strEQ(d,"telldir"))
	    FOP(O_TELLDIR);
	if (strEQ(d,"time"))
	    FUN0(O_TIME);
	if (strEQ(d,"times"))
	    FUN0(O_TMS);
	if (strEQ(d,"truncate"))
	    FOP2(O_TRUNCATE);
	break;
    case 'u': case 'U':
	SNARFWORD;
	if (strEQ(d,"using"))
	    OPERATOR(USING);
	if (strEQ(d,"until")) {
	    yylval.ival = curcmd->c_line;
	    OPERATOR(UNTIL);
	}
	if (strEQ(d,"unless")) {
	    yylval.ival = curcmd->c_line;
	    OPERATOR(UNLESS);
	}
	if (strEQ(d,"unlink"))
	    LOP(O_UNLINK);
	if (strEQ(d,"undef"))
	    LFUN(O_UNDEF);
	if (strEQ(d,"unpack"))
	    FUN2(O_UNPACK);
	if (strEQ(d,"utime"))
	    LOP(O_UTIME);
	if (strEQ(d,"umask"))
	    UNI(O_UMASK);
	if (strEQ(d,"unshift")) {
	    yylval.ival = O_UNSHIFT;
	    OPERATOR(PUSH);
	}
	break;
    case 'v': case 'V':
	SNARFWORD;
	if (strEQ(d,"values"))
	    HFUN(O_VALUES);
	if (strEQ(d,"vec")) {
	    sawvec = TRUE;
	    FUN3(O_VEC);
	}
	break;
    case 'w': case 'W':
	SNARFWORD;
	if (strEQ(d,"while")) {
	    yylval.ival = curcmd->c_line;
	    OPERATOR(WHILE);
	}
	if (strEQ(d,"warn"))
	    LOP(O_WARN);
	if (strEQ(d,"wait"))
	    FUN0(O_WAIT);
	if (strEQ(d,"waitpid"))
	    FUN2(O_WAITPID);
	if (strEQ(d,"wantarray")) {
	    yylval.arg = op_new(1);
	    yylval.arg->arg_type = O_ITEM;
	    yylval.arg[1].arg_type = A_WANTARRAY;
	    TERM(RSTRING);
	}
	if (strEQ(d,"write"))
	    FOP(O_WRITE);
	break;
    case 'x': case 'X':
	if (*s == 'x' && isDIGIT(s[1]) && !expectterm) {
	    s++;
	    MOP(O_REPEAT);
	}
	SNARFWORD;
	if (strEQ(d,"x")) {
	    if (!expectterm)
		MOP(O_REPEAT);
	    check_uni();
	}
	break;
    case 'y': case 'Y':
	if (s[1] == '\'') {
	    d = "y";
	    s++;
	}
	else {
	    SNARFWORD;
	}
	if (strEQ(d,"y")) {
	    s = scantrans(s);
	    TERM(TRANS);
	}
	break;
    case 'z': case 'Z':
	SNARFWORD;
	break;
    }
    yylval.cval = savestr(d);
    if (expectterm == 2) {		/* special case: start of statement */
	while (isSPACE(*s)) s++;
	if (*s == ':') {
	    s++;
	    CLINE;
	    OPERATOR(LABEL);
	}
	TERM(WORD);
    }
    expectterm = FALSE;
    if (oldoldbufptr && oldoldbufptr < bufptr) {
	while (isSPACE(*oldoldbufptr))
	    oldoldbufptr++;
	if (*oldoldbufptr == 'p' && strnEQ(oldoldbufptr,"print",5))
	    expectterm = TRUE;
	else if (*oldoldbufptr == 's' && strnEQ(oldoldbufptr,"sort",4))
	    expectterm = TRUE;
    }
    return (CLINE, bufptr = s, (int)WORD);
}

void
checkcomma(s,name,what)
register char *s;
char *name;
char *what;
{
    char *w;

    if (dowarn && *s == ' ' && s[1] == '(') {
	w = index(s,')');
	if (w)
	    for (w++; *w && isSPACE(*w); w++) ;
	if (!w || !*w || !index(";|}", *w))	/* an advisory hack only... */
	    warn("%s (...) interpreted as function",name);
    }
    while (s < bufend && isSPACE(*s))
	s++;
    if (*s == '(')
	s++;
    while (s < bufend && isSPACE(*s))
	s++;
    if (isALPHA(*s) || *s == '_') {
	w = s++;
	while (isALNUM(*s))
	    s++;
	while (s < bufend && isSPACE(*s))
	    s++;
	if (*s == ',') {
	    *s = '\0';
	    w = instr(
	      "tell eof times getlogin wait length shift umask getppid \
	      cos exp int log rand sin sqrt ord wantarray",
	      w);
	    *s = ',';
	    if (w)
		return;
	    fatal("No comma allowed after %s", what);
	}
    }
}

char *
scanident(s,send,dest)
register char *s;
register char *send;
char *dest;
{
    register char *d;
    int brackets = 0;

    reparse = Nullch;
    s++;
    d = dest;
    if (isDIGIT(*s)) {
	while (isDIGIT(*s))
	    *d++ = *s++;
    }
    else {
	while (isALNUM(*s) || *s == '\'')
	    *d++ = *s++;
    }
    while (d > dest+1 && d[-1] == '\'')
	d--,s--;
    *d = '\0';
    d = dest;
    if (!*d) {
	*d = *s++;
	if (*d == '{' /* } */ ) {
	    d = dest;
	    brackets++;
	    while (s < send && brackets) {
		if (!reparse && (d == dest || (*s && isALNUM(*s) ))) {
		    *d++ = *s++;
		    continue;
		}
		else if (!reparse)
		    reparse = s;
		switch (*s++) {
		/* { */
		case '}':
		    brackets--;
		    if (reparse && reparse == s - 1)
			reparse = Nullch;
		    break;
		case '{':   /* } */
		    brackets++;
		    break;
		}
	    }
	    *d = '\0';
	    d = dest;
	}
	else
	    d[1] = '\0';
    }
    if (*d == '^' && (isUPPER(*s) || index("[\\]^_?", *s))) {
#ifdef DEBUGGING
	if (*s == 'D')
	    debug |= 32768;
#endif
	*d = *s++ ^ 64;
    }
    return s;
}

void
scanconst(spat,string,len)
SPAT *spat;
char *string;
int len;
{
    register STR *tmpstr;
    register char *t;
    register char *d;
    register char *e;
    char *origstring = string;
    static char *vert = "|";

    if (ninstr(string, string+len, vert, vert+1))
	return;
    if (*string == '^')
	string++, len--;
    tmpstr = Str_new(86,len);
    str_nset(tmpstr,string,len);
    t = str_get(tmpstr);
    e = t + len;
    tmpstr->str_u.str_useful = 100;
    for (d=t; d < e; ) {
	switch (*d) {
	case '{':
	    if (isDIGIT(d[1]))
		e = d;
	    else
		goto defchar;
	    break;
	case '.': case '[': case '$': case '(': case ')': case '|': case '+':
	case '^':
	    e = d;
	    break;
	case '\\':
	    if (d[1] && index("wWbB0123456789sSdDlLuUExc",d[1])) {
		e = d;
		break;
	    }
	    Move(d+1,d,e-d,char);
	    e--;
	    switch(*d) {
	    case 'n':
		*d = '\n';
		break;
	    case 't':
		*d = '\t';
		break;
	    case 'f':
		*d = '\f';
		break;
	    case 'r':
		*d = '\r';
		break;
	    case 'e':
		*d = '\033';
		break;
	    case 'a':
		*d = '\007';
		break;
	    }
	    /* FALL THROUGH */
	default:
	  defchar:
	    if (d[1] == '*' || (d[1] == '{' && d[2] == '0') || d[1] == '?') {
		e = d;
		break;
	    }
	    d++;
	}
    }
    if (d == t) {
	str_free(tmpstr);
	return;
    }
    *d = '\0';
    tmpstr->str_cur = d - t;
    if (d == t+len)
	spat->spat_flags |= SPAT_ALL;
    if (*origstring != '^')
	spat->spat_flags |= SPAT_SCANFIRST;
    spat->spat_short = tmpstr;
    spat->spat_slen = d - t;
}

char *
scanpat(s)
register char *s;
{
    register SPAT *spat;
    register char *d;
    register char *e;
    int len;
    SPAT savespat;
    STR *str = Str_new(93,0);
    char delim;

    Newz(801,spat,1,SPAT);
    spat->spat_next = curstash->tbl_spatroot;	/* link into spat list */
    curstash->tbl_spatroot = spat;

    switch (*s++) {
    case 'm':
	s++;
	break;
    case '/':
	break;
    case '?':
	spat->spat_flags |= SPAT_ONCE;
	break;
    default:
	fatal("panic: scanpat");
    }
    s = str_append_till(str,s,bufend,s[-1],patleave);
    if (s >= bufend) {
	str_free(str);
	yyerror("Search pattern not terminated");
	yylval.arg = Nullarg;
	return s;
    }
    delim = *s++;
    while (*s == 'i' || *s == 'o' || *s == 'g') {
	if (*s == 'i') {
	    s++;
	    sawi = TRUE;
	    spat->spat_flags |= SPAT_FOLD;
	}
	if (*s == 'o') {
	    s++;
	    spat->spat_flags |= SPAT_KEEP;
	}
	if (*s == 'g') {
	    s++;
	    spat->spat_flags |= SPAT_GLOBAL;
	}
    }
    len = str->str_cur;
    e = str->str_ptr + len;
    if (delim == '\'')
	d = e;
    else
	d = str->str_ptr;
    for (; d < e; d++) {
	if (*d == '\\')
	    d++;
	else if ((*d == '$' && d[1] && d[1] != '|' && d[1] != ')') ||
		 (*d == '@')) {
	    register ARG *arg;

	    spat->spat_runtime = arg = op_new(1);
	    arg->arg_type = O_ITEM;
	    arg[1].arg_type = A_DOUBLE;
	    arg[1].arg_ptr.arg_str = str_smake(str);
	    d = scanident(d,bufend,buf);
	    (void)stabent(buf,TRUE);		/* make sure it's created */
	    for (; d < e; d++) {
		if (*d == '\\')
		    d++;
		else if (*d == '$' && d[1] && d[1] != '|' && d[1] != ')') {
		    d = scanident(d,bufend,buf);
		    (void)stabent(buf,TRUE);
		}
		else if (*d == '@') {
		    d = scanident(d,bufend,buf);
		    if (strEQ(buf,"ARGV") || strEQ(buf,"ENV") ||
		      strEQ(buf,"SIG") || strEQ(buf,"INC"))
			(void)stabent(buf,TRUE);
		}
	    }
	    goto got_pat;		/* skip compiling for now */
	}
    }
    if (spat->spat_flags & SPAT_FOLD)
	StructCopy(spat, &savespat, SPAT);
    scanconst(spat,str->str_ptr,len);
    if ((spat->spat_flags & SPAT_ALL) && (spat->spat_flags & SPAT_SCANFIRST)) {
	fbmcompile(spat->spat_short, spat->spat_flags & SPAT_FOLD);
	spat->spat_regexp = regcomp(str->str_ptr,str->str_ptr+len,
	    spat->spat_flags & SPAT_FOLD);
		/* Note that this regexp can still be used if someone says
		 * something like /a/ && s//b/;  so we can't delete it.
		 */
    }
    else {
	if (spat->spat_flags & SPAT_FOLD)
	StructCopy(&savespat, spat, SPAT);
	if (spat->spat_short)
	    fbmcompile(spat->spat_short, spat->spat_flags & SPAT_FOLD);
	spat->spat_regexp = regcomp(str->str_ptr,str->str_ptr+len,
	    spat->spat_flags & SPAT_FOLD);
	hoistmust(spat);
    }
  got_pat:
    str_free(str);
    yylval.arg = make_match(O_MATCH,stab2arg(A_STAB,defstab),spat);
    return s;
}

char *
scansubst(start)
char *start;
{
    register char *s = start;
    register SPAT *spat;
    register char *d;
    register char *e;
    int len;
    STR *str = Str_new(93,0);
    char term = *s;

    if (term && (d = index("([{< )]}> )]}>",term)))
	term = d[5];

    Newz(802,spat,1,SPAT);
    spat->spat_next = curstash->tbl_spatroot;	/* link into spat list */
    curstash->tbl_spatroot = spat;

    s = str_append_till(str,s+1,bufend,term,patleave);
    if (s >= bufend) {
	str_free(str);
	yyerror("Substitution pattern not terminated");
	yylval.arg = Nullarg;
	return s;
    }
    len = str->str_cur;
    e = str->str_ptr + len;
    for (d = str->str_ptr; d < e; d++) {
	if (*d == '\\')
	    d++;
	else if ((*d == '$' && d[1] && d[1] != '|' && /*(*/ d[1] != ')') ||
	    *d == '@' ) {
	    register ARG *arg;

	    spat->spat_runtime = arg = op_new(1);
	    arg->arg_type = O_ITEM;
	    arg[1].arg_type = A_DOUBLE;
	    arg[1].arg_ptr.arg_str = str_smake(str);
	    d = scanident(d,e,buf);
	    (void)stabent(buf,TRUE);		/* make sure it's created */
	    for (; *d; d++) {
		if (*d == '$' && d[1] && d[-1] != '\\' && d[1] != '|') {
		    d = scanident(d,e,buf);
		    (void)stabent(buf,TRUE);
		}
		else if (*d == '@' && d[-1] != '\\') {
		    d = scanident(d,e,buf);
		    if (strEQ(buf,"ARGV") || strEQ(buf,"ENV") ||
		      strEQ(buf,"SIG") || strEQ(buf,"INC"))
			(void)stabent(buf,TRUE);
		}
	    }
	    goto get_repl;		/* skip compiling for now */
	}
    }
    scanconst(spat,str->str_ptr,len);
get_repl:
    if (term != *start)
	s++;
    s = scanstr(s, SCAN_REPL);
    if (s >= bufend) {
	str_free(str);
	yyerror("Substitution replacement not terminated");
	yylval.arg = Nullarg;
	return s;
    }
    spat->spat_repl = yylval.arg;
    if ((spat->spat_repl[1].arg_type & A_MASK) == A_SINGLE)
	spat->spat_flags |= SPAT_CONST;
    else if ((spat->spat_repl[1].arg_type & A_MASK) == A_DOUBLE) {
	STR *tmpstr;
	register char *t;

	spat->spat_flags |= SPAT_CONST;
	tmpstr = spat->spat_repl[1].arg_ptr.arg_str;
	e = tmpstr->str_ptr + tmpstr->str_cur;
	for (t = tmpstr->str_ptr; t < e; t++) {
	    if (*t == '$' && t[1] && (index("`'&+0123456789",t[1]) ||
	      (t[1] == '{' /*}*/ && isDIGIT(t[2])) ))
		spat->spat_flags &= ~SPAT_CONST;
	}
    }
    while (*s == 'g' || *s == 'i' || *s == 'e' || *s == 'o') {
	int es = 0;

	if (*s == 'e') {
	    s++;
	    es++;
	    if ((spat->spat_repl[1].arg_type & A_MASK) == A_DOUBLE)
		spat->spat_repl[1].arg_type = A_SINGLE;
	    spat->spat_repl = make_op(
		(!es && spat->spat_repl[1].arg_type == A_SINGLE
			? O_EVALONCE
			: O_EVAL),
		2,
		spat->spat_repl,
		Nullarg,
		Nullarg);
	    spat->spat_flags &= ~SPAT_CONST;
	}
	if (*s == 'g') {
	    s++;
	    spat->spat_flags |= SPAT_GLOBAL;
	}
	if (*s == 'i') {
	    s++;
	    sawi = TRUE;
	    spat->spat_flags |= SPAT_FOLD;
	    if (!(spat->spat_flags & SPAT_SCANFIRST)) {
		str_free(spat->spat_short);	/* anchored opt doesn't do */
		spat->spat_short = Nullstr;	/* case insensitive match */
		spat->spat_slen = 0;
	    }
	}
	if (*s == 'o') {
	    s++;
	    spat->spat_flags |= SPAT_KEEP;
	}
    }
    if (spat->spat_short && (spat->spat_flags & SPAT_SCANFIRST))
	fbmcompile(spat->spat_short, spat->spat_flags & SPAT_FOLD);
    if (!spat->spat_runtime) {
	spat->spat_regexp = regcomp(str->str_ptr,str->str_ptr+len,
	  spat->spat_flags & SPAT_FOLD);
	hoistmust(spat);
    }
    yylval.arg = make_match(O_SUBST,stab2arg(A_STAB,defstab),spat);
    str_free(str);
    return s;
}

void
hoistmust(spat)
register SPAT *spat;
{
    if (!spat->spat_short && spat->spat_regexp->regstart &&
	(!spat->spat_regexp->regmust || spat->spat_regexp->reganch & ROPT_ANCH)
       ) {
	if (!(spat->spat_regexp->reganch & ROPT_ANCH))
	    spat->spat_flags |= SPAT_SCANFIRST;
	else if (spat->spat_flags & SPAT_FOLD)
	    return;
	spat->spat_short = str_smake(spat->spat_regexp->regstart);
    }
    else if (spat->spat_regexp->regmust) {/* is there a better short-circuit? */
	if (spat->spat_short &&
	  str_eq(spat->spat_short,spat->spat_regexp->regmust))
	{
	    if (spat->spat_flags & SPAT_SCANFIRST) {
		str_free(spat->spat_short);
		spat->spat_short = Nullstr;
	    }
	    else {
		str_free(spat->spat_regexp->regmust);
		spat->spat_regexp->regmust = Nullstr;
		return;
	    }
	}
	if (!spat->spat_short ||	/* promote the better string */
	  ((spat->spat_flags & SPAT_SCANFIRST) &&
	   (spat->spat_short->str_cur < spat->spat_regexp->regmust->str_cur) )){
	    str_free(spat->spat_short);		/* ok if null */
	    spat->spat_short = spat->spat_regexp->regmust;
	    spat->spat_regexp->regmust = Nullstr;
	    spat->spat_flags |= SPAT_SCANFIRST;
	}
    }
}

char *
scantrans(start)
char *start;
{
    register char *s = start;
    ARG *arg =
	l(make_op(O_TRANS,2,stab2arg(A_STAB,defstab),Nullarg,Nullarg));
    STR *tstr;
    STR *rstr;
    register char *t;
    register char *r;
    register short *tbl;
    register int i;
    register int j;
    int tlen, rlen;
    int squash;
    int delete;
    int complement;

    New(803,tbl,256,short);
    arg[2].arg_type = A_NULL;
    arg[2].arg_ptr.arg_cval = (char*) tbl;

    s = scanstr(s, SCAN_TR);
    if (s >= bufend) {
	yyerror("Translation pattern not terminated");
	yylval.arg = Nullarg;
	return s;
    }
    tstr = yylval.arg[1].arg_ptr.arg_str; 
    yylval.arg[1].arg_ptr.arg_str = Nullstr; 
    arg_free(yylval.arg);
    t = tstr->str_ptr;
    tlen = tstr->str_cur;

    if (s[-1] == *start)
	s--;

    s = scanstr(s, SCAN_TR|SCAN_REPL);
    if (s >= bufend) {
	yyerror("Translation replacement not terminated");
	yylval.arg = Nullarg;
	return s;
    }
    rstr = yylval.arg[1].arg_ptr.arg_str; 
    yylval.arg[1].arg_ptr.arg_str = Nullstr; 
    arg_free(yylval.arg);
    r = rstr->str_ptr;
    rlen = rstr->str_cur;

    complement = delete = squash = 0;
    while (*s == 'c' || *s == 'd' || *s == 's') {
	if (*s == 'c')
	    complement = 1;
	else if (*s == 'd')
	    delete = 2;
	else
	    squash = 1;
	s++;
    }
    arg[2].arg_len = delete|squash;
    yylval.arg = arg;
    if (complement) {
	Zero(tbl, 256, short);
	for (i = 0; i < tlen; i++)
	    tbl[t[i] & 0377] = -1;
	for (i = 0, j = 0; i < 256; i++) {
	    if (!tbl[i]) {
		if (j >= rlen) {
		    if (delete)
			tbl[i] = -2;
		    else if (rlen)
			tbl[i] = r[j-1] & 0377;
		    else
			tbl[i] = i;
		}
		else
		    tbl[i] = r[j++] & 0377;
	    }
	}
    }
    else {
	if (!rlen && !delete) {
	    r = t; rlen = tlen;
	}
	for (i = 0; i < 256; i++)
	    tbl[i] = -1;
	for (i = 0, j = 0; i < tlen; i++,j++) {
	    if (j >= rlen) {
		if (delete) {
		    if (tbl[t[i] & 0377] == -1)
			tbl[t[i] & 0377] = -2;
		    continue;
		}
		--j;
	    }
	    if (tbl[t[i] & 0377] == -1)
		tbl[t[i] & 0377] = r[j] & 0377;
	}
    }
    str_free(tstr);
    str_free(rstr);
    return s;
}

char *
scanstr(start, in_what)
char *start;
int in_what;
{
    register char *s = start;
    register char term;
    register char *d;
    register ARG *arg;
    register char *send;
    register bool makesingle = FALSE;
    register STAB *stab;
    bool alwaysdollar = FALSE;
    bool hereis = FALSE;
    STR *herewas;
    STR *str;
    /* which backslash sequences to keep */
    char *leave = (in_what & SCAN_TR)
	? "\\$@nrtfbeacx0123456789-"
	: "\\$@nrtfbeacx0123456789[{]}lLuUE";
    int len;

    arg = op_new(1);
    yylval.arg = arg;
    arg->arg_type = O_ITEM;

    switch (*s) {
    default:			/* a substitution replacement */
	arg[1].arg_type = A_DOUBLE;
	makesingle = TRUE;	/* maybe disable runtime scanning */
	term = *s;
	if (term == '\'')
	    leave = Nullch;
	goto snarf_it;
    case '0':
	{
	    unsigned long i;
	    int shift;

	    arg[1].arg_type = A_SINGLE;
	    if (s[1] == 'x') {
		shift = 4;
		s += 2;
	    }
	    else if (s[1] == '.')
		goto decimal;
	    else
		shift = 3;
	    i = 0;
	    for (;;) {
		switch (*s) {
		default:
		    goto out;
		case '_':
		    s++;
		    break;
		case '8': case '9':
		    if (shift != 4)
			yyerror("Illegal octal digit");
		    /* FALL THROUGH */
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7':
		    i <<= shift;
		    i += *s++ & 15;
		    break;
		case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
		case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
		    if (shift != 4)
			goto out;
		    i <<= 4;
		    i += (*s++ & 7) + 9;
		    break;
		}
	    }
	  out:
	    str = Str_new(92,0);
	    str_numset(str,(double)i);
	    if (str->str_ptr) {
		Safefree(str->str_ptr);
		str->str_ptr = Nullch;
		str->str_len = str->str_cur = 0;
	    }
	    arg[1].arg_ptr.arg_str = str;
	}
	break;
    case '1': case '2': case '3': case '4': case '5':
    case '6': case '7': case '8': case '9': case '.':
      decimal:
	arg[1].arg_type = A_SINGLE;
	d = tokenbuf;
	while (isDIGIT(*s) || *s == '_') {
	    if (*s == '_')
		s++;
	    else
		*d++ = *s++;
	}
	if (*s == '.' && s[1] != '.') {
	    *d++ = *s++;
	    while (isDIGIT(*s) || *s == '_') {
		if (*s == '_')
		    s++;
		else
		    *d++ = *s++;
	    }
	}
	if (*s && index("eE",*s) && index("+-0123456789",s[1])) {
	    *d++ = *s++;
	    if (*s == '+' || *s == '-')
		*d++ = *s++;
	    while (isDIGIT(*s))
		*d++ = *s++;
	}
	*d = '\0';
	str = Str_new(92,0);
	str_numset(str,atof(tokenbuf));
	if (str->str_ptr) {
	    Safefree(str->str_ptr);
	    str->str_ptr = Nullch;
	    str->str_len = str->str_cur = 0;
	}
	arg[1].arg_ptr.arg_str = str;
	break;
    case '<':
	if (in_what & (SCAN_REPL|SCAN_TR))
	    goto do_double;
	if (*++s == '<') {
	    hereis = TRUE;
	    d = tokenbuf;
	    if (!rsfp)
		*d++ = '\n';
	    if (*++s && index("`'\"",*s)) {
		term = *s++;
		s = cpytill(d,s,bufend,term,&len);
		if (s < bufend)
		    s++;
		d += len;
	    }
	    else {
		if (*s == '\\')
		    s++, term = '\'';
		else
		    term = '"';
		while (isALNUM(*s))
		    *d++ = *s++;
	    }				/* assuming tokenbuf won't clobber */
	    *d++ = '\n';
	    *d = '\0';
	    len = d - tokenbuf;
	    d = "\n";
	    if (rsfp || !(d=ninstr(s,bufend,d,d+1)))
		herewas = str_make(s,bufend-s);
	    else
		s--, herewas = str_make(s,d-s);
	    s += herewas->str_cur;
	    if (term == '\'')
		goto do_single;
	    if (term == '`')
		goto do_back;
	    goto do_double;
	}
	d = tokenbuf;
	s = cpytill(d,s,bufend,'>',&len);
	if (s < bufend)
	    s++;
	else
	    fatal("Unterminated <> operator");

	if (*d == '$') d++;
	while (*d && (isALNUM(*d) || *d == '\''))
	    d++;
	if (d - tokenbuf != len) {
	    s = start;
	    term = *s;
	    arg[1].arg_type = A_GLOB;
	    set_csh();
	    alwaysdollar = TRUE;	/* treat $) and $| as variables */
	    goto snarf_it;
	}
	else {
	    d = tokenbuf;
	    if (!len)
		(void)strcpy(d,"ARGV");
	    if (*d == '$') {
		arg[1].arg_type = A_INDREAD;
		arg[1].arg_ptr.arg_stab = stabent(d+1,TRUE);
	    }
	    else {
		arg[1].arg_type = A_READ;
		arg[1].arg_ptr.arg_stab = stabent(d,TRUE);
		if (!stab_io(arg[1].arg_ptr.arg_stab))
		    stab_io(arg[1].arg_ptr.arg_stab) = stio_new();
		if (strEQ(d,"ARGV")) {
		    (void)aadd(arg[1].arg_ptr.arg_stab);
		    stab_io(arg[1].arg_ptr.arg_stab)->flags |=
		      IOF_ARGV|IOF_START;
		}
	    }
	}
	break;

    case 'q':
	s++;
	if (*s == 'q') {
	    s++;
	    goto do_double;
	}
	if (*s == 'x') {
	    s++;
	    goto do_back;
	}
	/* FALL THROUGH */
    case '\'':
      do_single:
	term = *s;
	arg[1].arg_type = A_SINGLE;
	leave = Nullch;
	goto snarf_it;

    case '"': 
      do_double:
	term = *s;
	arg[1].arg_type = A_DOUBLE;
	makesingle = TRUE;	/* maybe disable runtime scanning */
	alwaysdollar = TRUE;	/* treat $) and $| as variables */
	goto snarf_it;
    case '`':
      do_back:
	term = *s;
	arg[1].arg_type = A_BACKTICK;
	set_csh();
	alwaysdollar = TRUE;	/* treat $) and $| as variables */
      snarf_it:
	{
	    STR *tmpstr;
	    STR *tmpstr2 = Nullstr;
	    char *tmps;
	    char *start;
	    bool dorange = FALSE;

	    CLINE;
	    multi_start = curcmd->c_line;
	    if (hereis)
		multi_open = multi_close = '<';
	    else {
		multi_open = term;
		if (term && (tmps = index("([{< )]}> )]}>",term)))
		    term = tmps[5];
		multi_close = term;
	    }
	    tmpstr = Str_new(87,80);
	    if (hereis) {
		term = *tokenbuf;
		if (!rsfp) {
		    d = s;
		    while (s < bufend &&
		      (*s != term || bcmp(s,tokenbuf,len) != 0) ) {
			if (*s++ == '\n')
			    curcmd->c_line++;
		    }
		    if (s >= bufend) {
			curcmd->c_line = multi_start;
			fatal("EOF in string");
		    }
		    str_nset(tmpstr,d+1,s-d);
		    s += len - 1;
		    str_ncat(herewas,s,bufend-s);
		    str_replace(linestr,herewas);
		    oldoldbufptr = oldbufptr = bufptr = s = str_get(linestr);
		    bufend = linestr->str_ptr + linestr->str_cur;
		    hereis = FALSE;
		}
		else
		    str_nset(tmpstr,"",0);   /* avoid "uninitialized" warning */
	    }
	    else
		s = str_append_till(tmpstr,s+1,bufend,term,leave);
	    while (s >= bufend) {	/* multiple line string? */
		if (!rsfp ||
		 !(oldoldbufptr = oldbufptr = s = str_gets(linestr, rsfp, 0))) {
		    curcmd->c_line = multi_start;
		    fatal("EOF in string");
		}
		curcmd->c_line++;
		if (perldb) {
		    STR *str = Str_new(88,0);

		    str_sset(str,linestr);
		    astore(stab_xarray(curcmd->c_filestab),
		      (int)curcmd->c_line,str);
		}
		bufend = linestr->str_ptr + linestr->str_cur;
		if (hereis) {
		    if (*s == term && bcmp(s,tokenbuf,len) == 0) {
			s = bufend - 1;
			*s = ' ';
			str_scat(linestr,herewas);
			bufend = linestr->str_ptr + linestr->str_cur;
		    }
		    else {
			s = bufend;
			str_scat(tmpstr,linestr);
		    }
		}
		else
		    s = str_append_till(tmpstr,s,bufend,term,leave);
	    }
	    multi_end = curcmd->c_line;
	    s++;
	    if (tmpstr->str_cur + 5 < tmpstr->str_len) {
		tmpstr->str_len = tmpstr->str_cur + 1;
		Renew(tmpstr->str_ptr, tmpstr->str_len, char);
	    }
	    if (arg[1].arg_type == A_SINGLE) {
		arg[1].arg_ptr.arg_str = tmpstr;
		break;
	    }
	    tmps = s;
	    s = tmpstr->str_ptr;
	    send = s + tmpstr->str_cur;
	    while (s < send) {		/* see if we can make SINGLE */
		if (*s == '\\' && s[1] && isDIGIT(s[1]) && !isDIGIT(s[2]) &&
		  !alwaysdollar && s[1] != '0')
		    *s = '$';		/* grandfather \digit in subst */
		if ((*s == '$' || *s == '@') && s+1 < send &&
		  (alwaysdollar || (s[1] != ')' && s[1] != '|'))) {
		    makesingle = FALSE;	/* force interpretation */
		}
		else if (*s == '\\' && s+1 < send) {
		    if (index("lLuUE",s[1]))
			makesingle = FALSE;
		    s++;
		}
		s++;
	    }
	    s = d = start = tmpstr->str_ptr;	/* assuming shrinkage only */
	    while (s < send || dorange) {
		if (in_what & SCAN_TR) {
		    if (dorange) {
			int i;
			int max;
			if (!tmpstr2) {	/* oops, have to grow */
			    tmpstr2 = str_smake(tmpstr);
			    s = tmpstr2->str_ptr + (s - tmpstr->str_ptr);
			    send = tmpstr2->str_ptr + (send - tmpstr->str_ptr);
			}
			i = d - tmpstr->str_ptr;
			STR_GROW(tmpstr, tmpstr->str_len + 256);
			d = tmpstr->str_ptr + i;
			d -= 2;
			max = d[1] & 0377;
			for (i = (*d & 0377); i <= max; i++)
			    *d++ = i;
			start = s;
			dorange = FALSE;
			continue;
		    }
		    else if (*s == '-' && s+1 < send  && s != start) {
			dorange = TRUE;
			s++;
		    }
		}
		else {
		    if ((*s == '$' && s+1 < send &&
			(alwaysdollar || /*(*/(s[1] != ')' && s[1] != '|')) ) ||
			(*s == '@' && s+1 < send) ) {
			if (s[1] == '#' && (isALPHA(s[2]) || s[2] == '_'))
			    *d++ = *s++;
			len = scanident(s,send,tokenbuf) - s;
			if (*s == '$' || strEQ(tokenbuf,"ARGV")
			  || strEQ(tokenbuf,"ENV")
			  || strEQ(tokenbuf,"SIG")
			  || strEQ(tokenbuf,"INC") )
			    (void)stabent(tokenbuf,TRUE); /* add symbol */
			while (len--)
			    *d++ = *s++;
			continue;
		    }
		}
		if (*s == '\\' && s+1 < send) {
		    s++;
		    switch (*s) {
		    case '-':
			if (in_what & SCAN_TR) {
			    *d++ = *s++;
			    continue;
			}
			/* FALL THROUGH */
		    default:
			if (!makesingle && (!leave || (*s && index(leave,*s))))
			    *d++ = '\\';
			*d++ = *s++;
			continue;
		    case '0': case '1': case '2': case '3':
		    case '4': case '5': case '6': case '7':
			*d++ = scanoct(s, 3, &len);
			s += len;
			continue;
		    case 'x':
			*d++ = scanhex(++s, 2, &len);
			s += len;
			continue;
		    case 'c':
			s++;
			*d = *s++;
			if (isLOWER(*d))
			    *d = toupper(*d);
			*d++ ^= 64;
			continue;
		    case 'b':
			*d++ = '\b';
			break;
		    case 'n':
			*d++ = '\n';
			break;
		    case 'r':
			*d++ = '\r';
			break;
		    case 'f':
			*d++ = '\f';
			break;
		    case 't':
			*d++ = '\t';
			break;
		    case 'e':
			*d++ = '\033';
			break;
		    case 'a':
			*d++ = '\007';
			break;
		    }
		    s++;
		    continue;
		}
		*d++ = *s++;
	    }
	    *d = '\0';

	    if (arg[1].arg_type == A_DOUBLE && makesingle)
		arg[1].arg_type = A_SINGLE;	/* now we can optimize on it */

	    tmpstr->str_cur = d - tmpstr->str_ptr;
	    if (arg[1].arg_type == A_GLOB) {
		arg[1].arg_ptr.arg_stab = stab = genstab();
		stab_io(stab) = stio_new();
		str_sset(stab_val(stab), tmpstr);
	    }
	    else
		arg[1].arg_ptr.arg_str = tmpstr;
	    s = tmps;
	    if (tmpstr2)
		str_free(tmpstr2);
	    break;
	}
    }
    if (hereis)
	str_free(herewas);
    return s;
}

FCMD *
load_format()
{
    FCMD froot;
    FCMD *flinebeg;
    char *eol;
    register FCMD *fprev = &froot;
    register FCMD *fcmd;
    register char *s;
    register char *t;
    register STR *str;
    bool noblank;
    bool repeater;

    Zero(&froot, 1, FCMD);
    s = bufptr;
    while (s < bufend || (rsfp && (s = str_gets(linestr,rsfp, 0)) != Nullch)) {
	curcmd->c_line++;
	if (in_eval && !rsfp) {
	    eol = index(s,'\n');
	    if (!eol++)
		eol = bufend;
	}
	else
	    eol = bufend = linestr->str_ptr + linestr->str_cur;
	if (perldb) {
	    STR *tmpstr = Str_new(89,0);

	    str_nset(tmpstr, s, eol-s);
	    astore(stab_xarray(curcmd->c_filestab), (int)curcmd->c_line,tmpstr);
	}
	if (*s == '.') {
	    /*SUPPRESS 530*/
	    for (t = s+1; *t == ' ' || *t == '\t'; t++) ;
	    if (*t == '\n') {
		bufptr = s;
		return froot.f_next;
	    }
	}
	if (*s == '#') {
	    s = eol;
	    continue;
	}
	flinebeg = Nullfcmd;
	noblank = FALSE;
	repeater = FALSE;
	while (s < eol) {
	    Newz(804,fcmd,1,FCMD);
	    fprev->f_next = fcmd;
	    fprev = fcmd;
	    for (t=s; t < eol && *t != '@' && *t != '^'; t++) {
		if (*t == '~') {
		    noblank = TRUE;
		    *t = ' ';
		    if (t[1] == '~') {
			repeater = TRUE;
			t[1] = ' ';
		    }
		}
	    }
	    fcmd->f_pre = nsavestr(s, t-s);
	    fcmd->f_presize = t-s;
	    s = t;
	    if (s >= eol) {
		if (noblank)
		    fcmd->f_flags |= FC_NOBLANK;
		if (repeater)
		    fcmd->f_flags |= FC_REPEAT;
		break;
	    }
	    if (!flinebeg)
		flinebeg = fcmd;		/* start values here */
	    if (*s++ == '^')
		fcmd->f_flags |= FC_CHOP;	/* for doing text filling */
	    switch (*s) {
	    case '*':
		fcmd->f_type = F_LINES;
		*s = '\0';
		break;
	    case '<':
		fcmd->f_type = F_LEFT;
		while (*s == '<')
		    s++;
		break;
	    case '>':
		fcmd->f_type = F_RIGHT;
		while (*s == '>')
		    s++;
		break;
	    case '|':
		fcmd->f_type = F_CENTER;
		while (*s == '|')
		    s++;
		break;
	    case '#':
	    case '.':
		/* Catch the special case @... and handle it as a string
		   field. */
		if (*s == '.' && s[1] == '.') {
		    goto default_format;
		}
		fcmd->f_type = F_DECIMAL;
		{
		    char *p;

		    /* Read a format in the form @####.####, where either group
		       of ### may be empty, or the final .### may be missing. */
		    while (*s == '#')
			s++;
		    if (*s == '.') {
			s++;
			p = s;
			while (*s == '#')
			    s++;
			fcmd->f_decimals = s-p;
			fcmd->f_flags |= FC_DP;
		    } else {
			fcmd->f_decimals = 0;
		    }
		}
		break;
	    default:
	    default_format:
		fcmd->f_type = F_LEFT;
		break;
	    }
	    if (fcmd->f_flags & FC_CHOP && *s == '.') {
		fcmd->f_flags |= FC_MORE;
		while (*s == '.')
		    s++;
	    }
	    fcmd->f_size = s-t;
	}
	if (flinebeg) {
	  again:
	    if (s >= bufend &&
	      (!rsfp || (s = str_gets(linestr, rsfp, 0)) == Nullch) )
		goto badform;
	    curcmd->c_line++;
	    if (in_eval && !rsfp) {
		eol = index(s,'\n');
		if (!eol++)
		    eol = bufend;
	    }
	    else
		eol = bufend = linestr->str_ptr + linestr->str_cur;
	    if (perldb) {
		STR *tmpstr = Str_new(90,0);

		str_nset(tmpstr, s, eol-s);
		astore(stab_xarray(curcmd->c_filestab),
		    (int)curcmd->c_line,tmpstr);
	    }
	    if (strnEQ(s,".\n",2)) {
		bufptr = s;
		yyerror("Missing values line");
		return froot.f_next;
	    }
	    if (*s == '#') {
		s = eol;
		goto again;
	    }
	    str = flinebeg->f_unparsed = Str_new(91,eol - s);
	    str->str_u.str_hash = curstash;
	    str_nset(str,"(",1);
	    flinebeg->f_line = curcmd->c_line;
	    eol[-1] = '\0';
	    if (!flinebeg->f_next->f_type || index(s, ',')) {
		eol[-1] = '\n';
		str_ncat(str, s, eol - s - 1);
		str_ncat(str,",$$);",5);
		s = eol;
	    }
	    else {
		eol[-1] = '\n';
		while (s < eol && isSPACE(*s))
		    s++;
		t = s;
		while (s < eol) {
		    switch (*s) {
		    case ' ': case '\t': case '\n': case ';':
			str_ncat(str, t, s - t);
			str_ncat(str, "," ,1);
			while (s < eol && (isSPACE(*s) || *s == ';'))
			    s++;
			t = s;
			break;
		    case '$':
			str_ncat(str, t, s - t);
			t = s;
			s = scanident(s,eol,tokenbuf);
			str_ncat(str, t, s - t);
			t = s;
			if (s < eol && *s && index("$'\"",*s))
			    str_ncat(str, ",", 1);
			break;
		    case '"': case '\'':
			str_ncat(str, t, s - t);
			t = s;
			s++;
			while (s < eol && (*s != *t || s[-1] == '\\'))
			    s++;
			if (s < eol)
			    s++;
			str_ncat(str, t, s - t);
			t = s;
			if (s < eol && *s && index("$'\"",*s))
			    str_ncat(str, ",", 1);
			break;
		    default:
			yyerror("Please use commas to separate fields");
		    }
		}
		str_ncat(str,"$$);",4);
	    }
	}
    }
  badform:
    bufptr = str_get(linestr);
    yyerror("Format not terminated");
    return froot.f_next;
}

static void
set_csh()
{
#ifdef CSH
    if (!cshlen)
	cshlen = strlen(cshname);
#endif
}
