/*
 * awk4 -- Code for features in new AWK, System V compatibility.
 *
 * $Log:	awk4.c,v $
 * Revision 1.38  89/03/31  13:26:09  david
 * GNU license
 * 
 * Revision 1.37  89/03/29  14:19:07  david
 * delinting and code movement
 * 
 * Revision 1.36  89/03/22  22:10:23  david
 * a cleaner way to handle assignment to $n where n > 0
 * 
 * Revision 1.35  89/03/22  21:05:24  david
 * delete some obsolete code
 * 
 * Revision 1.34  89/03/22  21:00:34  david
 * replace some free()'s with freenode()
 * 
 * Revision 1.33  89/03/21  19:26:01  david
 * minor cleanup
 * 
 * Revision 1.32  89/03/21  18:22:54  david
 * some function tracing debugging code
 * 
 * Revision 1.31  89/03/21  10:53:21  david
 * cleanup
 * 
 * Revision 1.30  89/03/15  22:22:03  david
 * fix from hack: check for null function before using it in diagnostic
 * 
 * Revision 1.29  89/03/15  22:02:24  david
 * new case stuff added
 * 
 * Revision 1.28  89/03/15  21:34:37  david
 * free more memory and purge obstack stuff
 * 
 * Revision 1.27  88/12/14  10:53:49  david
 * malloc structures in func_call and free them on return
 * 
 * Revision 1.26  88/12/13  22:29:15  david
 * minor change
 * 
 * Revision 1.25  88/12/08  10:52:01  david
 * small correction to #ifdef'ing
 * 
 * Revision 1.24  88/11/30  15:18:21  david
 * fooling around with memory allocation in func_call() but this new code remains
 * #ifdef'd out
 * correction to creasting private copy of string in do_sub()
 * 
 * Revision 1.23  88/11/29  09:55:48  david
 * corrections to code that tracks value of NF -- this needs cleanup
 * 
 * Revision 1.22  88/11/28  20:30:10  david
 * bug fix for do_sub when third arg. not specified
 * 
 * Revision 1.21  88/11/22  13:51:10  david
 * Arnold: delinting
 * 
 * Revision 1.20  88/11/15  10:25:14  david
 * Arnold: minor cleanup
 * 
 * Revision 1.19  88/11/14  22:00:09  david
 * Arnold: error message on bad regexp; correction to handling of RSTART in
 * match() on failure; return arg handling to previous behaviour: var=val
 * arg is processed whne it is encountered.
 * 
 * Revision 1.18  88/11/14  21:28:09  david
 * moved concat_exp(); update NF on assign to field > NF; temporarily aborted
 * mods. in func_call(); flag misnamed as TMP becomes PERM (don't free)
 * 
 * Revision 1.17  88/11/01  12:19:37  david
 * cleanup and substantial changes to do_sub()
 * 
 * Revision 1.16  88/10/19  21:59:20  david
 * replace malloc and realloc with error checking versions
 * 
 * Revision 1.15  88/10/17  20:54:54  david
 * SYSV --> USG
 * 
 * Revision 1.14  88/10/13  22:00:44  david
 * purge FAST and cleanup error messages
 * 
 * Revision 1.13  88/10/11  09:29:12  david
 * retrieve parameters from the stack in func_call
 * 
 * Revision 1.12  88/10/06  21:53:15  david
 * added FSF copyleft; Arnold's changes to command line processing
 * 
 *
 */

/* 
 * 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 NODE **fields_arr;

jmp_buf func_tag;
NODE **stack_ptr;

NODE *
func_call(name, arg_list)
NODE *name;		/* name is a Node_val giving function name */
NODE *arg_list;		/* Node_expression_list of calling args. */
{
	register NODE *arg, *argp, *r;
	NODE *n, *f;
	jmp_buf func_tag_stack;
	NODE *ret_node_stack;
	NODE **local_stack;
	NODE **sp;
	static int func_tag_valid = 0;
	int count;
	extern NODE *ret_node;

	/*
	 * retrieve function definition node
	 */
	f = lookup(variables, name->stptr);
	if (!f || f->type != Node_func)
		fatal("function `%s' not defined", name->stptr);
#ifdef FUNC_TRACE
	fprintf(stderr, "function %s called\n", name->stptr);
#endif
	/*
	 * mark stack for variables allocated during life of function
	 */
	count = f->lnode->param_cnt;
	emalloc(local_stack, NODE **, count * sizeof(NODE *), "func_call");
	sp = local_stack;

	/*
	 * for each calling arg. add NODE * on stack
	 */
	for (argp = arg_list; count && argp != NULL; argp = argp->rnode) {
		arg = argp->lnode;
		r = newnode(Node_var);
		/*
		 * call by reference for arrays; see below also
		 */
		if (arg->type == Node_param_list)
			arg = stack_ptr[arg->param_cnt];
		if (arg->type == Node_var_array)
			*r = *arg;
		else {
			n = dupnode(tree_eval(arg));
			r->lnode = n;
			r->rnode = (NODE *) NULL;
  		}
		*sp++ = r;
		count--;
	}
	if (argp != NULL)	/* left over calling args. */
		warning(
		    "function `%s' called with more arguments than declared",
		    name->stptr);
	/*
	 * add remaining params. on stack with null value
	 */
	while (count-- > 0) {
		r = newnode(Node_var);
		r->lnode = Nnull_string;
		r->rnode = (NODE *) NULL;
		*sp++ = r;
	}

	/*
	 * execute function body, saving context, as a return statement
	 * will longjmp back here
	 */
	sp = local_stack;
	local_stack = stack_ptr;
	stack_ptr = sp;
	PUSH_BINDING(func_tag_stack, func_tag, func_tag_valid);
	ret_node_stack = ret_node;
	if (_setjmp(func_tag) == 0)
		(void) interpret(f->rnode);
	r = ret_node;
	ret_node = ret_node_stack;
	RESTORE_BINDING(func_tag_stack, func_tag, func_tag_valid);
	sp = stack_ptr;
	stack_ptr = local_stack;
	local_stack = sp;

	/*
	 * here, we pop each parameter and check whether
	 * it was an array.  If so, and if the arg. passed in was
	 * a simple variable, then the value should be copied back.
	 * This achieves "call-by-reference" for arrays.
	 */
	count = f->lnode->param_cnt;
	for (argp = arg_list; count-- > 0 && argp != NULL; argp = argp->rnode) {
		arg = argp->lnode;
		n = *sp++;
		if (arg->type == Node_var && n->type == Node_var_array) {
			arg->var_array = n->var_array;
			arg->type = Node_var_array;
		}
		deref = n->lnode;
		do_deref();
		freenode(n);
	}
	while (count-- > 0) {
		n = *sp++;
		deref = n->lnode;
		do_deref();
		freenode(n);
	}
	free((char *) local_stack);
	return r;
}

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

	t1 = force_string(tree_eval(tree->lnode));
	if (tree->rnode->type == Node_regex) {
		rp = tree->rnode->rereg;
		if (!strict && ((IGNORECASE_node->var_value->numbr != 0)
		    ^ (tree->rnode->re_case != 0))) {
			/* recompile since case sensitivity differs */
			rp = tree->rnode->rereg =
				mk_re_parse(tree->rnode->re_text,
				(IGNORECASE_node->var_value->numbr != 0));
			tree->rnode->re_case = (IGNORECASE_node->var_value->numbr != 0);
		}
	} else {
		rp = make_regexp(force_string(tree_eval(tree->rnode)),
				(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;
	return tmp_number((AWKNUM) rstart);
}

NODE *
do_sub(tree)
NODE *tree;
{
	register int len;
	register char *scan;
	register char *bp, *cp;
	int search_start = 0;
	int match_length;
	int matches = 0;
	char *buf;
	int global;
	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;

	global = (tree->type == Node_gsub);

	if (tree->rnode->type == Node_regex) {
		rp = tree->rnode->rereg;
		if (! strict && ((IGNORECASE_node->var_value->numbr != 0)
		    ^ (tree->rnode->re_case != 0))) {
			/* recompile since case sensitivity differs */
			rp = tree->rnode->rereg =
				mk_re_parse(tree->rnode->re_text,
				(IGNORECASE_node->var_value->numbr != 0));
			tree->rnode->re_case = (IGNORECASE_node->var_value->numbr != 0);
		}
	} else {
		rp = make_regexp(force_string(tree_eval(tree->rnode)),
				(IGNORECASE_node->var_value->numbr != 0));
		if (rp == NULL)
			cant_happen();
	}
	tree = tree->lnode;
	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 (matches > 0) {
		if (field_num == 0)
			set_record(fields_arr[0]->stptr, fields_arr[0]->stlen);
		t->flags &= ~NUM;
	}
	field_num = -1;
	return tmp_number((AWKNUM) matches);
}
