
/********************************************
bi_funct.c

libmawk changes (C) 2009-2010, Tibor 'Igor2' Palinkas;
based on mawk code coming with the below copyright:

copyright 1991, Michael D. Brennan

This is a source file for mawk, an implementation of
the AWK programming language.

Mawk is distributed without warranty under the terms of
the GNU General Public License, version 2, 1991.
********************************************/
#include "mawk.h"
#include "bi_funct.h"
#include "bi_vars.h"
#include "memory.h"
#include "init.h"
#include "files.h"
#include "fin.h"
#include "field.h"
#include "regexp.h"
#include "repl.h"
#include "num.h"
#include "math_wrap.h"
#include "vio.h"
#include "cell.h"
#include <string.h>
#include <unistd.h>


/* statics */
static mawk_string_t *gsub(mawk_state_t *, PTR, mawk_cell_t *, char *, char *, int);
static void fplib_err(mawk_state_t *, char *, mawk_num_t, char *);

/****************************************************************
 string builtins (except split (in split.c) and [g]sub (at end))
 ****************************************************************/

mawk_cell_t *mawk_bi_length(mawk_state_t *MAWK, register mawk_cell_t *sp)
{
	unsigned len;

	if (sp->type == 0)
		mawk_cellcpy(MAWK, sp, MAWK->field);
	else
		sp--;

	if (sp->type < C_STRING)
		mawk_cast1_to_str(MAWK, sp);
	len = string(sp)->len;

	free_STRING(string(sp));
	sp->type = C_NUM;
	sp->d.dval = (double) len;

	return sp;
}

char *mawk_str_str(register char *target, char *key, unsigned key_len)
{
	register int k = key[0];

	switch (key_len) {
	case 0:
		return (char *) 0;
	case 1:
		return strchr(target, k);
	case 2:
		{
			int k1 = key[1];
			while ((target = strchr(target, k)))
				if (target[1] == k1)
					return target;
				else
					target++;
			/*failed */
			return (char *) 0;
		}
	}

	key_len--;
	while ((target = strchr(target, k))) {
		if (strncmp(target + 1, key + 1, key_len) == 0)
			return target;
		else
			target++;
	}
	/*failed */
	return (char *) 0;
}



mawk_cell_t *mawk_bi_index(mawk_state_t *MAWK, register mawk_cell_t *sp)
{
	register int idx;
	unsigned len;
	char *p;

	sp--;
	if (TEST2(sp) != TWO_STRINGS)
		mawk_cast2_to_str(MAWK, sp);

	if ((len = string(sp + 1)->len))
		idx = (p = mawk_str_str(string(sp)->str, string(sp + 1)->str, len))
			? p - string(sp)->str + 1 : 0;

	else													/* index of the empty string */
		idx = 1;

	free_STRING(string(sp));
	free_STRING(string(sp + 1));
	sp->type = C_NUM;
	sp->d.dval = (mawk_num_t) idx;
	return sp;
}

/*  substr(s, i, n)
    if l = length(s)  then get the characters
    from  max(1,i) to min(l,n-i-1) inclusive */

mawk_cell_t *mawk_bi_substr(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	int n_args, len;
	register int i, n;
	mawk_string_t *sval;									/* substr(sval->str, i, n) */

	n_args = sp->type;
	sp -= n_args;
	if (sp->type != C_STRING)
		mawk_cast1_to_str(MAWK, sp);
	/* don't use < C_STRING shortcut */
	sval = string(sp);

	if ((len = sval->len) == 0) {	/* substr on null string */
		if (n_args == 3) {
			mawk_cell_destroy(MAWK, sp + 2);
		}
		mawk_cell_destroy(MAWK, sp + 1);
		return sp;
	}

	if (n_args == 2) {
		n = MAX__INT;
		if (sp[1].type != C_NUM)
			mawk_cast1_to_num(MAWK, sp + 1);
	}
	else {
		if (TEST2(sp + 1) != TWO_NUMS)
			mawk_cast2_to_num(MAWK, sp + 1);
		n = d_to_i(sp[2].d.dval);
	}
	i = d_to_i(sp[1].d.dval) - 1;		/* i now indexes into string */

	if (i < 0)
		i = 0;

	if (n > len - i)
		n = len - i;

	if (n <= 0) {									/* the null string */
		sp->ptr = (PTR) & (MAWK->null_str);
		(MAWK->null_str.ref_cnt)++;
	}
	else {												/* got something */

		sp->ptr = (PTR) mawk_new_STRING0(MAWK, n);
		memcpy(string(sp)->str, sval->str + i, n);
	}

	free_STRING(sval);
	return sp;
}

/*
  match(s,r)
  sp[0] holds r, sp[-1] holds s
*/

mawk_cell_t *mawk_bi_match(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	char *p;
	unsigned length;

	if (sp->type != C_RE)
		mawk_cast_to_RE(MAWK, sp);
	if ((--sp)->type < C_STRING)
		mawk_cast1_to_str(MAWK, sp);

	mawk_cell_destroy(MAWK, RSTART);
	mawk_cell_destroy(MAWK, RLENGTH);
	RSTART->type = C_NUM;
	RLENGTH->type = C_NUM;

	p = mawk_REmatch(MAWK, string(sp)->str, (sp + 1)->ptr, &length, 0);

	if (p) {
		sp->d.dval = (mawk_num_t) (p - string(sp)->str + 1);
		RLENGTH->d.dval = (mawk_num_t) length;
	}
	else {
		sp->d.dval = MAWK_NUM_ZERO;
		RLENGTH->d.dval = -MAWK_NUM_ONE;				/* posix */
	}

	free_STRING(string(sp));
	sp->type = C_NUM;

	RSTART->d.dval = sp->d.dval;

	return sp;
}

mawk_cell_t *mawk_bi_toupper(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	mawk_string_t *old;
	register char *p, *q;

	if (sp->type != C_STRING)
		mawk_cast1_to_str(MAWK, sp);
	old = string(sp);
	sp->ptr = (PTR) mawk_new_STRING0(MAWK, old->len);

	q = string(sp)->str;
	p = old->str;
	while (*p) {
		*q = *p++;
		if (*q >= 'a' && *q <= 'z')
			*q += 'A' - 'a';
		q++;
	}
	free_STRING(old);
	return sp;
}

mawk_cell_t *mawk_bi_tolower(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	mawk_string_t *old;
	register char *p, *q;

	if (sp->type != C_STRING)
		mawk_cast1_to_str(MAWK, sp);
	old = string(sp);
	sp->ptr = (PTR) mawk_new_STRING0(MAWK, old->len);

	q = string(sp)->str;
	p = old->str;
	while (*p) {
		*q = *p++;
		if (*q >= 'A' && *q <= 'Z')
			*q += 'a' - 'A';
		q++;
	}
	free_STRING(old);
	return sp;
}


/************************************************
  arithemetic builtins
 ************************************************/

static void fplib_err(mawk_state_t *MAWK, char *fname, mawk_num_t val, char *error)
{
	mawk_rt_error(MAWK, "%s(" NUM_FMT ") : %s", fname, val, error);
}

#ifndef MAWK_NO_FLOAT

#ifndef MAWK_HAVE_SAFE_NAN
#	define handle_nan_1arg() \
	if (P_isnan((sp)->d.dval)) { \
		sp->d.dval = P_nan(); \
		return sp; \
	}
#else
#	define handle_nan_1arg()
#endif


mawk_cell_t *mawk_bi_sin(mawk_state_t *MAWK, mawk_cell_t *sp)
{
#if ! STDC_MATHERR
	if (sp->type != C_NUM)
		mawk_cast1_to_num(MAWK, sp);
	handle_nan_1arg();
	sp->d.dval = sin(sp->d.dval);
	return sp;
#else
	mawk_num_t x;

	errno = 0;
	if (sp->type != C_NUM)
		mawk_cast1_to_num(MAWK, sp);

	handle_nan_1arg();

	x = sp->d.dval;
	sp->d.dval = sin(sp->d.dval);
	if (errno)
		fplib_err(MAWK, "sin", x, "loss of precision");
	return sp;
#endif
}

mawk_cell_t *mawk_bi_cos(mawk_state_t *MAWK, mawk_cell_t *sp)
{
#if ! STDC_MATHERR
	if (sp->type != C_NUM)
		mawk_cast1_to_num(MAWK, sp);
	handle_nan_1arg();
	sp->d.dval = cos(sp->d.dval);
	return sp;
#else
	mawk_num_t x;

	errno = 0;
	if (sp->type != C_NUM)
		mawk_cast1_to_num(MAWK, sp);
	handle_nan_1arg();
	x = sp->d.dval;
	sp->d.dval = cos(sp->d.dval);
	if (errno)
		fplib_err(MAWK, "cos", x, "loss of precision");
	return sp;
#endif
}

mawk_cell_t *mawk_bi_atan2(mawk_state_t *MAWK, mawk_cell_t *sp)
{

#if  !	STDC_MATHERR
	sp--;
	if (TEST2(sp) != TWO_NUMS)
		mawk_cast2_to_num(MAWK, sp);

#ifndef MAWK_HAVE_SAFE_NAN
	if (P_isnan(sp->d.dval) || P_isnan((sp+1)->d.dval)) {
		sp->d.dval = P_nan();
		return sp;
	}
#endif

	sp->d.dval = atan2(sp->d.dval, (sp + 1)->d.dval);
	return sp;
#else

	errno = 0;
	sp--;
	if (TEST2(sp) != TWO_NUMS)
		mawk_cast2_to_num(MAWK, sp);


#ifndef MAWK_HAVE_SAFE_NAN
	if (P_isnan(sp->d.dval) || P_isnan((sp+1)->d.dval)) {
		sp->d.dval = P_nan();
		return sp;
	}
#endif

	sp->d.dval = atan2(sp->d.dval, (sp + 1)->d.dval);
	if (errno)
		mawk_rt_error(MAWK, "atan2(0,0) : domain error");
	return sp;
#endif
}

mawk_cell_t *mawk_bi_log(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	mawk_num_t x;

	errno = 0;
	if (sp->type != C_NUM)
		mawk_cast1_to_num(MAWK, sp);

	handle_nan_1arg();

	x = sp->d.dval;
	PM_BEGIN
		sp->d.dval = P_log(sp->d.dval);
	PM_ERROR
/* temporary workaround until the final nan support is there */
		sp->d.dval = P_nan();
/*		fplib_err(MAWK, "log", x, "domain error");*/
	PM_END;
	return sp;
}

mawk_cell_t *mawk_bi_exp(mawk_state_t *MAWK, mawk_cell_t *sp)
{

#if  ! STDC_MATHERR
	if (sp->type != C_NUM)
		mawk_cast1_to_num(MAWK, sp);
	handle_nan_1arg();
	sp->d.dval = exp(sp->d.dval);
	return sp;
#else
	mawk_num_t x;

	errno = 0;
	if (sp->type != C_NUM)
		mawk_cast1_to_num(MAWK, sp);
	handle_nan_1arg();
	x = sp->d.dval;
	sp->d.dval = exp(sp->d.dval);
	if (errno && sp->d.dval)
		fplib_err(MAWK, "exp", x, "mawk_overflow");
	/* on underflow sp->d.dval==0, ignore */
	return sp;
#endif
}
#endif

mawk_cell_t *mawk_bi_int(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	if (sp->type != C_NUM)
		mawk_cast1_to_num(MAWK, sp);
	handle_nan_1arg();
	sp->d.dval = mawk_num_int(sp->d.dval);
	return sp;
}

mawk_cell_t *mawk_bi_sqrt(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	mawk_num_t x;

	if (sp->type != C_NUM)
		mawk_cast1_to_num(MAWK, sp);

	handle_nan_1arg();

	x = sp->d.dval;

	if (x < 0) {
		sp->d.dval = P_nan();
		return sp;
	}

#if  ! STDC_MATHERR
	sp->d.dval = mawk_num_sqrt(x);
	return sp;
#else
	errno = 0;
	sp->d.dval = mawk_num_sqrt(x);
	if (errno)
		fplib_err(MAWK, "sqrt", x, "domain error");
	return sp;
#endif
}

#ifndef NO_TIME_H
#include <time.h>
#else
#include <sys/types.h>
#endif


/* For portability, we'll use our own random number generator , taken
   from:  Park, SK and Miller KW, "Random Number Generators:
   Good Ones are Hard to Find", CACM, 31, 1192-1201, 1988.
*/


#define		M	0x7fffffff					/* 2^31-1 */
#define		MX	0xffffffff
#define		A	16807
#define	  	Q	127773						/* M/A */
#define	  	R	2836							/* M%A */

#if M == MAX__LONG
#define crank(s)   s = A * (s % Q) - R * (s / Q) ;\
		   if ( s <= 0 ) s += M
#else
/* 64 bit longs */
#define crank(s)	{ unsigned long t = s ;\
			  t = (A * (t % Q) - R * (t / Q)) & MX ;\
			  if ( t >= M ) t = (t+M)&M ;\
			  s = t ;\
			}
#endif


mawk_cell_t *mawk_bi_srand(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	mawk_cell_t c;

	if (sp->type == 0) {					/* seed off clock */
		mawk_cellcpy(MAWK, sp, &MAWK->cseed);
		mawk_cell_destroy(MAWK, &MAWK->cseed);
		MAWK->cseed.type = C_NUM;
		MAWK->cseed.d.dval = (mawk_num_t) time((time_t *) 0);
	}
	else {												/* user seed */

		sp--;
		/* swap cseed and *sp ; don't need to adjust ref_cnts */
		c = *sp;
		*sp = MAWK->cseed;
		MAWK->cseed = c;
	}

	/* The old seed is now in *sp ; move the value in cseed to
	   seed in range [1,M) */

	mawk_cellcpy(MAWK, &c, &MAWK->cseed);
	if (c.type == C_NOINIT)
		mawk_cast1_to_num(MAWK, &c);

	MAWK->seed = c.type == C_NUM ? (d_to_i(c.d.dval) & M) % M + 1 : mawk_hash(string(&c)->str) % M + 1;
	if (MAWK->seed == M)
		MAWK->seed = M - 1;

	mawk_cell_destroy(MAWK, &c);

	/* crank it once so close seeds don't give a close
	   first result  */
	crank(MAWK->seed);

	return sp;
}

mawk_cell_t *mawk_bi_rand(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	crank(MAWK->seed);
	sp++;
	sp->type = C_NUM;
	sp->d.dval = (mawk_num_t) MAWK->seed / (mawk_num_t) M;
	return sp;
}

#undef	 A
#undef	 M
#undef   MX
#undef	 Q
#undef	 R
#undef   crank

/*************************************************
 miscellaneous builtins
 close, system and getline
 fflush
 *************************************************/

mawk_cell_t *mawk_bi_close(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	int x;

	if (sp->type < C_STRING)
		mawk_cast1_to_str(MAWK, sp);
	x = mawk_file_close(MAWK, (mawk_string_t *) sp->ptr);
	free_STRING(string(sp));
	sp->type = C_NUM;
	sp->d.dval = (mawk_num_t) x;
	return sp;
}


mawk_cell_t *mawk_bi_fflush(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	int ret = 0;

	if (sp->type == 0)
		mawk_vio_flush(MAWK, MAWK->fnode_stdout->vf);
	else {
		sp--;
		if (sp->type < C_STRING)
			mawk_cast1_to_str(MAWK, sp);
		ret = mawk_file_flush(MAWK, string(sp));
		free_STRING(string(sp));
	}

	sp->type = C_NUM;
	sp->d.dval = (mawk_num_t) ret;
	return sp;
}

#ifndef MAWK_NO_FORK
mawk_cell_t *mawk_bi_system(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	int pid;
	unsigned ret_val;

	if (sp->type < C_STRING)
		mawk_cast1_to_str(MAWK, sp);

	mawk_flush_all_output(MAWK);
	switch (pid = fork()) {
	case -1:											/* fork failed */

		mawk_errmsg(MAWK, errno, "could not create a new process");
		ret_val = 127;
		break;

	case 0:											/* the child */
		mawk_vio_exec_shell(MAWK, string(sp)->str);

	default:											/* wait for the child */
		ret_val = mawk_wait_for(MAWK, pid);
		break;
	}

	mawk_cell_destroy(MAWK, sp);
	sp->type = C_NUM;
	sp->d.dval = (mawk_num_t) ret_val;
	return sp;
}
#endif

/*  getline()  */

/*  if type == 0 :  stack is 0 , target address

    if type == F_IN : stack is F_IN, expr(filename), target address
    if type == PIPE_IN : stack is PIPE_IN, target address, expr(pipename)
*/

mawk_cell_t *mawk_bi_getline(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	mawk_cell_t tc, *cp;
	char *p;
	unsigned len;
	FILE_NODE *fnode = NULL;

	switch (sp->type) {
	case 0:
		sp--;
		if (!MAWK->main_input)
			mawk_FINopen_main(MAWK);

		p = mawk_FINgets(MAWK, MAWK->main_input, &len);
		if (p == (void *) mawk_FIN_nomore)
			goto nomore;

		if (!p)
			goto eof;

		cp = (mawk_cell_t *) sp;
		if (TEST2(NR) != TWO_NUMS)
			mawk_cast2_to_num(MAWK, NR);
		NR->d.dval += MAWK_NUM_ONE;
		MAWK->rt_nr++;
		FNR->d.dval += MAWK_NUM_ONE;
		MAWK->rt_fnr++;
		break;

	case F_IN:
		sp--;
		if (sp->type < C_STRING)
			mawk_cast1_to_str(MAWK, sp);
		fnode = mawk_file_find(MAWK, sp->ptr, F_IN, 1);
		free_STRING(string(sp));
		sp--;

		if (fnode == NULL)
			goto open_failure;

		p = mawk_FINgets(MAWK, fnode, &len);
		if (p == (void *) mawk_FIN_nomore)
			goto nomore;

		if (!p) {
			mawk_file_close_(MAWK, fnode);
			goto eof;
		}
		cp = sp;
		break;

	case PIPE_IN:
		sp -= 2;
		if (sp->type < C_STRING)
			mawk_cast1_to_str(MAWK, sp);
		fnode = mawk_file_find(MAWK, sp->ptr, PIPE_IN, 1);
		free_STRING(string(sp));

		if (fnode == NULL)
			goto open_failure;

		p = mawk_FINgets(MAWK, fnode, &len);
		if (p == (void *) mawk_FIN_nomore)
			goto nomore;

		if (!p) {
			mawk_file_close_(MAWK, fnode);
			goto eof;
		}
		cp = (sp + 1);
		break;

	default:
		mawk_bozo(MAWK, "type in mawk_bi_getline");

	}

	/* we've read a line , store it */

	if (len == 0) {
		tc.type = C_STRING;
		tc.ptr = (PTR) & (MAWK->null_str);
		MAWK->null_str.ref_cnt++;
	}
	else {
		tc.type = C_MBSTRN;
		tc.ptr = (PTR) mawk_new_STRING0(MAWK, len);
		memcpy(string(&tc)->str, p, len);
	}

	mawk_bifunct_target_assign(MAWK, cp, &tc);

	mawk_cell_destroy(MAWK, &tc);

	sp->d.dval = MAWK_NUM_ONE;
	goto done;
open_failure:;
	sp->d.dval = -MAWK_NUM_ONE;
	goto done;
eof:;
	sp->d.dval = MAWK_NUM_ZERO;								/* fall thru to done  */

done:;
	sp->type = C_NUM;
	return sp;

nomore:;
	sp->type = C_REQ_NOMORE;
	return sp;
}

/**********************************************
 sub() and gsub()
 **********************************************/

/* entry:  sp[0] = address of mawk_cell_t to sub on
	   sp[-1] = substitution CELL
	   sp[-2] = regular expression to match
*/

mawk_cell_t *mawk_bi_sub(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	mawk_cell_t *cp;											/* pointer to the replacement target */
	mawk_cell_t tc;											/* build the new string here */
	mawk_cell_t sc;											/* copy of the target mawk_cell_t */
	char *front, *middle, *back;	/* pieces */
	unsigned front_len, middle_len, back_len;

	sp -= 2;
	if (sp->type != C_RE)
		mawk_cast_to_RE(MAWK, sp);
	if (sp[1].type != C_REPL && sp[1].type != C_REPLV)
		mawk_cast_to_REPL(MAWK, sp + 1);
	cp = (mawk_cell_t *) (sp + 2);

	/* make a copy of the target, because we won't change anything
	   including type unless the match works */
	if (cp->type == C_ARR_REF_BT) {
		sc.type = C_NOINIT;
		mawk_array_find(MAWK, (mawk_array_t)cp->ptr, cp->d.idx_cell, &sc, 1);
	}
	else
		mawk_cellcpy(MAWK, &sc, cp->ptr);


	if (sc.type < C_STRING)
		mawk_cast1_to_str(MAWK, &sc);
	front = string(&sc)->str;

	if ((middle = mawk_REmatch(MAWK, front, sp->ptr, &middle_len, 0))) {
		front_len = middle - front;
		back = middle + middle_len;
		back_len = string(&sc)->len - front_len - middle_len;

		if ((sp + 1)->type == C_REPLV) {
			mawk_string_t *sval = mawk_new_STRING0(MAWK, middle_len);

			memcpy(sval->str, middle, middle_len);
			mawk_replv_to_repl(MAWK, sp + 1, sval);
			free_STRING(sval);
		}

		tc.type = C_STRING;
		tc.ptr = (PTR) mawk_new_STRING0(MAWK, front_len + string(sp + 1)->len + back_len);

		{
			char *p = string(&tc)->str;

			if (front_len) {
				memcpy(p, front, front_len);
				p += front_len;
			}
			if (string(sp + 1)->len) {
				memcpy(p, string(sp + 1)->str, string(sp + 1)->len);
				p += string(sp + 1)->len;
			}
			if (back_len)
				memcpy(p, back, back_len);
		}

		mawk_bifunct_target_assign(MAWK, cp, &tc);

		free_STRING(string(&tc));
	}

	free_STRING(string(&sc));
	mawk_repl_destroy(MAWK, sp + 1);
	sp->type = C_NUM;
	sp->d.dval = (mawk_num_t) (middle != (char *) 0 ? MAWK_NUM_ONE : MAWK_NUM_ZERO);
	return sp;
}

/* recursive global subsitution
   dealing with empty matches makes this mildly painful
*/

static mawk_string_t *gsub(mawk_state_t *MAWK, PTR re, mawk_cell_t *repl, char *target, char *orig_target, int flag)
{
	/*target -> if on, match of empty string at front is OK */
	/* repl -> always of type REPL or REPLV, destroyed by caller */
	/* flag -> if on, match of empty string at front is OK */
	char *front, *middle;
	mawk_string_t *back;
	unsigned front_len, middle_len;
	mawk_string_t *ret_val;
	mawk_cell_t xrepl;										/* a copy of repl so we can change repl */

	if (!(middle = mawk_REmatch(MAWK, target, re, &middle_len, target != orig_target)))
		return mawk_new_STRING(MAWK, target);	/* no match */

	mawk_cellcpy(MAWK, &xrepl, repl);

	if (!flag && middle_len == 0 && middle == target) {	/* match at front that's not allowed */

		if (*target == 0) {					/* target is empty string */
			mawk_repl_destroy(MAWK, &xrepl);
			MAWK->null_str.ref_cnt++;
			return &(MAWK->null_str);
		}
		else {
			char xbuff[2];

			front_len = 0;
			/* make new repl with target[0] */
			mawk_repl_destroy(MAWK, repl);
			xbuff[0] = *target++;
			xbuff[1] = 0;
			repl->type = C_REPL;
			repl->ptr = (PTR) mawk_new_STRING(MAWK, xbuff);
			back = gsub(MAWK, re, &xrepl, target, orig_target, 1);
		}
	}
	else {												/* a match that counts */

		MAWK->repl_cnt++;

		front = target;
		front_len = middle - target;

		if (*middle == 0) {					/* matched back of target */
			back = &(MAWK->null_str);
			MAWK->null_str.ref_cnt++;
		}
		else
			back = gsub(MAWK, re, &xrepl, middle + middle_len, orig_target, 0);

		/* patch the &'s if needed */
		if (repl->type == C_REPLV) {
			mawk_string_t *sval = mawk_new_STRING0(MAWK, middle_len);

			memcpy(sval->str, middle, middle_len);
			mawk_replv_to_repl(MAWK, repl, sval);
			free_STRING(sval);
		}
	}

	/* put the three pieces together */
	ret_val = mawk_new_STRING0(MAWK, front_len + string(repl)->len + back->len);
	{
		char *p = ret_val->str;

		if (front_len) {
			memcpy(p, front, front_len);
			p += front_len;
		}

		if (string(repl)->len) {
			memcpy(p, string(repl)->str, string(repl)->len);
			p += string(repl)->len;
		}
		if (back->len)
			memcpy(p, back->str, back->len);
	}

	/* cleanup, repl is freed by the caller */
	mawk_repl_destroy(MAWK, &xrepl);
	free_STRING(back);

	return ret_val;
}

/* set up for call to gsub() */
mawk_cell_t *mawk_bi_gsub(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	mawk_cell_t *cp;											/* pts at the replacement target */
	mawk_cell_t sc;											/* copy of replacement target */
	mawk_cell_t tc;											/* build the result here */

	sp -= 2;
	if (sp->type != C_RE)
		mawk_cast_to_RE(MAWK, sp);
	if ((sp + 1)->type != C_REPL && (sp + 1)->type != C_REPLV)
		mawk_cast_to_REPL(MAWK, sp + 1);

	cp = (mawk_cell_t *) (sp + 2);
	if (cp->type == C_ARR_REF_BT) {
		sc.type = C_NOINIT;
		mawk_array_find(MAWK, (mawk_array_t)cp->ptr, cp->d.idx_cell, &sc, 1);
	}
	else
		mawk_cellcpy(MAWK, &sc, cp->ptr);

	if (sc.type < C_STRING)
		mawk_cast1_to_str(MAWK, &sc);

	MAWK->repl_cnt = 0;
	tc.ptr = (PTR) gsub(MAWK, sp->ptr, sp + 1, string(&sc)->str, string(&sc)->str, 1);

	if (MAWK->repl_cnt) {
		tc.type = C_STRING;
		mawk_bifunct_target_assign(MAWK, cp, &tc);
	}

	/* cleanup */
	free_STRING(string(&sc));
	free_STRING(string(&tc));
	mawk_repl_destroy(MAWK, sp + 1);

	sp->type = C_NUM;
	sp->d.dval = (mawk_num_t) MAWK->repl_cnt;
	return sp;
}


mawk_cell_t *mawk_bi_call(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	int i, numargs;
	SYMTAB *fs;
	const char *fn;
/*	mawk_cell_t *ret;*/

	numargs = sp->type;
	sp -= numargs;
	if (sp->type < C_STRING)
		mawk_cast1_to_str(MAWK, sp);
	fn = string(sp)->str;

	fs = mawk_find(MAWK, fn, 0);

	if ((fs == NULL) || (fs->type != ST_FUNCT)) {
		/* does not exist or not a function */
		if (fs == NULL)
			mawk_set_errno(MAWK, "1 object does not exist");
		else
			mawk_set_errno(MAWK, "2 object is not a function");

TODO("this should be some common code in execute.c?")
		for (i = 0; i < numargs; i++) {
			mawk_cell_destroy(MAWK, &sp[i]);
			sp[i].type = C_NOINIT;
			sp[i].d.dval = MAWK_NUM_ZERO;
		}
		sp[numargs].type = C_NOINIT;
		sp[numargs].d.dval = MAWK_NUM_ZERO;

		return sp;
	}

	mawk_set_errno(MAWK, "");
/*	ret = mawk_call(MAWK, sp + numargs - 1, fs->stval.fbp, numargs - 1);*/

	/* shift args down to replace first arg (function name) with the first
	   actual arg passed to the function */
	for (i = 0; i < numargs; i++)
		sp[i] = sp[i+1];
	sp += numargs-1;

	/* perform the call */
	sp->type = C_NUM;
	sp->d.dval = numargs - 1;
	inc_sp();
	sp->type = C_REQ_CALL;
	sp->ptr = fs->stval.fbp;
	return sp;
}

mawk_cell_t *mawk_bi_acall(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	int numargs;
	SYMTAB *fs, *fa;
	const char *fn;
	mawk_cell_t *ret, idx;

	numargs = 1;
	sp -= numargs;

	if (sp->type < C_STRING)
		mawk_cast1_to_str(MAWK, sp);
	fn = string(sp)->str;

	fs = mawk_find(MAWK, fn, 0);

	if (fs == NULL) {
		/* does not exist */
		mawk_set_errno(MAWK, "1 object does not exist");
		sp++;
		goto error;
	}

	sp++;
	if (sp->type < C_STRING)
		mawk_cast1_to_str(MAWK, sp);

	fa = mawk_find(MAWK, string(sp)->str, 0);
	if (fa == NULL) {
		mawk_set_errno(MAWK, "3 argument array does not exist");
		goto error;
	}

	if (fa->type != ST_ARRAY) {
		mawk_set_errno(MAWK, "4 argument array is not an array");
		goto error;
	}

	sp -= 2;
	for (numargs = 1;; numargs++) {
		inc_sp();
		idx.type = C_NUM;
		idx.d.dval = numargs;
		if (mawk_array_find(MAWK, fa->stval.array, &idx, sp, 0) == 0)
			break;
	}

	if (fs->type == ST_FUNCT) {
		/* perform the call */
		mawk_set_errno(MAWK, "");
		inc_sp();
		sp->type = C_NUM;
		sp->d.dval = numargs;
		inc_sp();
		sp->type = C_REQ_CALL;
		sp->ptr = fs->stval.fbp;
		return sp;
	}
	else {
		mawk_set_errno(MAWK, "5 object is a function");
		idx.type = C_NOINIT;
		idx.d.dval = MAWK_NUM_ZERO;
		ret = &idx;
	}

	sp -= numargs;
	sp--;
TODO(": cell destroy all the allocated but unused args?")
	return sp;

error:;
	mawk_cell_destroy(MAWK, sp);
	sp--;
	sp->type = C_NOINIT;
	return sp;
}

mawk_cell_t *mawk_bi_valueof(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	int numargs;
	SYMTAB *fs;
	mawk_cell_t  *ret, *idx;

	numargs = sp->type;
	sp -= numargs;

	if (sp->type < C_STRING)
		mawk_cast1_to_str(MAWK, sp);

	fs = mawk_find(MAWK, string(sp)->str, 0);

	if (fs == NULL) {
		/* does not exist */
		mawk_set_errno(MAWK, "1 object does not exist");
		goto error;
	}

	if (fs->type == ST_FUNCT) {
		mawk_set_errno(MAWK, "5 object is a function");
		goto error;
	}

	if (numargs > 1) {
		if (fs->type == ST_ARRAY) {
			idx = sp + 1;
			mawk_array_find(MAWK, fs->stval.array, idx, sp, 0);
			goto has_set_sp;
		}
		else {
			mawk_set_errno(MAWK, "6 object is not an array, can not index it");
			goto error;
		}
	}
	else {
		if (fs->type == ST_ARRAY) {
			mawk_set_errno(MAWK, "7 object is an array, need to index it");
			goto error;
		}
		ret = fs->stval.cp;
	}

	if ((ret != NULL) && ((fs->type == ST_VAR) || (fs->type == ST_KEYWORD))) {
		mawk_cell_destroy(MAWK, sp);
		*sp = *ret;
		has_set_sp:;
		mawk_set_errno(MAWK, "");
		return sp;
	}

	if (ret != NULL)
		mawk_set_errno(MAWK, "8 object is not a variable (and not a function either)");

error:;
	mawk_cell_destroy(MAWK, sp);
	sp->type = C_NOINIT;
	return sp;
}

mawk_cell_t *mawk_bi_isnan(mawk_state_t *MAWK, mawk_cell_t *sp)
{
	if (sp->type != C_NUM)
		mawk_cast1_to_num(MAWK, sp);

	sp->type = C_NUM;
	sp->d.dval = P_isnan(sp->d.dval);

	return sp--;
}
