/* $RCSfile: str.c,v $$Revision: 1.2 $$Date: 1995/05/30 05:03:21 $
 *
 *    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: str.c,v $
 * Revision 1.2  1995/05/30 05:03:21  rgrimes
 * Remove trailing whitespace.
 *
 * Revision 1.1.1.1  1994/09/10  06:27:33  gclarkii
 * Initial import of Perl 4.046 bmaked
 *
 * Revision 1.1.1.1  1993/08/23  21:29:39  nate
 * PERL!
 *
 * Revision 4.0.1.7  1993/02/05  19:43:47  lwall
 * patch36: the non-std stdio input code wasn't null-proof
 *
 * Revision 4.0.1.6  92/06/11  21:14:21  lwall
 * patch34: quotes containing subscripts containing variables didn't parse right
 *
 * Revision 4.0.1.5  92/06/08  15:40:43  lwall
 * patch20: removed implicit int declarations on functions
 * patch20: Perl now distinguishes overlapped copies from non-overlapped
 * patch20: paragraph mode now skips extra newlines automatically
 * patch20: fixed memory leak in doube-quote interpretation
 * patch20: made /\$$foo/ look for literal '$foo'
 * patch20: "$var{$foo'bar}" didn't scan subscript correctly
 * patch20: a splice on non-existent array elements could dump core
 * patch20: running taintperl explicitly now does checks even if $< == $>
 *
 * Revision 4.0.1.4  91/11/05  18:40:51  lwall
 * patch11: $foo .= <BAR> could overrun malloced memory
 * patch11: \$ didn't always make it through double-quoter to regexp routines
 * patch11: prepared for ctype implementations that don't define isascii()
 *
 * Revision 4.0.1.3  91/06/10  01:27:54  lwall
 * patch10: $) and $| incorrectly handled in run-time patterns
 *
 * Revision 4.0.1.2  91/06/07  11:58:13  lwall
 * patch4: new copyright notice
 * patch4: taint check on undefined string could cause core dump
 *
 * Revision 4.0.1.1  91/04/12  09:15:30  lwall
 * patch1: fixed undefined environ problem
 * patch1: substr($ENV{"PATH"},0,0) = "/foo:" didn't modify environment
 * patch1: $foo .= <BAR> could cause core dump for certain lengths of $foo
 *
 * Revision 4.0  91/03/20  01:39:55  lwall
 * 4.0 baseline.
 *
 */

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

static void ucase();
static void lcase();

#ifndef str_get
char *
str_get(str)
STR *str;
{
#ifdef TAINT
    tainted |= str->str_tainted;
#endif
    return str->str_pok ? str->str_ptr : str_2ptr(str);
}
#endif

/* dlb ... guess we have a "crippled cc".
 * dlb the following functions are usually macros.
 */
#ifndef str_true
int
str_true(Str)
STR *Str;
{
	if (Str->str_pok) {
	    if (*Str->str_ptr > '0' ||
	      Str->str_cur > 1 ||
	      (Str->str_cur && *Str->str_ptr != '0'))
		return 1;
	    return 0;
	}
	if (Str->str_nok)
		return (Str->str_u.str_nval != 0.0);
	return 0;
}
#endif /* str_true */

#ifndef str_gnum
double str_gnum(Str)
STR *Str;
{
#ifdef TAINT
	tainted |= Str->str_tainted;
#endif /* TAINT*/
	if (Str->str_nok)
		return Str->str_u.str_nval;
	return str_2num(Str);
}
#endif /* str_gnum */
/* dlb ... end of crutch */

char *
str_grow(str,newlen)
register STR *str;
#ifndef DOSISH
register int newlen;
#else
unsigned long newlen;
#endif
{
    register char *s = str->str_ptr;

#ifdef MSDOS
    if (newlen >= 0x10000) {
	fprintf(stderr, "Allocation too large: %lx\n", newlen);
	exit(1);
    }
#endif /* MSDOS */
    if (str->str_state == SS_INCR) {		/* data before str_ptr? */
	str->str_len += str->str_u.str_useful;
	str->str_ptr -= str->str_u.str_useful;
	str->str_u.str_useful = 0L;
	Move(s, str->str_ptr, str->str_cur+1, char);
	s = str->str_ptr;
	str->str_state = SS_NORM;			/* normal again */
	if (newlen > str->str_len)
	    newlen += 10 * (newlen - str->str_cur); /* avoid copy each time */
    }
    if (newlen > str->str_len) {		/* need more room? */
        if (str->str_len)
	    Renew(s,newlen,char);
        else
	    New(703,s,newlen,char);
	str->str_ptr = s;
        str->str_len = newlen;
    }
    return s;
}

void
str_numset(str,num)
register STR *str;
double num;
{
    if (str->str_pok) {
	str->str_pok = 0;	/* invalidate pointer */
	if (str->str_state == SS_INCR)
	    Str_Grow(str,0);
    }
    str->str_u.str_nval = num;
    str->str_state = SS_NORM;
    str->str_nok = 1;			/* validate number */
#ifdef TAINT
    str->str_tainted = tainted;
#endif
}

char *
str_2ptr(str)
register STR *str;
{
    register char *s;
    int olderrno;

    if (!str)
	return "";
    if (str->str_nok) {
	STR_GROW(str, 30);
	s = str->str_ptr;
	olderrno = errno;	/* some Xenix systems wipe out errno here */
#if defined(scs) && defined(ns32000)
	gcvt(str->str_u.str_nval,20,s);
#else
#ifdef apollo
	if (str->str_u.str_nval == 0.0)
	    (void)strcpy(s,"0");
	else
#endif /*apollo*/
	(void)sprintf(s,"%.20g",str->str_u.str_nval);
#endif /*scs*/
	errno = olderrno;
	while (*s) s++;
#ifdef hcx
	if (s[-1] == '.')
	    s--;
#endif
    }
    else {
	if (str == &str_undef)
	    return No;
	if (dowarn)
	    warn("Use of uninitialized variable");
	STR_GROW(str, 30);
	s = str->str_ptr;
    }
    *s = '\0';
    str->str_cur = s - str->str_ptr;
    str->str_pok = 1;
#ifdef DEBUGGING
    if (debug & 32)
	fprintf(stderr,"0x%lx ptr(%s)\n",str,str->str_ptr);
#endif
    return str->str_ptr;
}

double
str_2num(str)
register STR *str;
{
    if (!str)
	return 0.0;
    if (str->str_state == SS_INCR)
	Str_Grow(str,0);       /* just force copy down */
    str->str_state = SS_NORM;
    if (str->str_len && str->str_pok)
	str->str_u.str_nval = atof(str->str_ptr);
    else  {
	if (str == &str_undef)
	    return 0.0;
	if (dowarn)
	    warn("Use of uninitialized variable");
	str->str_u.str_nval = 0.0;
    }
    str->str_nok = 1;
#ifdef DEBUGGING
    if (debug & 32)
	fprintf(stderr,"0x%lx num(%g)\n",str,str->str_u.str_nval);
#endif
    return str->str_u.str_nval;
}

/* Note: str_sset() should not be called with a source string that needs
 * be reused, since it may destroy the source string if it is marked
 * as temporary.
 */

void
str_sset(dstr,sstr)
STR *dstr;
register STR *sstr;
{
#ifdef TAINT
    if (sstr)
	tainted |= sstr->str_tainted;
#endif
    if (sstr == dstr || dstr == &str_undef)
	return;
    if (!sstr)
	dstr->str_pok = dstr->str_nok = 0;
    else if (sstr->str_pok) {

	/*
	 * Check to see if we can just swipe the string.  If so, it's a
	 * possible small lose on short strings, but a big win on long ones.
	 * It might even be a win on short strings if dstr->str_ptr
	 * has to be allocated and sstr->str_ptr has to be freed.
	 */

	if (sstr->str_pok & SP_TEMP) {		/* slated for free anyway? */
	    if (dstr->str_ptr) {
		if (dstr->str_state == SS_INCR)
		    dstr->str_ptr -= dstr->str_u.str_useful;
		Safefree(dstr->str_ptr);
	    }
	    dstr->str_ptr = sstr->str_ptr;
	    dstr->str_len = sstr->str_len;
	    dstr->str_cur = sstr->str_cur;
	    dstr->str_state = sstr->str_state;
	    dstr->str_pok = sstr->str_pok & ~SP_TEMP;
#ifdef TAINT
	    dstr->str_tainted = sstr->str_tainted;
#endif
	    sstr->str_ptr = Nullch;
	    sstr->str_len = 0;
	    sstr->str_pok = 0;			/* wipe out any weird flags */
	    sstr->str_state = 0;		/* so sstr frees uneventfully */
	}
	else {					/* have to copy actual string */
	    if (dstr->str_ptr) {
		if (dstr->str_state == SS_INCR) {
			Str_Grow(dstr,0);
		}
	    }
	    str_nset(dstr,sstr->str_ptr,sstr->str_cur);
	}
	/*SUPPRESS 560*/
	if (dstr->str_nok = sstr->str_nok)
	    dstr->str_u.str_nval = sstr->str_u.str_nval;
	else {
#ifdef STRUCTCOPY
	    dstr->str_u = sstr->str_u;
#else
	    dstr->str_u.str_nval = sstr->str_u.str_nval;
#endif
	    if (dstr->str_cur == sizeof(STBP)) {
		char *tmps = dstr->str_ptr;

		if (*tmps == 'S' && bcmp(tmps,"StB",4) == 0) {
		    if (dstr->str_magic && dstr->str_magic->str_rare == 'X') {
			str_free(dstr->str_magic);
			dstr->str_magic = Nullstr;
		    }
		    if (!dstr->str_magic) {
			dstr->str_magic = str_smake(sstr->str_magic);
			dstr->str_magic->str_rare = 'X';
		    }
		}
	    }
	}
    }
    else if (sstr->str_nok)
	str_numset(dstr,sstr->str_u.str_nval);
    else {
	if (dstr->str_state == SS_INCR)
	    Str_Grow(dstr,0);       /* just force copy down */

#ifdef STRUCTCOPY
	dstr->str_u = sstr->str_u;
#else
	dstr->str_u.str_nval = sstr->str_u.str_nval;
#endif
	dstr->str_pok = dstr->str_nok = 0;
    }
}

void
str_nset(str,ptr,len)
register STR *str;
register char *ptr;
register STRLEN len;
{
    if (str == &str_undef)
	return;
    STR_GROW(str, len + 1);
    if (ptr)
	Move(ptr,str->str_ptr,len,char);
    str->str_cur = len;
    *(str->str_ptr+str->str_cur) = '\0';
    str->str_nok = 0;		/* invalidate number */
    str->str_pok = 1;		/* validate pointer */
#ifdef TAINT
    str->str_tainted = tainted;
#endif
}

void
str_set(str,ptr)
register STR *str;
register char *ptr;
{
    register STRLEN len;

    if (str == &str_undef)
	return;
    if (!ptr)
	ptr = "";
    len = strlen(ptr);
    STR_GROW(str, len + 1);
    Move(ptr,str->str_ptr,len+1,char);
    str->str_cur = len;
    str->str_nok = 0;		/* invalidate number */
    str->str_pok = 1;		/* validate pointer */
#ifdef TAINT
    str->str_tainted = tainted;
#endif
}

void
str_chop(str,ptr)	/* like set but assuming ptr is in str */
register STR *str;
register char *ptr;
{
    register STRLEN delta;

    if (!ptr || !(str->str_pok))
	return;
    delta = ptr - str->str_ptr;
    str->str_len -= delta;
    str->str_cur -= delta;
    str->str_ptr += delta;
    if (str->str_state == SS_INCR)
	str->str_u.str_useful += delta;
    else {
	str->str_u.str_useful = delta;
	str->str_state = SS_INCR;
    }
    str->str_nok = 0;		/* invalidate number */
    str->str_pok = 1;		/* validate pointer (and unstudy str) */
}

void
str_ncat(str,ptr,len)
register STR *str;
register char *ptr;
register STRLEN len;
{
    if (str == &str_undef)
	return;
    if (!(str->str_pok))
	(void)str_2ptr(str);
    STR_GROW(str, str->str_cur + len + 1);
    Move(ptr,str->str_ptr+str->str_cur,len,char);
    str->str_cur += len;
    *(str->str_ptr+str->str_cur) = '\0';
    str->str_nok = 0;		/* invalidate number */
    str->str_pok = 1;		/* validate pointer */
#ifdef TAINT
    str->str_tainted |= tainted;
#endif
}

void
str_scat(dstr,sstr)
STR *dstr;
register STR *sstr;
{
    if (!sstr)
	return;
#ifdef TAINT
    tainted |= sstr->str_tainted;
#endif
    if (!(sstr->str_pok))
	(void)str_2ptr(sstr);
    if (sstr)
	str_ncat(dstr,sstr->str_ptr,sstr->str_cur);
}

void
str_cat(str,ptr)
register STR *str;
register char *ptr;
{
    register STRLEN len;

    if (str == &str_undef)
	return;
    if (!ptr)
	return;
    if (!(str->str_pok))
	(void)str_2ptr(str);
    len = strlen(ptr);
    STR_GROW(str, str->str_cur + len + 1);
    Move(ptr,str->str_ptr+str->str_cur,len+1,char);
    str->str_cur += len;
    str->str_nok = 0;		/* invalidate number */
    str->str_pok = 1;		/* validate pointer */
#ifdef TAINT
    str->str_tainted |= tainted;
#endif
}

char *
str_append_till(str,from,fromend,delim,keeplist)
register STR *str;
register char *from;
register char *fromend;
register int delim;
char *keeplist;
{
    register char *to;
    register STRLEN len;

    if (str == &str_undef)
	return Nullch;
    if (!from)
	return Nullch;
    len = fromend - from;
    STR_GROW(str, str->str_cur + len + 1);
    str->str_nok = 0;		/* invalidate number */
    str->str_pok = 1;		/* validate pointer */
    to = str->str_ptr+str->str_cur;
    for (; from < fromend; from++,to++) {
	if (*from == '\\' && from+1 < fromend && delim != '\\') {
	    if (!keeplist) {
		if (from[1] == delim || from[1] == '\\')
		    from++;
		else
		    *to++ = *from++;
	    }
	    else if (from[1] && index(keeplist,from[1]))
		*to++ = *from++;
	    else
		from++;
	}
	else if (*from == delim)
	    break;
	*to = *from;
    }
    *to = '\0';
    str->str_cur = to - str->str_ptr;
    return from;
}

STR *
#ifdef LEAKTEST
str_new(x,len)
int x;
#else
str_new(len)
#endif
STRLEN len;
{
    register STR *str;

    if (freestrroot) {
	str = freestrroot;
	freestrroot = str->str_magic;
	str->str_magic = Nullstr;
	str->str_state = SS_NORM;
    }
    else {
	Newz(700+x,str,1,STR);
    }
    if (len)
	STR_GROW(str, len + 1);
    return str;
}

void
str_magic(str, stab, how, name, namlen)
register STR *str;
STAB *stab;
int how;
char *name;
STRLEN namlen;
{
    if (str == &str_undef || str->str_magic)
	return;
    str->str_magic = Str_new(75,namlen);
    str = str->str_magic;
    str->str_u.str_stab = stab;
    str->str_rare = how;
    if (name)
	str_nset(str,name,namlen);
}

void
str_insert(bigstr,offset,len,little,littlelen)
STR *bigstr;
STRLEN offset;
STRLEN len;
char *little;
STRLEN littlelen;
{
    register char *big;
    register char *mid;
    register char *midend;
    register char *bigend;
    register int i;

    if (bigstr == &str_undef)
	return;
    bigstr->str_nok = 0;
    bigstr->str_pok = SP_VALID;	/* disable possible screamer */

    i = littlelen - len;
    if (i > 0) {			/* string might grow */
	STR_GROW(bigstr, bigstr->str_cur + i + 1);
	big = bigstr->str_ptr;
	mid = big + offset + len;
	midend = bigend = big + bigstr->str_cur;
	bigend += i;
	*bigend = '\0';
	while (midend > mid)		/* shove everything down */
	    *--bigend = *--midend;
	Move(little,big+offset,littlelen,char);
	bigstr->str_cur += i;
	STABSET(bigstr);
	return;
    }
    else if (i == 0) {
	Move(little,bigstr->str_ptr+offset,len,char);
	STABSET(bigstr);
	return;
    }

    big = bigstr->str_ptr;
    mid = big + offset;
    midend = mid + len;
    bigend = big + bigstr->str_cur;

    if (midend > bigend)
	fatal("panic: str_insert");

    if (mid - big > bigend - midend) {	/* faster to shorten from end */
	if (littlelen) {
	    Move(little, mid, littlelen,char);
	    mid += littlelen;
	}
	i = bigend - midend;
	if (i > 0) {
	    Move(midend, mid, i,char);
	    mid += i;
	}
	*mid = '\0';
	bigstr->str_cur = mid - big;
    }
    /*SUPPRESS 560*/
    else if (i = mid - big) {	/* faster from front */
	midend -= littlelen;
	mid = midend;
	str_chop(bigstr,midend-i);
	big += i;
	while (i--)
	    *--midend = *--big;
	if (littlelen)
	    Move(little, mid, littlelen,char);
    }
    else if (littlelen) {
	midend -= littlelen;
	str_chop(bigstr,midend);
	Move(little,midend,littlelen,char);
    }
    else {
	str_chop(bigstr,midend);
    }
    STABSET(bigstr);
}

/* make str point to what nstr did */

void
str_replace(str,nstr)
register STR *str;
register STR *nstr;
{
    if (str == &str_undef)
	return;
    if (str->str_state == SS_INCR)
	Str_Grow(str,0);	/* just force copy down */
    if (nstr->str_state == SS_INCR)
	Str_Grow(nstr,0);
    if (str->str_ptr)
	Safefree(str->str_ptr);
    str->str_ptr = nstr->str_ptr;
    str->str_len = nstr->str_len;
    str->str_cur = nstr->str_cur;
    str->str_pok = nstr->str_pok;
    str->str_nok = nstr->str_nok;
#ifdef STRUCTCOPY
    str->str_u = nstr->str_u;
#else
    str->str_u.str_nval = nstr->str_u.str_nval;
#endif
#ifdef TAINT
    str->str_tainted = nstr->str_tainted;
#endif
    if (nstr->str_magic)
	str_free(nstr->str_magic);
    Safefree(nstr);
}

void
str_free(str)
register STR *str;
{
    if (!str || str == &str_undef)
	return;
    if (str->str_state) {
	if (str->str_state == SS_FREE)	/* already freed */
	    return;
	if (str->str_state == SS_INCR && !(str->str_pok & 2)) {
	    str->str_ptr -= str->str_u.str_useful;
	    str->str_len += str->str_u.str_useful;
	}
    }
    if (str->str_magic)
	str_free(str->str_magic);
    str->str_magic = freestrroot;
#ifdef LEAKTEST
    if (str->str_len) {
	Safefree(str->str_ptr);
	str->str_ptr = Nullch;
    }
    if ((str->str_pok & SP_INTRP) && str->str_u.str_args)
	arg_free(str->str_u.str_args);
    Safefree(str);
#else /* LEAKTEST */
    if (str->str_len) {
	if (str->str_len > 127) {	/* next user not likely to want more */
	    Safefree(str->str_ptr);	/* so give it back to malloc */
	    str->str_ptr = Nullch;
	    str->str_len = 0;
	}
	else
	    str->str_ptr[0] = '\0';
    }
    if ((str->str_pok & SP_INTRP) && str->str_u.str_args)
	arg_free(str->str_u.str_args);
    str->str_cur = 0;
    str->str_nok = 0;
    str->str_pok = 0;
    str->str_state = SS_FREE;
#ifdef TAINT
    str->str_tainted = 0;
#endif
    freestrroot = str;
#endif /* LEAKTEST */
}

STRLEN
str_len(str)
register STR *str;
{
    if (!str)
	return 0;
    if (!(str->str_pok))
	(void)str_2ptr(str);
    if (str->str_ptr)
	return str->str_cur;
    else
	return 0;
}

int
str_eq(str1,str2)
register STR *str1;
register STR *str2;
{
    if (!str1 || str1 == &str_undef)
	return (str2 == Nullstr || str2 == &str_undef || !str2->str_cur);
    if (!str2 || str2 == &str_undef)
	return !str1->str_cur;

    if (!str1->str_pok)
	(void)str_2ptr(str1);
    if (!str2->str_pok)
	(void)str_2ptr(str2);

    if (str1->str_cur != str2->str_cur)
	return 0;

    return !bcmp(str1->str_ptr, str2->str_ptr, str1->str_cur);
}

int
str_cmp(str1,str2)
register STR *str1;
register STR *str2;
{
    int retval;

    if (!str1 || str1 == &str_undef)
	return (str2 == Nullstr || str2 == &str_undef || !str2->str_cur)?0:-1;
    if (!str2 || str2 == &str_undef)
	return str1->str_cur != 0;

    if (!str1->str_pok)
	(void)str_2ptr(str1);
    if (!str2->str_pok)
	(void)str_2ptr(str2);

    if (str1->str_cur < str2->str_cur) {
	/*SUPPRESS 560*/
	if (retval = memcmp(str1->str_ptr, str2->str_ptr, str1->str_cur))
	    return retval < 0 ? -1 : 1;
	else
	    return -1;
    }
    /*SUPPRESS 560*/
    else if (retval = memcmp(str1->str_ptr, str2->str_ptr, str2->str_cur))
	return retval < 0 ? -1 : 1;
    else if (str1->str_cur == str2->str_cur)
	return 0;
    else
	return 1;
}

char *
str_gets(str,fp,append)
register STR *str;
register FILE *fp;
int append;
{
    register char *bp;		/* we're going to steal some values */
    register int cnt;		/*  from the stdio struct and put EVERYTHING */
    register STDCHAR *ptr;	/*   in the innermost loop into registers */
    register int newline = rschar;/* (assuming >= 6 registers) */
    int i;
    STRLEN bpx;
    int shortbuffered;

    if (str == &str_undef)
	return Nullch;
    if (rspara) {		/* have to do this both before and after */
	do {			/* to make sure file boundaries work right */
	    i = getc(fp);
	    if (i != '\n') {
		ungetc(i,fp);
		break;
	    }
	} while (i != EOF);
    }
#ifdef STDSTDIO		/* Here is some breathtakingly efficient cheating */
    cnt = fp->_cnt;			/* get count into register */
    str->str_nok = 0;			/* invalidate number */
    str->str_pok = 1;			/* validate pointer */
    if (str->str_len - append <= cnt + 1) { /* make sure we have the room */
	if (cnt > 80 && str->str_len > append) {
	    shortbuffered = cnt - str->str_len + append + 1;
	    cnt -= shortbuffered;
	}
	else {
	    shortbuffered = 0;
	    STR_GROW(str, append+cnt+2);/* (remembering cnt can be -1) */
	}
    }
    else
	shortbuffered = 0;
    bp = str->str_ptr + append;		/* move these two too to registers */
    ptr = fp->_ptr;
    for (;;) {
      screamer:
	while (--cnt >= 0) {			/* this */	/* eat */
	    if ((*bp++ = *ptr++) == newline)	/* really */	/* dust */
		goto thats_all_folks;		/* screams */	/* sed :-) */
	}

	if (shortbuffered) {			/* oh well, must extend */
	    cnt = shortbuffered;
	    shortbuffered = 0;
	    bpx = bp - str->str_ptr;	/* prepare for possible relocation */
	    str->str_cur = bpx;
	    STR_GROW(str, str->str_len + append + cnt + 2);
	    bp = str->str_ptr + bpx;	/* reconstitute our pointer */
	    continue;
	}

	fp->_cnt = cnt;			/* deregisterize cnt and ptr */
	fp->_ptr = ptr;
	i = _filbuf(fp);		/* get more characters */
	cnt = fp->_cnt;
	ptr = fp->_ptr;			/* reregisterize cnt and ptr */

	bpx = bp - str->str_ptr;	/* prepare for possible relocation */
	str->str_cur = bpx;
	STR_GROW(str, bpx + cnt + 2);
	bp = str->str_ptr + bpx;	/* reconstitute our pointer */

	if (i == newline) {		/* all done for now? */
	    *bp++ = i;
	    goto thats_all_folks;
	}
	else if (i == EOF)		/* all done for ever? */
	    goto thats_really_all_folks;
	*bp++ = i;			/* now go back to screaming loop */
    }

thats_all_folks:
    if (rslen > 1 && (bp - str->str_ptr < rslen || bcmp(bp - rslen, rs, rslen)))
	goto screamer;	/* go back to the fray */
thats_really_all_folks:
    if (shortbuffered)
	cnt += shortbuffered;
    fp->_cnt = cnt;			/* put these back or we're in trouble */
    fp->_ptr = ptr;
    *bp = '\0';
    str->str_cur = bp - str->str_ptr;	/* set length */

#else /* !STDSTDIO */	/* The big, slow, and stupid way */

    {
	static char buf[8192];
	char * bpe = buf + sizeof(buf) - 3;

screamer:
	bp = buf;
	while ((i = getc(fp)) != EOF && (*bp++ = i) != newline && bp < bpe) ;

	if (append)
	    str_ncat(str, buf, bp - buf);
	else
	    str_nset(str, buf, bp - buf);
	if (i != EOF			/* joy */
	    &&
	    (i != newline
	     ||
	     (rslen > 1
	      &&
	      (str->str_cur < rslen
	       ||
	       bcmp(str->str_ptr + str->str_cur - rslen, rs, rslen)
	      )
	     )
	    )
	   )
	{
	    append = -1;
	    goto screamer;
	}
    }

#endif /* STDSTDIO */

    if (rspara) {
        while (i != EOF) {
	    i = getc(fp);
	    if (i != '\n') {
		ungetc(i,fp);
		break;
	    }
	}
    }
    return str->str_cur - append ? str->str_ptr : Nullch;
}

ARG *
parselist(str)
STR *str;
{
    register CMD *cmd;
    register ARG *arg;
    CMD *oldcurcmd = curcmd;
    int oldperldb = perldb;
    int retval;

    perldb = 0;
    str_sset(linestr,str);
    in_eval++;
    oldoldbufptr = oldbufptr = bufptr = str_get(linestr);
    bufend = bufptr + linestr->str_cur;
    if (++loop_ptr >= loop_max) {
        loop_max += 128;
        Renew(loop_stack, loop_max, struct loop);
    }
    loop_stack[loop_ptr].loop_label = "_EVAL_";
    loop_stack[loop_ptr].loop_sp = 0;
#ifdef DEBUGGING
    if (debug & 4) {
        deb("(Pushing label #%d _EVAL_)\n", loop_ptr);
    }
#endif
    if (setjmp(loop_stack[loop_ptr].loop_env)) {
	in_eval--;
	loop_ptr--;
	perldb = oldperldb;
	fatal("%s\n",stab_val(stabent("@",TRUE))->str_ptr);
    }
#ifdef DEBUGGING
    if (debug & 4) {
	char *tmps = loop_stack[loop_ptr].loop_label;
	deb("(Popping label #%d %s)\n",loop_ptr,
	    tmps ? tmps : "" );
    }
#endif
    loop_ptr--;
    error_count = 0;
    curcmd = &compiling;
    curcmd->c_line = oldcurcmd->c_line;
    retval = yyparse();
    curcmd = oldcurcmd;
    perldb = oldperldb;
    in_eval--;
    if (retval || error_count)
	fatal("Invalid component in string or format");
    cmd = eval_root;
    arg = cmd->c_expr;
    if (cmd->c_type != C_EXPR || cmd->c_next || arg->arg_type != O_LIST)
	fatal("panic: error in parselist %d %x %d", cmd->c_type,
	  cmd->c_next, arg ? arg->arg_type : -1);
    cmd->c_expr = Nullarg;
    cmd_free(cmd);
    eval_root = Nullcmd;
    return arg;
}

void
intrpcompile(src)
STR *src;
{
    register char *s = str_get(src);
    register char *send = s + src->str_cur;
    register STR *str;
    register char *t;
    STR *toparse;
    STRLEN len;
    register int brackets;
    register char *d;
    STAB *stab;
    char *checkpoint;
    int sawcase = 0;

    toparse = Str_new(76,0);
    str = Str_new(77,0);

    str_nset(str,"",0);
    str_nset(toparse,"",0);
    t = s;
    while (s < send) {
	if (*s == '\\' && s[1] && index("$@[{\\]}lLuUE",s[1])) {
	    str_ncat(str, t, s - t);
	    ++s;
	    if (isALPHA(*s)) {
		str_ncat(str, "$c", 2);
		sawcase = (*s != 'E');
	    }
	    else {
		if (*nointrp) {		/* in a regular expression */
		    if (*s == '@')	/* always strip \@ */ /*SUPPRESS 530*/
			;
		    else		/* don't strip \\, \[, \{ etc. */
			str_ncat(str,s-1,1);
		}
		str_ncat(str, "$b", 2);
	    }
	    str_ncat(str, s, 1);
	    ++s;
	    t = s;
	}
	else if (*s == '$' && s+1 < send && *nointrp && index(nointrp,s[1])) {
	    str_ncat(str, t, s - t);
	    str_ncat(str, "$b", 2);
	    str_ncat(str, s, 2);
	    s += 2;
	    t = s;
	}
	else if ((*s == '@' || *s == '$') && s+1 < send) {
	    str_ncat(str,t,s-t);
	    t = s;
	    if (*s == '$' && s[1] == '#' && (isALPHA(s[2]) || s[2] == '_'))
		s++;
	    s = scanident(s,send,tokenbuf,sizeof tokenbuf);
	    if (*t == '@' &&
	      (!(stab = stabent(tokenbuf,FALSE)) ||
		 (*s == '{' ? !stab_xhash(stab) : !stab_xarray(stab)) )) {
		str_ncat(str,"@",1);
		s = ++t;
		continue;	/* grandfather @ from old scripts */
	    }
	    str_ncat(str,"$a",2);
	    str_ncat(toparse,",",1);
	    if (t[1] != '{' && (*s == '['  || *s == '{' /* }} */ ) &&
	      (stab = stabent(tokenbuf,FALSE)) &&
	      ((*s == '[') ? (stab_xarray(stab) != 0) : (stab_xhash(stab) != 0)) ) {
		brackets = 0;
		checkpoint = s;
		do {
		    switch (*s) {
		    case '[':
			brackets++;
			break;
		    case '{':
			brackets++;
			break;
		    case ']':
			brackets--;
			break;
		    case '}':
			brackets--;
			break;
		    case '$':
		    case '%':
		    case '@':
		    case '&':
		    case '*':
			s = scanident(s,send,tokenbuf,sizeof tokenbuf);
			continue;
		    case '\'':
		    case '"':
			/*SUPPRESS 68*/
			s = cpytill(tokenbuf,s+1,send,*s,&len);
			if (s >= send)
			    fatal("Unterminated string");
			break;
		    }
		    s++;
		} while (brackets > 0 && s < send);
		if (s > send)
		    fatal("Unmatched brackets in string");
		if (*nointrp) {		/* we're in a regular expression */
		    d = checkpoint;
		    if (*d == '{' && s[-1] == '}') {	/* maybe {n,m} */
			++d;
			if (isDIGIT(*d)) {	/* matches /^{\d,?\d*}$/ */
			    if (*++d == ',')
				++d;
			    while (isDIGIT(*d))
				d++;
			    if (d == s - 1)
				s = checkpoint;		/* Is {n,m}! Backoff! */
			}
		    }
		    else if (*d == '[' && s[-1] == ']') { /* char class? */
			int weight = 2;		/* let's weigh the evidence */
			char seen[256];
			unsigned char un_char = 0, last_un_char;

			Zero(seen,256,char);
			*--s = '\0';
			if (d[1] == '^')
			    weight += 150;
			else if (d[1] == '$')
			    weight -= 3;
			if (isDIGIT(d[1])) {
			    if (d[2]) {
				if (isDIGIT(d[2]) && !d[3])
				    weight -= 10;
			    }
			    else
				weight -= 100;
			}
			for (d++; d < s; d++) {
			    last_un_char = un_char;
			    un_char = (unsigned char)*d;
			    switch (*d) {
			    case '&':
			    case '$':
				weight -= seen[un_char] * 10;
				if (isALNUM(d[1])) {
				    d = scanident(d,s,tokenbuf,sizeof tokenbuf);
				    if (stabent(tokenbuf,FALSE))
					weight -= 100;
				    else
					weight -= 10;
				}
				else if (*d == '$' && d[1] &&
				  index("[#!%*<>()-=",d[1])) {
				    if (!d[2] || /*{*/ index("])} =",d[2]))
					weight -= 10;
				    else
					weight -= 1;
				}
				break;
			    case '\\':
				un_char = 254;
				if (d[1]) {
				    if (index("wds",d[1]))
					weight += 100;
				    else if (seen['\''] || seen['"'])
					weight += 1;
				    else if (index("rnftb",d[1]))
					weight += 40;
				    else if (isDIGIT(d[1])) {
					weight += 40;
					while (d[1] && isDIGIT(d[1]))
					    d++;
				    }
				}
				else
				    weight += 100;
				break;
			    case '-':
				if (last_un_char < (unsigned char) d[1]
				  || d[1] == '\\') {
				    if (index("aA01! ",last_un_char))
					weight += 30;
				    if (index("zZ79~",d[1]))
					weight += 30;
				}
				else
				    weight -= 1;
			    default:
				if (isALPHA(*d) && d[1] && isALPHA(d[1])) {
				    bufptr = d;
				    if (yylex() != WORD)
					weight -= 150;
				    d = bufptr;
				}
				if (un_char == last_un_char + 1)
				    weight += 5;
				weight -= seen[un_char];
				break;
			    }
			    seen[un_char]++;
			}
#ifdef DEBUGGING
			if (debug & 512)
			    fprintf(stderr,"[%s] weight %d\n",
			      checkpoint+1,weight);
#endif
			*s++ = ']';
			if (weight >= 0)	/* probably a character class */
			    s = checkpoint;
		    }
		}
	    }
	    if (*t == '@')
		str_ncat(toparse, "join($\",", 8);
	    if (t[1] == '{' && s[-1] == '}') {
		str_ncat(toparse, t, 1);
		str_ncat(toparse, t+2, s - t - 3);
	    }
	    else
		str_ncat(toparse, t, s - t);
	    if (*t == '@')
		str_ncat(toparse, ")", 1);
	    t = s;
	}
	else
	    s++;
    }
    str_ncat(str,t,s-t);
    if (sawcase)
	str_ncat(str, "$cE", 3);
    if (toparse->str_ptr && *toparse->str_ptr == ',') {
	*toparse->str_ptr = '(';
	str_ncat(toparse,",$$);",5);
	str->str_u.str_args = parselist(toparse);
	str->str_u.str_args->arg_len--;		/* ignore $$ reference */
    }
    else
	str->str_u.str_args = Nullarg;
    str_free(toparse);
    str->str_pok |= SP_INTRP;
    str->str_nok = 0;
    str_replace(src,str);
}

STR *
interp(str,src,sp)
register STR *str;
STR *src;
int sp;
{
    register char *s;
    register char *t;
    register char *send;
    register STR **elem;
    int docase = 0;
    int l = 0;
    int u = 0;
    int L = 0;
    int U = 0;

    if (str == &str_undef)
	return Nullstr;
    if (!(src->str_pok & SP_INTRP)) {
	int oldsave = savestack->ary_fill;

	(void)savehptr(&curstash);
	curstash = curcmd->c_stash;	/* so stabent knows right package */
	intrpcompile(src);
	restorelist(oldsave);
    }
    s = src->str_ptr;		/* assumed valid since str_pok set */
    t = s;
    send = s + src->str_cur;

    if (src->str_u.str_args) {
	(void)eval(src->str_u.str_args,G_ARRAY,sp);
	/* Assuming we have correct # of args */
	elem = stack->ary_array + sp;
    }

    str_nset(str,"",0);
    while (s < send) {
	if (*s == '$' && s+1 < send) {
	    if (s-t > 0)
		str_ncat(str,t,s-t);
	    switch(*++s) {
	    default:
		fatal("panic: unknown interp cookie\n");
		break;
	    case 'a':
		str_scat(str,*++elem);
		break;
	    case 'b':
		str_ncat(str,++s,1);
		break;
	    case 'c':
		if (docase && str->str_cur >= docase) {
		    char *b = str->str_ptr + --docase;

		    if (L)
			lcase(b, str->str_ptr + str->str_cur);
		    else if (U)
			ucase(b, str->str_ptr + str->str_cur);

		    if (u)	/* note that l & u are independent of L & U */
			ucase(b, b+1);
		    else if (l)
			lcase(b, b+1);
		    l = u = 0;
		}
		docase = str->str_cur + 1;
		switch (*++s) {
		case 'u':
		    u = 1;
		    l = 0;
		    break;
		case 'U':
		    U = 1;
		    L = 0;
		    break;
		case 'l':
		    l = 1;
		    u = 0;
		    break;
		case 'L':
		    L = 1;
		    U = 0;
		    break;
		case 'E':
		    docase = L = U = l = u = 0;
		    break;
		}
		break;
	    }
	    t = ++s;
	}
	else
	    s++;
    }
    if (s-t > 0)
	str_ncat(str,t,s-t);
    return str;
}

static void
ucase(s,send)
register char *s;
register char *send;
{
    while (s < send) {
	if (isLOWER(*s))
	    *s = toupper(*s);
	s++;
    }
}

static void
lcase(s,send)
register char *s;
register char *send;
{
    while (s < send) {
	if (isUPPER(*s))
	    *s = tolower(*s);
	s++;
    }
}

void
str_inc(str)
register STR *str;
{
    register char *d;

    if (!str || str == &str_undef)
	return;
    if (str->str_nok) {
	str->str_u.str_nval += 1.0;
	str->str_pok = 0;
	return;
    }
    if (!str->str_pok || !*str->str_ptr) {
	str->str_u.str_nval = 1.0;
	str->str_nok = 1;
	str->str_pok = 0;
	return;
    }
    d = str->str_ptr;
    while (isALPHA(*d)) d++;
    while (isDIGIT(*d)) d++;
    if (*d) {
        str_numset(str,atof(str->str_ptr) + 1.0);  /* punt */
	return;
    }
    d--;
    while (d >= str->str_ptr) {
	if (isDIGIT(*d)) {
	    if (++*d <= '9')
		return;
	    *(d--) = '0';
	}
	else {
	    ++*d;
	    if (isALPHA(*d))
		return;
	    *(d--) -= 'z' - 'a' + 1;
	}
    }
    /* oh,oh, the number grew */
    STR_GROW(str, str->str_cur + 2);
    str->str_cur++;
    for (d = str->str_ptr + str->str_cur; d > str->str_ptr; d--)
	*d = d[-1];
    if (isDIGIT(d[1]))
	*d = '1';
    else
	*d = d[1];
}

void
str_dec(str)
register STR *str;
{
    if (!str || str == &str_undef)
	return;
    if (str->str_nok) {
	str->str_u.str_nval -= 1.0;
	str->str_pok = 0;
	return;
    }
    if (!str->str_pok) {
	str->str_u.str_nval = -1.0;
	str->str_nok = 1;
	return;
    }
    str_numset(str,atof(str->str_ptr) - 1.0);
}

/* Make a string that will exist for the duration of the expression
 * evaluation.  Actually, it may have to last longer than that, but
 * hopefully cmd_exec won't free it until it has been assigned to a
 * permanent location. */

static long tmps_size = -1;

STR *
str_mortal(oldstr)
STR *oldstr;
{
    register STR *str = Str_new(78,0);

    str_sset(str,oldstr);
    if (++tmps_max > tmps_size) {
	tmps_size = tmps_max;
	if (!(tmps_size & 127)) {
	    if (tmps_size)
		Renew(tmps_list, tmps_size + 128, STR*);
	    else
		New(702,tmps_list, 128, STR*);
	}
    }
    tmps_list[tmps_max] = str;
    if (str->str_pok)
	str->str_pok |= SP_TEMP;
    return str;
}

/* same thing without the copying */

STR *
str_2mortal(str)
register STR *str;
{
    if (!str || str == &str_undef)
	return str;
    if (++tmps_max > tmps_size) {
	tmps_size = tmps_max;
	if (!(tmps_size & 127)) {
	    if (tmps_size)
		Renew(tmps_list, tmps_size + 128, STR*);
	    else
		New(704,tmps_list, 128, STR*);
	}
    }
    tmps_list[tmps_max] = str;
    if (str->str_pok)
	str->str_pok |= SP_TEMP;
    return str;
}

STR *
str_make(s,len)
char *s;
STRLEN len;
{
    register STR *str = Str_new(79,0);

    if (!len)
	len = strlen(s);
    str_nset(str,s,len);
    return str;
}

STR *
str_nmake(n)
double n;
{
    register STR *str = Str_new(80,0);

    str_numset(str,n);
    return str;
}

/* make an exact duplicate of old */

STR *
str_smake(old)
register STR *old;
{
    register STR *new = Str_new(81,0);

    if (!old)
	return Nullstr;
    if (old->str_state == SS_FREE) {
	warn("semi-panic: attempt to dup freed string");
	return Nullstr;
    }
    if (old->str_state == SS_INCR && !(old->str_pok & 2))
	Str_Grow(old,0);
    if (new->str_ptr)
	Safefree(new->str_ptr);
    StructCopy(old,new,STR);
    if (old->str_ptr) {
	new->str_ptr = nsavestr(old->str_ptr,old->str_len);
	new->str_pok &= ~SP_TEMP;
    }
    return new;
}

void
str_reset(s,stash)
register char *s;
HASH *stash;
{
    register HENT *entry;
    register STAB *stab;
    register STR *str;
    register int i;
    register SPAT *spat;
    register int max;

    if (!*s) {		/* reset ?? searches */
	for (spat = stash->tbl_spatroot;
	  spat != Nullspat;
	  spat = spat->spat_next) {
	    spat->spat_flags &= ~SPAT_USED;
	}
	return;
    }

    /* reset variables */

    if (!stash->tbl_array)
	return;
    while (*s) {
	i = *s;
	if (s[1] == '-') {
	    s += 2;
	}
	max = *s++;
	for ( ; i <= max; i++) {
	    for (entry = stash->tbl_array[i];
	      entry;
	      entry = entry->hent_next) {
		stab = (STAB*)entry->hent_val;
		str = stab_val(stab);
		str->str_cur = 0;
		str->str_nok = 0;
#ifdef TAINT
		str->str_tainted = tainted;
#endif
		if (str->str_ptr != Nullch)
		    str->str_ptr[0] = '\0';
		if (stab_xarray(stab)) {
		    aclear(stab_xarray(stab));
		}
		if (stab_xhash(stab)) {
		    hclear(stab_xhash(stab), FALSE);
		    if (stab == envstab)
			environ[0] = Nullch;
		}
	    }
	}
    }
}

#ifdef TAINT
void
taintproper(s)
char *s;
{
#ifdef DEBUGGING
    if (debug & 2048)
	fprintf(stderr,"%s %d %d %d\n",s,tainted,uid, euid);
#endif
    if (tainted && (!euid || euid != uid || egid != gid || taintanyway)) {
	if (!unsafe)
	    fatal("%s", s);
	else if (dowarn)
	    warn("%s", s);
    }
}

void
taintenv()
{
    register STR *envstr;

    envstr = hfetch(stab_hash(envstab),"PATH",4,FALSE);
    if (envstr == &str_undef || envstr->str_tainted) {
	tainted = 1;
	if (envstr->str_tainted == 2)
	    taintproper("Insecure directory in PATH");
	else
	    taintproper("Insecure PATH");
    }
    envstr = hfetch(stab_hash(envstab),"IFS",3,FALSE);
    if (envstr != &str_undef && envstr->str_tainted) {
	tainted = 1;
	taintproper("Insecure IFS");
    }
}
#endif /* TAINT */
