/*
 * builtin.c - Builtin functions and various utility procedures 
 */

/* 
 * Copyright (C) 1986, 1988, 1989 the Free Software Foundation, Inc.
 * 
 * This file is part of GAWK, the GNU implementation of the
 * AWK Progamming Language.
 * 
 * GAWK is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 1, or (at your option)
 * any later version.
 * 
 * GAWK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with GAWK; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "awk.h"

extern void srandom();
extern char *initstate();
extern char *setstate();
extern long random();

extern NODE **fields_arr;

static void get_one();
static void get_two();
static int get_three();

/* Builtin functions */
NODE *
do_exp(tree)
NODE *tree;
{
	NODE *tmp;
	double d, res;
	double exp();

	get_one(tree, &tmp);
	d = force_number(tmp);
	free_temp(tmp);
	errno = 0;
	res = exp(d);
	if (errno == ERANGE)
		warning("exp argument %g is out of range", d);
	return tmp_number((AWKNUM) res);
}

NODE *
do_index(tree)
NODE *tree;
{
	NODE *s1, *s2;
	register char *p1, *p2;
	register int l1, l2;
	long ret;


	get_two(tree, &s1, &s2);
	force_string(s1);
	force_string(s2);
	p1 = s1->stptr;
	p2 = s2->stptr;
	l1 = s1->stlen;
	l2 = s2->stlen;
	ret = 0;
	if (! strict && IGNORECASE_node->var_value->numbr != 0.0) {
		while (l1) {
			if (casetable[*p1] == casetable[*p2]
			    && strncasecmp(p1, p2, l2) == 0) {
				ret = 1 + s1->stlen - l1;
				break;
			}
			l1--;
			p1++;
		}
	} else {
		while (l1) {
			if (STREQN(p1, p2, l2)) {
				ret = 1 + s1->stlen - l1;
				break;
			}
			l1--;
			p1++;
		}
	}
	free_temp(s1);
	free_temp(s2);
	return tmp_number((AWKNUM) ret);
}

NODE *
do_int(tree)
NODE *tree;
{
	NODE *tmp;
	double floor();
	double d;

	get_one(tree, &tmp);
	d = floor((double)force_number(tmp));
	free_temp(tmp);
	return tmp_number((AWKNUM) d);
}

NODE *
do_length(tree)
NODE *tree;
{
	NODE *tmp;
	int len;

	get_one(tree, &tmp);
	len = force_string(tmp)->stlen;
	free_temp(tmp);
	return tmp_number((AWKNUM) len);
}

NODE *
do_log(tree)
NODE *tree;
{
	NODE *tmp;
	double log();
	double d, arg;

	get_one(tree, &tmp);
	arg = (double) force_number(tmp);
	if (arg < 0.0)
		warning("log called with negative argument %g", arg);
	d = log(arg);
	free_temp(tmp);
	return tmp_number((AWKNUM) d);
}

/*
 * Note that the output buffer cannot be static because sprintf may get
 * called recursively by force_string.  Hence the wasteful alloca calls 
 */

/* %e and %f formats are not properly implemented.  Someone should fix them */
NODE *
do_sprintf(tree)
NODE *tree;
{
#define bchunk(s,l) if(l) {\
    while((l)>ofre) {\
      char *tmp;\
      tmp=(char *)alloca(osiz*2);\
      memcpy(tmp,obuf,olen);\
      obuf=tmp;\
      ofre+=osiz;\
      osiz*=2;\
    }\
    memcpy(obuf+olen,s,(l));\
    olen+=(l);\
    ofre-=(l);\
  }

	/* Is there space for something L big in the buffer? */
#define chksize(l)  if((l)>ofre) {\
    char *tmp;\
    tmp=(char *)alloca(osiz*2);\
    memcpy(tmp,obuf,olen);\
    obuf=tmp;\
    ofre+=osiz;\
    osiz*=2;\
  }

	/*
	 * Get the next arg to be formatted.  If we've run out of args,
	 * return "" (Null string) 
	 */
#define parse_next_arg() {\
  if(!carg) arg= Nnull_string;\
  else {\
  	get_one(carg,&arg);\
	carg=carg->rnode;\
  }\
 }

	char *obuf;
	int osiz, ofre, olen;
	static char chbuf[] = "0123456789abcdef";
	static char sp[] = " ";
	char *s0, *s1;
	int n0;
	NODE *sfmt, *arg;
	register NODE *carg;
	long fw, prec, lj, alt, big;
	long *cur;
	long val;
#ifdef sun386			/* Can't cast unsigned (int/long) from ptr->value */
	long tmp_uval;		/* on 386i 4.0.1 C compiler -- it just hangs */
#endif
	unsigned long uval;
	int sgn;
	int base;
	char cpbuf[30];		/* if we have numbers bigger than 30 */
	char *cend = &cpbuf[30];/* chars, we lose, but seems unlikely */
	char *cp;
	char *fill;
	double tmpval;
	char *pr_str;
	int ucasehex = 0;
	extern char *gcvt();


	obuf = (char *) alloca(120);
	osiz = 120;
	ofre = osiz;
	olen = 0;
	get_one(tree, &sfmt);
	sfmt = force_string(sfmt);
	carg = tree->rnode;
	for (s0 = s1 = sfmt->stptr, n0 = sfmt->stlen; n0-- > 0;) {
		if (*s1 != '%') {
			s1++;
			continue;
		}
		bchunk(s0, s1 - s0);
		s0 = s1;
		cur = &fw;
		fw = 0;
		prec = 0;
		lj = alt = big = 0;
		fill = sp;
		cp = cend;
		s1++;

retry:
		--n0;
		switch (*s1++) {
		case '%':
			bchunk("%", 1);
			s0 = s1;
			break;

		case '0':
			if (fill != sp || lj)
				goto lose;
			if (cur == &fw)
				fill = "0";	/* FALL through */
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			if (cur == 0)
				goto lose;
			*cur = s1[-1] - '0';
			while (n0 > 0 && *s1 >= '0' && *s1 <= '9') {
				--n0;
				*cur = *cur * 10 + *s1++ - '0';
			}
			goto retry;
#ifdef not_yet
		case ' ':		/* print ' ' or '-' */
		case '+':		/* print '+' or '-' */
#endif
		case '-':
			if (lj || fill != sp)
				goto lose;
			lj++;
			goto retry;
		case '.':
			if (cur != &fw)
				goto lose;
			cur = &prec;
			goto retry;
		case '#':
			if (alt)
				goto lose;
			alt++;
			goto retry;
		case 'l':
			if (big)
				goto lose;
			big++;
			goto retry;
		case 'c':
			parse_next_arg();
			if (arg->flags & NUMERIC) {
#ifdef sun386
				tmp_uval = arg->numbr; 
				uval= (unsigned long) tmp_uval;
#else
				uval = (unsigned long) arg->numbr;
#endif
				cpbuf[0] = uval;
				prec = 1;
				pr_str = cpbuf;
				goto dopr_string;
			}
			if (! prec)
				prec = 1;
			else if (prec > arg->stlen)
				prec = arg->stlen;
			pr_str = arg->stptr;
			goto dopr_string;
		case 's':
			parse_next_arg();
			arg = force_string(arg);
			if (!prec || prec > arg->stlen)
				prec = arg->stlen;
			pr_str = arg->stptr;

	dopr_string:
			if (fw > prec && !lj) {
				while (fw > prec) {
					bchunk(sp, 1);
					fw--;
				}
			}
			bchunk(pr_str, (int) prec);
			if (fw > prec) {
				while (fw > prec) {
					bchunk(sp, 1);
					fw--;
				}
			}
			s0 = s1;
			free_temp(arg);
			break;
		case 'd':
		case 'i':
			parse_next_arg();
			val = (long) force_number(arg);
			free_temp(arg);
			if (val < 0) {
				sgn = 1;
				val = -val;
			} else
				sgn = 0;
			do {
				*--cp = '0' + val % 10;
				val /= 10;
			} while (val);
			if (sgn)
				*--cp = '-';
			if (prec > fw)
				fw = prec;
			prec = cend - cp;
			if (fw > prec && !lj) {
				if (fill != sp && *cp == '-') {
					bchunk(cp, 1);
					cp++;
					prec--;
					fw--;
				}
				while (fw > prec) {
					bchunk(fill, 1);
					fw--;
				}
			}
			bchunk(cp, (int) prec);
			if (fw > prec) {
				while (fw > prec) {
					bchunk(fill, 1);
					fw--;
				}
			}
			s0 = s1;
			break;
		case 'u':
			base = 10;
			goto pr_unsigned;
		case 'o':
			base = 8;
			goto pr_unsigned;
		case 'X':
			ucasehex = 1;
		case 'x':
			base = 16;
			goto pr_unsigned;
	pr_unsigned:
			parse_next_arg();
			uval = (unsigned long) force_number(arg);
			free_temp(arg);
			do {
				*--cp = chbuf[uval % base];
				if (ucasehex && isalpha(*cp))
					*cp = toupper(*cp);
				uval /= base;
			} while (uval);
			if (alt && (base == 8 || base == 16)) {
				if (base == 16) {
					if (ucasehex)
						*--cp = 'X';
					else
						*--cp = 'x';
				}
				*--cp = '0';
			}
			prec = cend - cp;
			if (fw > prec && !lj) {
				while (fw > prec) {
					bchunk(fill, 1);
					fw--;
				}
			}
			bchunk(cp, (int) prec);
			if (fw > prec) {
				while (fw > prec) {
					bchunk(fill, 1);
					fw--;
				}
			}
			s0 = s1;
			break;
		case 'g':
			parse_next_arg();
			tmpval = force_number(arg);
			free_temp(arg);
			if (prec == 0)
				prec = 13;
			(void) gcvt(tmpval, (int) prec, cpbuf);
			prec = strlen(cpbuf);
			cp = cpbuf;
			if (fw > prec && !lj) {
				if (fill != sp && *cp == '-') {
					bchunk(cp, 1);
					cp++;
					prec--;
				}	/* Deal with .5 as 0.5 */
				if (fill == sp && *cp == '.') {
					--fw;
					while (--fw >= prec) {
						bchunk(fill, 1);
					}
					bchunk("0", 1);
				} else
					while (fw-- > prec)
						bchunk(fill, 1);
			} else {/* Turn .5 into 0.5 */
				/* FOO */
				if (*cp == '.' && fill == sp) {
					bchunk("0", 1);
					--fw;
				}
			}
			bchunk(cp, (int) prec);
			if (fw > prec)
				while (fw-- > prec)
					bchunk(fill, 1);
			s0 = s1;
			break;
		case 'f':
			parse_next_arg();
			tmpval = force_number(arg);
			free_temp(arg);
			chksize(fw + prec + 5);	/* 5==slop */

			cp = cpbuf;
			*cp++ = '%';
			if (lj)
				*cp++ = '-';
			if (fill != sp)
				*cp++ = '0';
			if (cur != &fw) {
				(void) strcpy(cp, "*.*f");
				(void) sprintf(obuf + olen, cpbuf, (int) fw, (int) prec, (double) tmpval);
			} else {
				(void) strcpy(cp, "*f");
				(void) sprintf(obuf + olen, cpbuf, (int) fw, (double) tmpval);
			}
			ofre -= strlen(obuf + olen);
			olen += strlen(obuf + olen);	/* There may be nulls */
			s0 = s1;
			break;
		case 'e':
			parse_next_arg();
			tmpval = force_number(arg);
			free_temp(arg);
			chksize(fw + prec + 5);	/* 5==slop */
			cp = cpbuf;
			*cp++ = '%';
			if (lj)
				*cp++ = '-';
			if (fill != sp)
				*cp++ = '0';
			if (cur != &fw) {
				(void) strcpy(cp, "*.*e");
				(void) sprintf(obuf + olen, cpbuf, (int) fw, (int) prec, (double) tmpval);
			} else {
				(void) strcpy(cp, "*e");
				(void) sprintf(obuf + olen, cpbuf, (int) fw, (double) tmpval);
			}
			ofre -= strlen(obuf + olen);
			olen += strlen(obuf + olen);	/* There may be nulls */
			s0 = s1;
			break;

		default:
	lose:
			break;
		}
	}
	bchunk(s0, s1 - s0);
	free_temp(sfmt);
	return tmp_string(obuf, olen);
}

void
do_printf(tree)
NODE *tree;
{
	struct redirect *rp = NULL;
	register FILE *fp = stdout;
	int errflg = 0;		/* not used, sigh */

	if (tree->rnode) {
		rp = redirect(tree->rnode, &errflg);
		if (rp)
			fp = rp->fp;
	}
	if (fp)
		print_simple(do_sprintf(tree->lnode), fp);
	if (rp && (rp->flag & RED_NOBUF))
		fflush(fp);
}

NODE *
do_sqrt(tree)
NODE *tree;
{
	NODE *tmp;
	double sqrt();
	double d, arg;

	get_one(tree, &tmp);
	arg = (double) force_number(tmp);
	if (arg < 0.0)
		warning("sqrt called with negative argument %g", arg);
	d = sqrt(arg);
	free_temp(tmp);
	return tmp_number((AWKNUM) d);
}

NODE *
do_substr(tree)
NODE *tree;
{
	NODE *t1, *t2, *t3;
	NODE *r;
	register int indx, length;

	t1 = t2 = t3 = NULL;
	length = -1;
	if (get_three(tree, &t1, &t2, &t3) == 3)
		length = (int) force_number(t3);
	indx = (int) force_number(t2) - 1;
	t1 = force_string(t1);
	if (length == -1)
		length = t1->stlen;
	if (indx < 0)
		indx = 0;
	if (indx >= t1->stlen || length <= 0) {
		if (t3)
			free_temp(t3);
		free_temp(t2);
		free_temp(t1);
		return Nnull_string;
	}
	if (indx + length > t1->stlen)
		length = t1->stlen - indx;
	if (t3)
		free_temp(t3);
	free_temp(t2);
	r =  tmp_string(t1->stptr + indx, length);
	free_temp(t1);
	return r;
}

NODE *
do_system(tree)
NODE *tree;
{
#if defined(unix) || defined(MSDOS) /* || defined(gnu) */
	NODE *tmp;
	int ret;

	(void) flush_io ();	/* so output is synchronous with gawk's */
	get_one(tree, &tmp);
	ret = system(force_string(tmp)->stptr);
	ret = (ret >> 8) & 0xff;
	free_temp(tmp);
	return tmp_number((AWKNUM) ret);
#else
	fatal("the \"system\" function is not supported.");
	/* NOTREACHED */
#endif
}

void 
do_print(tree)
register NODE *tree;
{
	struct redirect *rp = NULL;
	register FILE *fp = stdout;
	int errflg = 0;		/* not used, sigh */

	if (tree->rnode) {
		rp = redirect(tree->rnode, &errflg);
		if (rp)
			fp = rp->fp;
	}
	if (!fp)
		return;
	tree = tree->lnode;
	if (!tree)
		tree = WHOLELINE;
	if (tree->type != Node_expression_list) {
		if (!(tree->flags & STR))
			cant_happen();
		print_simple(tree, fp);
	} else {
		while (tree) {
			print_simple(force_string(tree_eval(tree->lnode)), fp);
			tree = tree->rnode;
			if (tree)
				print_simple(OFS_node->var_value, fp);
		}
	}
	print_simple(ORS_node->var_value, fp);
	if (rp && (rp->flag & RED_NOBUF))
		fflush(fp);
}

NODE *
do_tolower(tree)
NODE *tree;
{
	NODE *t1, *t2;
	register char *cp, *cp2;

	get_one(tree, &t1);
	t1 = force_string(t1);
	t2 = tmp_string(t1->stptr, t1->stlen);
	for (cp = t2->stptr, cp2 = t2->stptr + t2->stlen; cp < cp2; cp++)
		if (isupper(*cp))
			*cp = tolower(*cp);
	free_temp(t1);
	return t2;
}

NODE *
do_toupper(tree)
NODE *tree;
{
	NODE *t1, *t2;
	register char *cp;

	get_one(tree, &t1);
	t1 = force_string(t1);
	t2 = tmp_string(t1->stptr, t1->stlen);
	for (cp = t2->stptr; cp < t2->stptr + t2->stlen; cp++)
		if (islower(*cp))
			*cp = toupper(*cp);
	free_temp(t1);
	return t2;
}

/*
 * Get the arguments to functions.  No function cares if you give it too many
 * args (they're ignored).  Only a few fuctions complain about being given
 * too few args.  The rest have defaults.
 */

static void
get_one(tree, res)
NODE *tree, **res;
{
	if (!tree) {
		*res = WHOLELINE;
		return;
	}
	*res = tree_eval(tree->lnode);
}

static void
get_two(tree, res1, res2)
NODE *tree, **res1, **res2;
{
	if (!tree) {
		*res1 = WHOLELINE;
		return;
	}
	*res1 = tree_eval(tree->lnode);
	if (!tree->rnode)
		return;
	tree = tree->rnode;
	*res2 = tree_eval(tree->lnode);
}

static int
get_three(tree, res1, res2, res3)
NODE *tree, **res1, **res2, **res3;
{
	if (!tree) {
		*res1 = WHOLELINE;
		return 0;
	}
	*res1 = tree_eval(tree->lnode);
	if (!tree->rnode)
		return 1;
	tree = tree->rnode;
	*res2 = tree_eval(tree->lnode);
	if (!tree->rnode)
		return 2;
	tree = tree->rnode;
	*res3 = tree_eval(tree->lnode);
	return 3;
}

int
a_get_three(tree, res1, res2, res3)
NODE *tree, **res1, **res2, **res3;
{
	if (!tree) {
		*res1 = WHOLELINE;
		return 0;
	}
	*res1 = tree_eval(tree->lnode);
	if (!tree->rnode)
		return 1;
	tree = tree->rnode;
	*res2 = tree->lnode;
	if (!tree->rnode)
		return 2;
	tree = tree->rnode;
	*res3 = tree_eval(tree->lnode);
	return 3;
}

void
print_simple(tree, fp)
NODE *tree;
FILE *fp;
{
	if (fwrite(tree->stptr, sizeof(char), tree->stlen, fp) != tree->stlen)
		warning("fwrite: %s", strerror(errno));
	free_temp(tree);
}

NODE *
do_atan2(tree)
NODE *tree;
{
	NODE *t1, *t2;
	extern double atan2();
	double d1, d2;

	get_two(tree, &t1, &t2);
	d1 = force_number(t1);
	d2 = force_number(t2);
	free_temp(t1);
	free_temp(t2);
	return tmp_number((AWKNUM) atan2(d1, d2));
}

NODE *
do_sin(tree)
NODE *tree;
{
	NODE *tmp;
	extern double sin();
	double d;

	get_one(tree, &tmp);
	d = sin((double)force_number(tmp));
	free_temp(tmp);
	return tmp_number((AWKNUM) d);
}

NODE *
do_cos(tree)
NODE *tree;
{
	NODE *tmp;
	extern double cos();
	double d;

	get_one(tree, &tmp);
	d = cos((double)force_number(tmp));
	free_temp(tmp);
	return tmp_number((AWKNUM) d);
}

static int firstrand = 1;
static char state[256];

#define	MAXLONG	2147483647	/* maximum value for long int */

/* ARGSUSED */
NODE *
do_rand(tree)
NODE *tree;
{
	if (firstrand) {
		(void) initstate((unsigned) 1, state, sizeof state);
		srandom(1);
		firstrand = 0;
	}
	return tmp_number((AWKNUM) random() / MAXLONG);
}

NODE *
do_srand(tree)
NODE *tree;
{
	NODE *tmp;
	static long save_seed = 1;
	long ret = save_seed;	/* SVR4 awk srand returns previous seed */
	extern long time();

	if (firstrand)
		(void) initstate((unsigned) 1, state, sizeof state);
	else
		(void) setstate(state);

	if (!tree)
		srandom((int) (save_seed = time((long *) 0)));
	else {
		get_one(tree, &tmp);
		srandom((int) (save_seed = (long) force_number(tmp)));
		free_temp(tmp);
	}
	firstrand = 0;
	return tmp_number((AWKNUM) ret);
}

NODE *
do_match(tree)
NODE *tree;
{
	NODE *t1;
	int rstart;
	struct re_registers reregs;
	struct re_pattern_buffer *rp;
	int need_to_free = 0;

	t1 = force_string(tree_eval(tree->lnode));
	tree = tree->rnode;
	if (tree == NULL || tree->lnode == NULL)
		fatal("match called with only one argument");
	tree = tree->lnode;
	if (tree->type == Node_regex) {
		rp = tree->rereg;
		if (!strict && ((IGNORECASE_node->var_value->numbr != 0)
		    ^ (tree->re_case != 0))) {
			/* recompile since case sensitivity differs */
			rp = tree->rereg =
				mk_re_parse(tree->re_text,
				(IGNORECASE_node->var_value->numbr != 0));
			tree->re_case =
				(IGNORECASE_node->var_value->numbr != 0);
		}
	} else {
		need_to_free = 1;
		rp = make_regexp(force_string(tree_eval(tree)),
				(IGNORECASE_node->var_value->numbr != 0));
		if (rp == NULL)
			cant_happen();
	}
	rstart = re_search(rp, t1->stptr, t1->stlen, 0, t1->stlen, &reregs);
	free_temp(t1);
	if (rstart >= 0) {
		rstart++;	/* 1-based indexing */
		/* RSTART set to rstart below */
		RLENGTH_node->var_value->numbr =
			(AWKNUM) (reregs.end[0] - reregs.start[0]);
	} else {
		/*
		 * Match failed. Set RSTART to 0, RLENGTH to -1.
		 * Return the value of RSTART.
		 */
		rstart = 0;	/* used as return value */
		RLENGTH_node->var_value->numbr = -1.0;
	}
	RSTART_node->var_value->numbr = (AWKNUM) rstart;
	if (need_to_free) {
		free(rp->buffer);
		free(rp->fastmap);
		free((char *) rp);
	}
	return tmp_number((AWKNUM) rstart);
}

static NODE *
sub_common(tree, global)
NODE *tree;
int global;
{
	register int len;
	register char *scan;
	register char *bp, *cp;
	int search_start = 0;
	int match_length;
	int matches = 0;
	char *buf;
	struct re_pattern_buffer *rp;
	NODE *s;		/* subst. pattern */
	NODE *t;		/* string to make sub. in; $0 if none given */
	struct re_registers reregs;
	unsigned int saveflags;
	NODE *tmp;
	NODE **lhs;
	char *lastbuf;
	int need_to_free = 0;

	if (tree == NULL)
		fatal("sub or gsub called with 0 arguments");
	tmp = tree->lnode;
	if (tmp->type == Node_regex) {
		rp = tmp->rereg;
		if (! strict && ((IGNORECASE_node->var_value->numbr != 0)
		    ^ (tmp->re_case != 0))) {
			/* recompile since case sensitivity differs */
			rp = tmp->rereg =
				mk_re_parse(tmp->re_text,
				(IGNORECASE_node->var_value->numbr != 0));
			tmp->re_case = (IGNORECASE_node->var_value->numbr != 0);
		}
	} else {
		need_to_free = 1;
		rp = make_regexp(force_string(tree_eval(tmp)),
				(IGNORECASE_node->var_value->numbr != 0));
		if (rp == NULL)
			cant_happen();
	}
	tree = tree->rnode;
	if (tree == NULL)
		fatal("sub or gsub called with only 1 argument");
	s = force_string(tree_eval(tree->lnode));
	tree = tree->rnode;
	deref = 0;
	field_num = -1;
	if (tree == NULL) {
		t = node0_valid ? fields_arr[0] : *get_field(0, 0);
		lhs = &fields_arr[0];
		field_num = 0;
		deref = t;
	} else {
		t = tree->lnode;
		lhs = get_lhs(t, 1);
		t = force_string(tree_eval(t));
	}
	/*
	 * create a private copy of the string
	 */
	if (t->stref > 1 || (t->flags & PERM)) {
		saveflags = t->flags;
		t->flags &= ~MALLOC;
		tmp = dupnode(t);
		t->flags = saveflags;
		do_deref();
		t = tmp;
		if (lhs)
			*lhs = tmp;
	}
	lastbuf = t->stptr;
	do {
		if (re_search(rp, t->stptr, t->stlen, search_start,
		    t->stlen-search_start, &reregs) == -1
		    || reregs.start[0] == reregs.end[0])
			break;
		matches++;

		/*
		 * first, make a pass through the sub. pattern, to calculate
		 * the length of the string after substitution 
		 */
		match_length = reregs.end[0] - reregs.start[0];
		len = t->stlen - match_length;
		for (scan = s->stptr; scan < s->stptr + s->stlen; scan++)
			if (*scan == '&')
				len += match_length;
			else if (*scan == '\\' && *(scan+1) == '&') {
				scan++;
				len++;
			} else
				len++;
		emalloc(buf, char *, len + 1, "do_sub");
		bp = buf;

		/*
		 * now, create the result, copying in parts of the original
		 * string 
		 */
		for (scan = t->stptr; scan < t->stptr + reregs.start[0]; scan++)
			*bp++ = *scan;
		for (scan = s->stptr; scan < s->stptr + s->stlen; scan++)
			if (*scan == '&')
				for (cp = t->stptr + reregs.start[0];
				     cp < t->stptr + reregs.end[0]; cp++)
					*bp++ = *cp;
			else if (*scan == '\\' && *(scan+1) == '&') {
				scan++;
				*bp++ = *scan;
			} else
				*bp++ = *scan;
		search_start = bp - buf;
		for (scan = t->stptr + reregs.end[0];
		     scan < t->stptr + t->stlen; scan++)
			*bp++ = *scan;
		*bp = '\0';
		free(lastbuf);
		t->stptr = buf;
		lastbuf = buf;
		t->stlen = len;
	} while (global && search_start < t->stlen);

	free_temp(s);
	if (need_to_free) {
		free(rp->buffer);
		free(rp->fastmap);
		free((char *) rp);
	}
	if (matches > 0) {
		if (field_num == 0)
			set_record(fields_arr[0]->stptr, fields_arr[0]->stlen);
		t->flags &= ~(NUM|NUMERIC);
	}
	field_num = -1;
	return tmp_number((AWKNUM) matches);
}

NODE *
do_gsub(tree)
NODE *tree;
{
	return sub_common(tree, 1);
}

NODE *
do_sub(tree)
NODE *tree;
{
	return sub_common(tree, 0);
}

