OpenSolaris_b135/cmd/awk_xpg4/awk3.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * awk -- executor
 *
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 1985, 1994 by Mortice Kern Systems Inc.  All rights reserved.
 *
 * Based on MKS awk(1) ported to be /usr/xpg4/bin/awk with POSIX/XCU4 changes
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include "awk.h"
#include "y.tab.h"

static int	dohash(wchar_t *name);
static NODE	*arithmetic(NODE *np);
static NODE	*comparison(NODE *np);
static int	type_of(NODE *np);
static NODE	*lfield(INT fieldno, NODE *value);
static NODE	*rfield(INT fieldno);
static NODE	*userfunc(NODE *np);
static wchar_t	*lltoa(long long l);
static NODE	*exprconcat(NODE *np, int len);
static int	s_if(NODE *np);
static int	s_while(NODE *np);
static int	s_for(NODE *np);
static int	s_forin(NODE *np);
static void	setrefield(NODE *value);
static void	freetemps(void);
static int	action(NODE *np);
static wchar_t	*makeindex(NODE *np, wchar_t *array, int tag);
static int	exprtest(NODE *np);

#define	regmatch(rp, s) REGWEXEC(rp, s, 0, (REGWMATCH_T*)NULL, 0)

/*
 * This code allows for integers to be stored in longs (type INT) and
 * only promoted to double precision floating point numbers (type REAL)
 * when overflow occurs during +, -, or * operations.  This is very
 * non-portable if you desire such a speed optimisation.  You may wish
 * to put something here for your system.  This "something" would likely
 * include either an assembler "jump on overflow" instruction or a
 * method to get traps on overflows from the hardware.
 *
 * This portable method works for ones and twos complement integer
 * representations (which is, realistically) almost all machines.
 */
#if	__TURBOC__
#define	addoverflow()	asm	jo	overflow
#define	suboverflow()	asm	jo	overflow
#else
/*
 * These are portable to two's complement integer machines
 */
#define	addoverflow()	if ((i1^i2) >= 0 && (iresult^i1) < 0) goto overflow
#define	suboverflow()	if ((i1^i2) < 0 && (iresult^i2) >= 0) goto overflow
#endif
#define	muloverflow()	if (((short)i1 != i1 || (short)i2 != i2) &&	\
			    ((i2 != 0 && iresult/i2 != i1) ||		\
			    (i1 == LONG_MIN && i2 == -1)))	  goto overflow

static char	notarray[] = "scalar \"%s\" cannot be used as array";
static char	badarray[] = "array \"%s\" cannot be used as a scalar";
static char	varnotfunc[] = "variable \"%s\" cannot be used as a function";
static char	tmfld[] = "Too many fields (LIMIT: %d)";
static char	toolong[] = "Record too long (LIMIT: %d bytes)";
static char	divzero[] =  "division (/ or %%) by zero";
static char	toodeep[] = "too deeply nested for in loop (LIMIT: %d)";

static wchar_t	numbuf[NUMSIZE];	/* Used to convert INTs to strings */
static wchar_t	*fields[NFIELD];	/* Cache of pointers into fieldbuf */
static wchar_t	*fieldbuf;		/* '\0' separated copy of linebuf */
static NODE	nodes[NSNODE];		/* Cache of quick access nodes */
static NODE	*fnodep = &nodes[0];
#define	NINDEXBUF	50
static wchar_t	indexbuf[NINDEXBUF];	/* Used for simple array indices */
static int	concflag;		/* In CONCAT operation (no frees) */
static NODE	*retval;		/* Last return value of a function */

/*
 * The following stack is used to store the next pointers for all nested
 * for-in loops. This needs to be global so that delete can check to see
 * if it is deleting the next node to be used by a loop.
 */
#define	NFORINLOOP	10
static NODE*	forindex[NFORINLOOP];
static NODE**	next_forin = forindex;

/*
 * Assign a string directly to a NODE without creating an intermediate
 * NODE.  This can handle either FALLOC, FSTATIC, FNOALLOC or FSENSE for
 * "flags" argument.  Also the NODE "np" must be reduced to an lvalue
 * (PARM nodes are not acceptable).
 */
void
strassign(NODE *np, STRING string, int flags, size_t length)
{
	if (np->n_type == FUNC)
		awkerr(gettext("attempt to redefine builtin function"));
	else if (np->n_type == GETLINE || np->n_type == KEYWORD)
		awkerr(gettext("inadmissible use of reserved keyword"));
	if (np->n_flags & FSPECIAL) {
		(void) nassign(np, stringnode(string, flags, length));
		return;
	}
	if (isastring(np->n_flags))
		free((wchar_t *)np->n_string);
	np->n_strlen = length++;
	if (flags & FALLOC) {
		length *= sizeof (wchar_t);
		np->n_string = (STRING) emalloc(length);
		(void) memcpy((void *)np->n_string, string, length);
	} else {
		np->n_string = string;
		if (flags & FNOALLOC) {
			flags &= ~FNOALLOC;
			flags |= FALLOC;
		}
	}
	np->n_flags &= FSAVE;
	if (flags & FSENSE) {
		flags &= ~FSENSE;
		flags |= type_of(np);
	} else
		flags |= FSTRING;
	np->n_flags |= flags;
}

/*
 * Assign to a variable node.
 * LHS must be a VAR type and RHS must be reduced by now.
 * To speed certain operations up, check for
 * certain things here and do special assignments.
 */
NODE *
nassign(NODE *np, NODE *value)
{
	register wchar_t *cp;
	register int len;

	/* short circuit assignment of a node to itself */
	if (np == value)
		return (np);
	if (np->n_flags & FSPECIAL) {
		if (np == varRS || np == varFS) {
			if (isastring(np->n_flags))
				free((void *)np->n_string);
			len = sizeof (wchar_t) * ((np->n_strlen =
				wcslen(cp = exprstring(value)))+1);
			np->n_string = emalloc(len);
			(void) memcpy((wchar_t *)np->n_string, cp, len);
			np->n_flags = FALLOC|FSTRING|FSPECIAL;
			if (np == varRS) {
				if (np->n_string[0] == '\n')
					awkrecord = defrecord;
				else if (np->n_string[0] == '\0')
					awkrecord = multirecord;
				else
					awkrecord = charrecord;
			} else if (np == varFS) {
				if (resep != (REGEXP)NULL) {
					REGWFREE(resep);
					resep = (REGEXP)NULL;
				}
				if (wcslen((wchar_t *)np->n_string) > 1)
					setrefield(np);
				else if (np->n_string[0] == ' ')
					awkfield = whitefield;
				else
					awkfield = blackfield;
			}
			return (np);
		}
	}
	if (isastring(np->n_flags))
		free((wchar_t *)np->n_string);
	if (isstring(value->n_flags)) {
		np->n_strlen = value->n_strlen;
		if (value->n_flags&FALLOC || value->n_string != _null) {
			len = (np->n_strlen+1) * sizeof (wchar_t);
			np->n_string = emalloc(len);
			(void) memcpy(np->n_string, value->n_string, len);
			np->n_flags &= FSAVE;
			np->n_flags |= value->n_flags & ~FSAVE;
			np->n_flags |= FALLOC;
			return (np);
		} else
			np->n_string = value->n_string;
	} else if (value->n_flags & FINT)
		np->n_int = value->n_int;
	else
		np->n_real = value->n_real;
	np->n_flags &= FSAVE;
	np->n_flags |= value->n_flags & ~FSAVE;
	return (np);
}

/*
 * Set regular expression FS value.
 */
static void
setrefield(NODE *np)
{
	static REGEXP re;
	int n;

	if ((n = REGWCOMP(&re, np->n_string)) != REG_OK) {
		REGWERROR(n, &re, (char *)linebuf, sizeof (linebuf));
		awkerr(gettext("syntax error \"%s\" in /%s/\n"),
			(char *)linebuf, np->n_string);
	}
	resep = re;
	awkfield = refield;
}

/*
 * Assign to an l-value node.
 */
NODE *
assign(NODE *left, NODE *right)
{
	if (isleaf(right->n_flags)) {
		if (right->n_type == PARM)
			right = right->n_next;
	} else
		right = exprreduce(right);
top:
	switch (left->n_type) {
	case INDEX:
		left = exprreduce(left);
	/*FALLTHRU*/
	case VAR:
		return (nassign(left, right));

	case PARM:
		/*
		 * If it's a parameter then link to the actual value node and
		 * do the checks again.
		 */
		left = left->n_next;
		goto top;

	case FIELD:
		return (lfield(exprint(left->n_left), right));

	case CALLUFUNC:
	case UFUNC:
		awkerr(gettext("cannot assign to function \"%s\""),
		    left->n_name);

	default:
		awkerr(gettext("lvalue required in assignment"));
	}
	/* NOTREACHED */
	return (0);
}

/*
 * Compiled tree non-terminal node.
 */
NODE *
node(int type, NODE *left, NODE *right)
{
	register NODE *np;

	np = emptynode(type, 0);
	np->n_left = left;
	np->n_right = right;
	np->n_lineno = lineno;
	return (np);
}

/*
 * Create an integer node.
 */
NODE *
intnode(INT i)
{
	register NODE *np;

	np = emptynode(CONSTANT, 0);
	np->n_flags = FINT|FVINT;
	np->n_int = i;
	return (np);
}

/*
 * Create a real number node.
 */
NODE *
realnode(REAL real)
{
	register NODE *np;

	np = emptynode(CONSTANT, 0);
	np->n_flags = FREAL|FVREAL;
	np->n_real = real;
	return (np);
}

/*
 * Make a node for a string.
 */
NODE *
stringnode(STRING s, int how, size_t length)
{
	register NODE *np;

	np = emptynode(CONSTANT, 0);
	np->n_strlen = length;
	if (how & FALLOC) {
		np->n_string = emalloc(length = (length+1) * sizeof (wchar_t));
		(void) memcpy(np->n_string, s, length);
	} else {
		np->n_string = s;
		if (how & FNOALLOC) {
			how &= ~FNOALLOC;
			how |= FALLOC;
		}
	}
	if (how & FSENSE) {
		np->n_flags = type_of(np);
		how &= ~FSENSE;
	} else
		np->n_flags = FSTRING;
	np->n_flags |= how;
	return (np);
}

/*
 * Save a copy of a string.
 */
STRING
strsave(wchar_t *old)
{
	STRING new;
	register size_t len;

	new = (STRING)emalloc(len = (wcslen(old)+1) * sizeof (wchar_t));
	(void) memcpy(new, old, len);
	return (new);
}

/*
 * Allocate an empty node of given type.
 * String space for the node is given by `length'.
 */
NODE *
emptynode(int type, size_t length)
{
	register NODE *np;

	if (length == 0 && running && fnodep < &nodes[NSNODE]) {
		np = fnodep++;
	} else {
		np = (NODE *)emalloc(sizeof (NODE) +
		    (length * sizeof (wchar_t)));
		if (running && type != VAR && type != ARRAY) {
			np->n_next = freelist;
			freelist = np;
		}
	}
	np->n_flags = FNONTOK;
	np->n_type = type;
	np->n_alink = NNULL;

	return (np);
}

/*
 * Free a node.
 */
void
freenode(NODE *np)
{
	if (isastring(np->n_flags))
		free((wchar_t *)np->n_string);
	else if (np->n_type == RE) {
		REGWFREE(np->n_regexp);
	}
	free((wchar_t *)np);
}

/*
 * Install a keyword of given `type'.
 */
void
kinstall(LOCCHARP name, int type)
{
	register NODE *np;
	register size_t l;

	l = wcslen(name);
	np = emptynode(KEYWORD, l);
	np->n_keywtype = type;
	(void) memcpy(np->n_name, name, (l+1) * sizeof (wchar_t));
	addsymtab(np);
}

/*
 * Install built-in function.
 */
NODE *
finstall(LOCCHARP name, FUNCTION func, int type)
{
	register NODE *np;
	register size_t l;

	l = wcslen(name);
	np = emptynode(type, l);
	np->n_function = func;
	(void) memcpy(np->n_name, name, (l+1) * sizeof (wchar_t));
	addsymtab(np);
	return (np);
}

/*
 * Lookup an identifier.
 * nocreate contains the following flag values:
 *	1 if no creation of a new NODE,
 *	0 if ok to create new NODE
 */
NODE *
vlookup(wchar_t *name, int nocreate)
{
	register ushort_t hash;
	register NODE *np;

	np = symtab[hashbuck(hash = dohash((wchar_t *)name))];
	while (np != NNULL) {
		if (np->n_hash == hash && wcscmp(name, np->n_name) == 0)
			return (np);
		np = np->n_next;
	}
	if (nocreate) {
		np = NNULL;
	} else {
		np = emptynode(VAR, hash = wcslen(name));
		np->n_flags = FSTRING|FVINT;
		np->n_strlen = 0;
		np->n_string = _null;
		(void) memcpy(np->n_name, name,
			(hash+1) * sizeof (wchar_t));
		addsymtab(np);
	}
	return (np);
}

/*
 * Add a symbol to the table.
 */
void
addsymtab(NODE *np)
{
	register NODE **spp;

	np->n_hash = dohash((wchar_t *)np->n_name);
	spp = &symtab[hashbuck(np->n_hash)];
	np->n_next = *spp;
	*spp = np;
}

/*
 * Delete the given node from the symbol table.
 * If fflag is non-zero, also free the node space.
 * This routine must also check the stack of forin loop pointers. If
 * we are deleting the next item to be used, then the pointer must be
 * advanced.
 */
void
delsymtab(NODE *np, int fflag)
{
	register NODE *rnp;
	register NODE *prevp;
	register NODE **sptr;
	register ushort_t h;





	h = hashbuck(np->n_hash);
	prevp = NNULL;
	for (rnp = symtab[h]; rnp != NNULL; rnp = rnp->n_next) {
		if (rnp == np) {
			/*
			 * check all of the for-in loop pointers
			 * to see if any need to be advanced because
			 * this element is being deleted.
			 */
			if (next_forin != forindex) {
				sptr = next_forin;
				do {
					if (*--sptr == rnp) {
						*sptr = rnp->n_next;
						break;
					}
				} while (sptr != forindex);
			}
			if (prevp == NNULL)
				symtab[h] = rnp->n_next; else
				prevp->n_next = rnp->n_next;
			if (fflag)
				freenode(rnp);
			break;
		}
		prevp = rnp;
	}
}

/*
 * Hashing function.
 */
static int
dohash(wchar_t *name)
{
	register int hash = 0;

	while (*name != '\0')
		hash += *name++;
	return (hash);
}

/*
 * Top level executor for an awk programme.
 * This will be passed: pattern, action or a list of these.
 * The former function to evaluate a pattern has been
 * subsumed into this function for speed.
 * Patterns are:
 *	BEGIN,
 *	END,
 *	other expressions (including regular expressions)
 */
void
execute(NODE *wp)
{
	register NODE *np;
	register int type;
	register NODE *tnp;

	curnode = wp;
	if (phase != 0) {
		linebuf[0] = '\0';
		lbuflen = 0;
	}
	while (wp != NNULL) {
		if (wp->n_type == COMMA) {
			np = wp->n_left;
			wp = wp->n_right;
		} else {
			np = wp;
			wp = NNULL;
		}
		if (np->n_type != PACT)
			awkerr(interr, "PACT");
		/*
		 * Save the parent node and evaluate the pattern.
		 * If it evaluates to false (0) just continue
		 * to the next pattern/action (PACT) pair.
		 */
		tnp = np;
		np = np->n_left;
		if (np == NNULL) {
			if (phase != 0)
				continue;
		} else if (phase != 0) {
			if (np->n_type != phase)
				continue;
		} else if ((type = np->n_type) == BEGIN || type == END) {
			continue;
		} else if (type == COMMA) {
			/*
			 * The grammar only allows expressions
			 * to be separated by the ',' operator
			 * for range patterns.
			 */
			if (np->n_flags & FMATCH) {
				if (exprint(np->n_right) != 0)
					np->n_flags &= ~FMATCH;
			} else if (exprint(np->n_left) != 0) {
				if (exprint(np->n_right) == 0)
					np->n_flags |= FMATCH;
			} else
				continue;
		} else if (exprint(np) == 0)
			continue;
		np = tnp;
		if (action(np->n_right)) {
			loopexit = 0;
			break;
		}
	}
	if (freelist != NNULL)
		freetemps();
}

/*
 * Free all temporary nodes.
 */
static void
freetemps()
{
	register NODE *np, *nnp;

	if (concflag)
		return;
	for (np = &nodes[0]; np < fnodep; np++) {
		if (isastring(np->n_flags)) {
			free((wchar_t *)np->n_string);
		} else if (np->n_type == RE) {
			REGWFREE(np->n_regexp);
		}
	}
	fnodep = &nodes[0];
	for (np = freelist; np != NNULL; np = nnp) {
		nnp = np->n_next;
		freenode(np);
	}
	freelist = NNULL;
}

/*
 * Do the given action.
 * Actions are statements or expressions.
 */
static int
action(NODE *wp)
{
	register NODE *np;
	register int act = 0;
	register NODE *l;

	while (wp != NNULL) {
		if (wp->n_type == COMMA) {
			np = wp->n_left;
			wp = wp->n_right;
		} else {
			np = wp;
			wp = NNULL;
		}
		if (freelist != NNULL)
			freetemps();
		curnode = np;
		/*
		 * Don't change order of these cases without
		 * changing order in awk.y declarations.
		 * The order is optimised.
		 */
		switch (np->n_type) {
		case ASG:
			(void) assign(np->n_left, np->n_right);
			continue;

		case PRINT:
			s_print(np);
			continue;

		case PRINTF:
			s_prf(np);
			continue;

		case EXIT:
			if (np->n_left != NNULL)
				act = (int)exprint(np->n_left); else
				act = 0;
			doend(act);
			/* NOTREACHED */

		case RETURN:
			if (slevel == 0)
				awkerr(gettext("return outside of a function"));
			np = np->n_left != NNULL
			    ? exprreduce(np->n_left)
			    : const0;
			retval = emptynode(CONSTANT, 0);
			retval->n_flags = FINT;
			(void) nassign(retval, np);
			return (RETURN);

		case NEXT:
			loopexit = NEXT;
		/*FALLTHRU*/
		case BREAK:
		case CONTINUE:
			return (np->n_type);

		case DELETE:
			if ((l = np->n_left)->n_type == PARM) {
				l = l->n_next;
				if (!(l->n_flags & FLARRAY))
					l = l->n_alink;
			}
			switch (l->n_type) {
			case ARRAY:
				delarray(l);
				break;

			case INDEX:
				if ((np = l->n_left)->n_type == PARM) {
					np = np->n_next;
					if (!(np->n_flags & FLARRAY))
						np = np->n_alink;
				}
				/*
				 * get pointer to the node for this array
				 * element using the hash key.
				 */
				l = exprreduce(l);
				/*
				 * now search linearly from the beginning of
				 * the list to find the element before the
				 * one being deleted. This must be done
				 * because arrays are singley-linked.
				 */
				while (np != NNULL) {
					if (np->n_alink == l) {
						np->n_alink = l->n_alink;
						break;
					}
					np = np->n_alink;
				}
				delsymtab(l, 1);
				break;

			case VAR:
				if (isstring(l->n_flags) &&
				    l->n_string == _null)
					break;
			default:
				awkerr(gettext(
				    "may delete only array element or array"));
				break;
			}
			continue;

		case WHILE:
		case DO:
			if ((act = s_while(np)) != 0)
				break;
			continue;

		case FOR:
			if ((act = s_for(np)) != 0)
				break;
			continue;

		case FORIN:
			if ((act = s_forin(np)) != 0)
				break;
			continue;

		case IF:
			if ((act = s_if(np)) != 0)
				break;
			continue;

		default:
			(void) exprreduce(np);
			if (loopexit != 0) {
				act = loopexit;
				break;
			}
			continue;
		}
		return (act);
	}
	return (0);
}

/*
 * Delete an entire array
 */
void
delarray(NODE *np)
{
	register NODE *nnp;

	nnp = np->n_alink;
	np->n_alink = NNULL;
	while (nnp != NNULL) {
		np = nnp->n_alink;
		delsymtab(nnp, 1);
		nnp = np;
	}
}

/*
 * Return the INT value of an expression.
 */
INT
exprint(NODE *np)
{
	if (isleaf(np->n_flags)) {
		if (np->n_type == PARM)
			np = np->n_next;
		goto leaf;
	}
	np = exprreduce(np);
	switch (np->n_type) {
	case CONSTANT:
	case VAR:
	leaf:
		if (np->n_flags & FINT)
			return (np->n_int);
		if (np->n_flags & FREAL)
			return ((INT)np->n_real);
		return ((INT)wcstoll(np->n_string, NULL, 10));

	default:
		awkerr(interr, "exprint");
	}
	/* NOTREACHED */
	return (0);
}

/*
 * Return a real number from an expression tree.
 */
REAL
exprreal(NODE *np)
{
	if (loopexit)
		return ((REAL)loopexit);
	if (isleaf(np->n_flags)) {
		if (np->n_type == PARM)
			np = np->n_next;
		goto leaf;
	}
	np = exprreduce(np);
	switch (np->n_type) {
	case CONSTANT:
	case VAR:
	leaf:
		if (np->n_flags & FREAL)
			return (np->n_real);
		if (np->n_flags & FINT)
			return ((REAL)np->n_int);
		return ((REAL)wcstod((wchar_t *)np->n_string, (wchar_t **)0));

	default:
		awkerr(interr, "exprreal");
	}
	/* NOTREACHED */
	return ((REAL)0);
}

/*
 * Return a string from an expression tree.
 */
STRING
exprstring(NODE *np)
{
	if (isleaf(np->n_flags)) {
		if (np->n_type == PARM)
			np = np->n_next;
		goto leaf;
	}
	np = exprreduce(np);
	switch (np->n_type) {
	case CONSTANT:
	case VAR:
	leaf:
		if (isstring(np->n_flags))
			return (np->n_string);
		if (np->n_flags & FINT)
			return (STRING)lltoa((long long)np->n_int);
		{
			char *tmp;
			(void) wsprintf(numbuf,
		(const char *) (tmp = wcstombsdup(exprstring(varCONVFMT))),
				(double)np->n_real);
			if (tmp != NULL)
				free(tmp);
		}
		return ((STRING)numbuf);

	default:
		awkerr(interr, "exprstring");
	}
	/* NOTREACHED */
	return (0);
}

/*
 * Convert number to string.
 */
static wchar_t *
lltoa(long long l)
{
	register wchar_t *p = &numbuf[NUMSIZE];
	register int s;
	register int neg;
	static wchar_t zero[] = M_MB_L("0");

	if (l == 0)
		return (zero);
	*--p = '\0';
	if (l < 0)
		neg = 1, l = -l; else
		neg = 0;
	if ((s = (short)l) == l) {
		while (s != 0) {
			*--p = s%10 + '0';
			s /= 10;
		}
	} else {
		while (l != 0) {
			*--p = l%10 + '0';
			l /= 10;
		}
	}
	if (neg)
		*--p = '-';
	return (wcscpy(numbuf, p));
}

/*
 * Return pointer to node with concatenation of operands of CONCAT node.
 * In the interest of speed, a left recursive tree of CONCAT nodes
 * is handled with a single malloc.  The accumulated lengths of the
 * right operands are passed down recursive invocations of this
 * routine, which allocates a large enough string when the left
 * operand is not a CONCAT node.
 */
static NODE *
exprconcat(NODE *np, int len)
{
	/* we KNOW (np->n_type==CONCAT) */
	register NODE *lnp = np->n_left;
	register NODE *rnp = np->n_right;
	register STRING	rsp;
	int rlen;
	size_t llen;
	wchar_t *cp;
	wchar_t rnumbuf[NUMSIZE];

	if (isleaf(rnp->n_flags) && rnp->n_type == PARM)
		rnp = rnp->n_next;
	if (isstring(rnp->n_flags)) {
		rsp = rnp->n_string;
		rlen = rnp->n_strlen;
	} else
		rlen = wcslen((wchar_t *)(rsp = exprstring(rnp)));
	if (rsp == numbuf) {	/* static, so save a copy */
		(void) memcpy(rnumbuf, (wchar_t *)rsp,
			(rlen+1) * sizeof (wchar_t));
		rsp = rnumbuf;
	}
	len += rlen;
	if (lnp->n_type == CONCAT) {
		lnp = exprconcat(lnp, len);
		cp = lnp->n_string;
		llen = lnp->n_strlen;
	} else {
		register STRING	lsp;

		if (isleaf(lnp->n_flags) && lnp->n_type == PARM)
			lnp = lnp->n_next;
		if (isstring(lnp->n_flags)) {
			lsp = lnp->n_string;
			llen = lnp->n_strlen;
		} else
			llen = wcslen((wchar_t *)(lsp = exprstring(lnp)));
		cp = emalloc((llen+len+1) * sizeof (wchar_t));
		(void) memcpy(cp, (wchar_t *)lsp, llen * sizeof (wchar_t));
		lnp = stringnode(cp, FNOALLOC, llen);
	}
	(void) memcpy(cp+llen, (wchar_t *)rsp, (rlen+1) * sizeof (wchar_t));
	lnp->n_strlen += rlen;
	return (lnp);
}

/*
 * Reduce an expression to a terminal node.
 */
NODE *
exprreduce(NODE *np)
{
	register wchar_t *cp;
	NODE *tnp;
	register int temp;
	register int t;
	register int  tag;
	register wchar_t *fname;
	register wchar_t *aname;

	/*
	 * a var or constant is a leaf-node (no further reduction required)
	 * so return immediately.
	 */
	if ((t = np->n_type) == VAR || t == CONSTANT)
		return (np);
	/*
	 * If it's a parameter then it is probably a leaf node but it
	 * might be an array so we check.. If it is an array, then signal
	 * an error as an array by itself cannot be used in this context.
	 */
	if (t == PARM)
		if ((np = np->n_next)->n_type == ARRAY)
			awkerr(badarray, np->n_name);
		else
			return (np);
	/*
	 * All the rest are non-leaf nodes.
	 */
	curnode = np;
	switch (t) {
	case CALLUFUNC:
		return (userfunc(np));

	case FIELD:
		return (rfield(exprint(np->n_left)));

	case IN:
	case INDEX:
		tag = 0;
		temp = np->n_type;
		tnp = np->n_left;
		np = np->n_right;
		/* initially formal var name and array key name are the same */
		fname = aname = tnp->n_name;
		if (tnp->n_type == PARM) {
			tnp = tnp->n_next;
			tag = tnp->n_scope;
			if (!(tnp->n_flags & FLARRAY)) {
				tnp = tnp->n_alink;
			}
			aname = tnp->n_name;
		}
		if (tnp->n_type != ARRAY) {
			if (!isstring(tnp->n_flags) || tnp->n_string != _null)
				awkerr(notarray, fname);
			else {
				/* promotion to array */
				promote(tnp);
				if (tnp->n_alink != NNULL) {
					tag = tnp->n_scope;
					if (!(tnp->n_flags & FLARRAY))
						tnp = tnp->n_alink;
					aname = tnp->n_name;
				} else {
					tag = 0;
					if (tnp->n_flags & FLARRAY)
						tag = tnp->n_scope;
				}
			}
		}
		if (tnp == varSYMTAB) {
			if (np == NNULL || np->n_type == COMMA)
				awkerr(gettext(
				    "SYMTAB must have exactly one index"));
			np = vlook(exprstring(np));
			return (np);
		}
		cp = makeindex(np, aname, tag);
		if (temp == INDEX) {
			np = vlook(cp);
			if (!(np->n_flags & FINARRAY)) {
				np->n_alink = tnp->n_alink;
				tnp->n_alink = np;
				np->n_flags |= FINARRAY;
			}
		} else
			np = vlookup(cp, 1) == NNULL ? const0 : const1;
		if (cp != indexbuf)
			free(cp);
		return (np);

	case CONCAT:
		++concflag;
		np = exprconcat(np, 0);
		--concflag;
		return (np);

	case NOT:
		return (intnode(exprtest(np->n_left) == 0 ? (INT)1 : (INT)0));

	case AND:
		return ((exprtest(np->n_left) != 0 &&
		    exprtest(np->n_right) != 0) ? const1 : const0);

	case OR:
		return ((exprtest(np->n_left) != 0 ||
		    exprtest(np->n_right) != 0) ? const1 : const0);

	case EXP:
		{
			double f1, f2;

			/*
			 * evaluate expressions in proper order before
			 * calling pow().
			 * Can't guarantee that compiler will do this
			 * correctly for us if we put them inline.
			 */
			f1 = (double)exprreal(np->n_left);
			f2 = (double)exprreal(np->n_right);
			return (realnode((REAL)pow(f1, f2)));
		}

	case QUEST:
		if (np->n_right->n_type != COLON)
			awkerr(interr, "?:");
		if (exprtest(np->n_left))
			np = np->n_right->n_left; else
			np = np->n_right->n_right;
		return (exprreduce(np));

	case EQ:
	case NE:
	case GE:
	case LE:
	case GT:
	case LT:
		return (comparison(np));

	case ADD:
	case SUB:
	case MUL:
	case DIV:
	case REM:
		return (arithmetic(np));

	case DEC:
		inc_oper->n_type = SUB;
		goto do_inc_op;
	case INC:
		inc_oper->n_type = ADD;
do_inc_op:
		if ((np = np->n_left)->n_type == INDEX)
			np = exprreduce(np);
		if (np->n_flags & FREAL)
			tnp = realnode(np->n_real);
		else
			tnp = intnode(exprint(np));
		inc_oper->n_left = np;
		(void) assign(np, inc_oper);
		return (tnp);

	case PRE_DEC:
		inc_oper->n_type = SUB;
		goto do_pinc_op;
	case PRE_INC:
		inc_oper->n_type = ADD;
do_pinc_op:
		if ((np = np->n_left)->n_type == INDEX)
			np = exprreduce(np);
		inc_oper->n_left = np;
		return (assign(np, inc_oper));

	case AADD:
		asn_oper->n_type = ADD;
		goto do_asn_op;
	case ASUB:
		asn_oper->n_type = SUB;
		goto do_asn_op;
	case AMUL:
		asn_oper->n_type = MUL;
		goto do_asn_op;
	case ADIV:
		asn_oper->n_type = DIV;
		goto do_asn_op;
	case AREM:
		asn_oper->n_type = REM;
		goto do_asn_op;
	case AEXP:
		asn_oper->n_type = EXP;
do_asn_op:
		asn_oper->n_right = np->n_right;
		if ((np = np->n_left)->n_type == INDEX)
			np = exprreduce(np);
		asn_oper->n_left = np;
		return (assign(np, asn_oper));


	case GETLINE:
		return (f_getline(np));

	case CALLFUNC:
		return ((*np->n_left->n_function)(np->n_right));

	case RE:
		if (regmatch(np->n_regexp, linebuf) == REG_OK)
			return (const1);
		return (const0);

	case TILDE:
		cp = exprstring(np->n_left);
		if (regmatch(getregexp(np->n_right), cp) == REG_OK)
			return (const1);
		return (const0);

	case NRE:
		cp = exprstring(np->n_left);
		if (regmatch(getregexp(np->n_right), cp) != REG_OK)
			return (const1);
		return (const0);

	case ASG:
		return (assign(np->n_left, np->n_right));

	case ARRAY:
		awkerr(badarray, np->n_name);

	/*FALLTHRU*/
	case UFUNC:
		awkerr(varnotfunc, np->n_name);

	/*FALLTHRU*/
	default:
		awkerr(gettext("panic: exprreduce(%d)"), t);
		/* NOTREACHED */
	}
	return (0);
}

/*
 * Do arithmetic operators.
 */
static NODE *
arithmetic(NODE *np)
{
	register NODE *left, *right;
	int type;
	register INT i1, i2;
	register INT iresult;
	register REAL r1, r2;

	left = exprreduce(np->n_left);
	if (isreal(left->n_flags) ||
	    (isstring(left->n_flags) && (type_of(left)&FVREAL))) {
		type = FREAL;
		r1 = exprreal(left);
		r2 = exprreal(np->n_right);
	} else {
		i1 = exprint(left);
		right = exprreduce(np->n_right);
		if (isreal(right->n_flags) ||
		    (isstring(right->n_flags) && (type_of(right)&FVREAL))) {

			type = FREAL;
			r1 = i1;
			r2 = exprreal(right);
		} else {
			type = FINT;
			i2 = exprint(right);
		}
	}
reswitch:
	switch (np->n_type) {
	case ADD:
		if (type == FINT) {
			iresult = i1 + i2;
			addoverflow();
		} else
			r1 += r2;
		break;

	/*
	 * Strategically placed between ADD and SUB
	 * so "jo" branches will reach on 80*86
	 */
	overflow:
		r1 = i1;
		r2 = i2;
		type = FREAL;
		goto reswitch;

	case SUB:
		if (type == FINT) {
			iresult = i1 - i2;
			suboverflow();
		} else
			r1 -= r2;
		break;

	case MUL:
		if (type == FINT) {
			iresult = i1 * i2;
			muloverflow();
		} else
			r1 *= r2;
		break;

	case DIV:
		if (type == FINT) {
			r1 = i1;
			r2 = i2;
			type = FREAL;
		}
		if (r2 == 0.0)
			awkerr(divzero);
		r1 /= r2;
		break;

	case REM:
		if (type == FINT) {
			if (i2 == 0)
				awkerr(divzero);
			iresult = i1 % i2;
		} else {
			double fmod(double x, double y);

			errno = 0;
			r1 = fmod(r1, r2);
			if (errno == EDOM)
				awkerr(divzero);
		}
		break;
	}
	return (type == FINT ? intnode(iresult) : realnode(r1));
}

/*
 * Do comparison operators.
 */
static NODE *
comparison(NODE *np)
{
	register NODE *left, *right;
	register int cmp;
	int tl, tr;
	register REAL r1, r2;
	register INT i1, i2;

	left = np->n_left;
	if (isleaf(left->n_flags)) {
		if (left->n_type == PARM)
			left = left->n_next;
	} else
		left = exprreduce(left);
	tl = left->n_flags;
	right = np->n_right;
	if (isleaf(right->n_flags)) {
		if (right->n_type == PARM)
			right = right->n_next;
	} else {
		++concflag;
		right = exprreduce(right);
		--concflag;
	}
	tr = right->n_flags;
	/*
	 * Posix mandates semantics for the comparison operators that
	 * are incompatible with traditional AWK behaviour. If the following
	 * define is true then awk will use the traditional behaviour.
	 * if it's false, then AWK will use the POSIX-mandated behaviour.
	 */
#define	TRADITIONAL 0
#if TRADITIONAL
	if (!isnumber(tl) || !isnumber(tr)) {
		cmp = wcscoll((wchar_t *)exprstring(left),
		    (wchar_t *)exprstring(right));
	} else if (isreal(tl) || isreal(tr)) {
		r1 = exprreal(left);
		r2 = exprreal(right);
		if (r1 < r2)
			cmp = -1;
		else if (r1 > r2)
			cmp = 1;
		else
			cmp = 0;
	} else {
		i1 = exprint(left);
		i2 = exprint(right);
		if (i1 < i2)
			cmp = -1;
		else if (i1 > i2)
			cmp = 1;
		else
			cmp = 0;
	}
#else
	if (!isnumber(tl) && !isnumber(tr)) {
do_strcmp:
		cmp = wcscoll((wchar_t *)exprstring(left),
		    (wchar_t *)exprstring(right));
	} else {
		if (isstring(tl))
			tl = type_of(left);
		if (isstring(tr))
			tr = type_of(right);
		if (!isnumber(tl) || !isnumber(tr))
			goto do_strcmp;
		if (isreal(tl) || isreal(tr)) {
			r1 = exprreal(left);
			r2 = exprreal(right);
			if (r1 < r2)
				cmp = -1;
			else if (r1 > r2)
				cmp = 1;
			else
				cmp = 0;
		} else {
			i1 = exprint(left);
			i2 = exprint(right);
			if (i1 < i2)
				cmp = -1;
			else if (i1 > i2)
				cmp = 1;
			else
				cmp = 0;
		}
	}
#endif
	switch (np->n_type) {
	case EQ:
		return (cmp == 0 ? const1 : const0);

	case  NE:
		return (cmp != 0 ? const1 : const0);

	case GE:
		return (cmp >= 0 ? const1 : const0);

	case LE:
		return (cmp <= 0 ? const1 : const0);

	case GT:
		return (cmp > 0 ? const1 : const0);

	case LT:
		return (cmp < 0 ? const1 : const0);

	default:
		awkerr(interr, "comparison");
	}
	/* NOTREACHED */
	return (0);
}

/*
 * Return the type of a constant that is a string.
 * The node must be a FSTRING type and the return value
 * will possibly have FVINT or FVREAL or'ed in.
 */
static int
type_of(NODE *np)
{
	wchar_t *cp;
	int somedigits = 0;
	int seene = 0;
	int seenradix = 0;
	int seensign = 0;
	int digitsaftere = 0;

	cp = (wchar_t *)np->n_string;
	if (*cp == '\0')
		return (FSTRING|FVINT);
	while (iswspace(*cp))
		cp++;
	if (*cp == '-' || *cp == '+')
		cp++;
	while (*cp != '\0') {
		switch (*cp) {
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			if (seene)
				digitsaftere = 1;
			somedigits++;
			break;

		case 'E':
		case 'e':
			if (seene || !somedigits)
				return (FSTRING);
			seene = 1;
			break;

		case '+':
		case '-':
			if (seensign || !seene || digitsaftere)
				return (FSTRING);
			seensign = 1;
			break;

		default:
			if (*cp == radixpoint) {
				if (seenradix || seene || (!somedigits &&
				    !iswdigit(*++cp)))
					return (FSTRING);
			} else
				return (FSTRING);
			seenradix = 1;
		}
		cp++;
	}
	if (somedigits == 0)
		return (FSTRING);
	if (somedigits >= MAXDIGINT || seenradix || seene) {
		if (seensign && !digitsaftere)
			return (FSTRING);
		else
			return (FSTRING|FVREAL);
	} else
		return (FSTRING|FVINT);
}

/*
 * Return a field rvalue.
 */
static NODE *
rfield(INT fieldno)
{
	register wchar_t *cp;

	if (fieldno == 0)
		return (stringnode(linebuf, FSTATIC|FSENSE, lbuflen));
	if (!splitdone)
		fieldsplit();
	if (fieldno > nfield || fieldno < 0)
		return (stringnode(_null, FSTATIC, 0));
	cp = fields[fieldno-1];
	return (stringnode(cp, FSTATIC|FSENSE, wcslen(cp)));
}

/*
 * Split linebuf into fields.  Done only once
 * per input record (maximum).
 */
void
fieldsplit()
{
	register wchar_t *ip, *op;
	register int n;
	wchar_t *ep;

	if (fieldbuf == NULL)
		fieldbuf = emalloc(NLINE * sizeof (wchar_t));
	fcount = 0;
	ep = linebuf;
	op = fieldbuf;
	while ((ip = (*awkfield)(&ep)) != NULL) {
		fields[fcount++] = op;
		if (fcount > NFIELD)
			awkerr(tmfld, NFIELD);
		n = ep-ip;
		(void) memcpy(op, ip, n * sizeof (wchar_t));
		op += n;
		*op++ = '\0';
	}
	if (varNF->n_flags & FINT)
		varNF->n_int = fcount;
	else {
		constant->n_int = fcount;
		(void) nassign(varNF, constant);
	}
	nfield = fcount;
	splitdone++;
}

/*
 * Assign to a field as an lvalue.
 * Return the unevaluated node as one doesn't always need it
 * evaluated in an assignment.
 */
static NODE *
lfield(INT fieldno, NODE *np)
{
	register wchar_t *cp;
	register wchar_t *op;
	register wchar_t *sep;
	register int i;
	register wchar_t *newval;
	register int seplen;
	register int newlen;

	newlen = wcslen(newval = (wchar_t *)exprstring(np));
	if (fieldno == 0) {
		splitdone = 0;
		(void) memcpy(linebuf, newval, (newlen+1) * sizeof (wchar_t));
		lbuflen = newlen;
		fieldsplit();
	} else {
		seplen = wcslen(sep = (wchar_t *)exprstring(varOFS));
		if (!splitdone)
			fieldsplit();
		if (--fieldno < nfield &&
		    (newlen <= wcslen(fields[fieldno]))) {
			(void) memcpy(fields[fieldno], newval,
				(newlen+1) * sizeof (wchar_t));
		} else {
			register wchar_t *buf;

			buf = fieldbuf;
			fieldbuf = emalloc(NLINE * sizeof (wchar_t));
			if (fieldno >= nfield) {
				if (fieldno >= NFIELD)
					awkerr(tmfld, NFIELD);
				while (nfield < fieldno)
					fields[nfield++] = _null;
				++nfield;
			}
			fields[fieldno] = newval;
			op = fieldbuf;
			for (i = 0; i < nfield; i++) {
				newlen = wcslen(cp = fields[i])+1;
				fields[i] = op;
				if (op+newlen >= fieldbuf+NLINE)
					awkerr(toolong, NLINE);
				(void) memcpy(op, cp,
				    newlen * sizeof (wchar_t));
				op += newlen;
			}
			free(buf);
		}
		/*
		 * Reconstruct $0
		 */
		op = linebuf;
		i = 0;
		while (i < nfield) {
			newlen = wcslen(cp = fields[i++]);
			(void) memcpy(op, cp, newlen * sizeof (wchar_t));
			op += newlen;
			if (i < nfield) {
				(void) memcpy(op, sep,
					seplen * sizeof (wchar_t));
				op += seplen;
			}
			if (op >= &linebuf[NLINE])
				awkerr(toolong, NLINE);
		}
		*op = '\0';
		lbuflen = op-linebuf;
		if (varNF->n_flags & FINT)
			varNF->n_int = nfield;
		else {
			constant->n_int = nfield;
			(void) nassign(varNF, constant);
		}
	}
	return (np);
}

/*
 * Do a user function.
 * Each formal parameter must:
 *	have the actual parameter assigned to it (call by value),
 *	have a pointer to an array put into it (call by reference),
 *	and be made undefined (extra formal parameters)
 */
static NODE *
userfunc(NODE *np)
{
	register NODE *temp;
	NODE *fnp;

	if ((fnp = np->n_left) == NNULL)
		awkerr(gettext("impossible function call"));
	if (fnp->n_type != UFUNC)
		awkerr(varnotfunc, fnp->n_name);

#ifndef M_STKCHK
	if (slevel >= NRECUR)
		awkerr(gettext("function \"%S\" nesting level > %u"),
		    fnp->n_name, NRECUR);
#else
	if (!M_STKCHK)
		awkerr(gettext("function \"%s\" nesting level too deep"),
		    fnp->n_name);
#endif

	fnp = fnp->n_ufunc;
	{
		register NODE *formal;
		register NODE *actual;
		NODE *formlist, *actlist, *templist, *temptail;

		templist = temptail = NNULL;
		actlist = np->n_right;
		formlist = fnp->n_left;
		/*
		 * pass through formal list, setting up a list
		 * (on templist) containing temps for the values
		 * of the actuals.
		 * If the actual list runs out before the formal
		 * list, assign 'constundef' as the value
		 */
		while ((formal = getlist(&formlist)) != NNULL) {
			register NODE *array;
			register int t;
			register size_t len;
			register int scope_tag;

			actual = getlist(&actlist);
			if (actual == NNULL) {
				actual = constundef;
				scope_tag = slevel+1;
			} else
				scope_tag = 0;
			array = actual;
			switch (actual->n_type) {
			case ARRAY:
				t = ARRAY;
				scope_tag = 0;
				break;

			case PARM:
				array = actual = actual->n_next;
				t = actual->n_type;
				scope_tag = actual->n_scope;
				if (!(actual->n_flags & FLARRAY))
					array = actual->n_alink;
				break;

			default:
				t = VAR;
				break;
			}
			temp = emptynode(t, len = wcslen(formal->n_name));
			(void) memcpy(temp->n_name, formal->n_name,
			    (len+1) * sizeof (wchar_t));
			temp->n_flags = FSTRING|FVINT;
			temp->n_string = _null;
			temp->n_strlen = 0;
			if (t == VAR)
				(void) assign(temp, actual);
			if (t != ARRAY)
				temp->n_flags |= FLARRAY;
			temp->n_scope = scope_tag;
			/*
			 * link to actual parameter in case of promotion to
			 * array
			 */
			if (actual != constundef)
				temp->n_alink = actual;
			/*
			 * Build the templist
			 */
			if (templist != NNULL) {
				temptail->n_next = temp;
				temptail = temp;
			} else
				templist = temptail = temp;
			temp->n_next = NNULL;
			if (actual->n_type == CONSTANT)
				temp->n_alink = temp;
			else
				temp->n_alink = array;
		}
		/*
		 * Bind results of the evaluation of actuals to formals.
		 */
		formlist = fnp->n_left;
		while (templist != NNULL) {
			temp = templist;
			templist = temp->n_next;
			formal = getlist(&formlist);
			temp->n_next = formal->n_next;
			formal->n_next = temp;








		}
	}
	{
		register NODE *savenode = curnode;

		++slevel;
		if (action(fnp->n_right) == RETURN)
			np = retval; else
			np = const0;
		curnode = savenode;
	}
	{
		register NODE *formal;
		NODE *formlist;

		formlist = fnp->n_left;
		while ((formal = getlist(&formlist)) != NNULL) {
			temp = formal->n_next;
			formal->n_next = temp->n_next;
			/* if node is a local array, free the elements */
			if (temp->n_type == ARRAY && (temp->n_scope == slevel))
				delarray(temp);
			freenode(temp);
		}
	}
	--slevel;
	return (np);
}

/*
 * Get the regular expression from an expression tree.
 */
REGEXP
getregexp(NODE *np)
{
	if (np->n_type == RE)
		return (np->n_regexp);
	np = renode((wchar_t *)exprstring(np));
	return (np->n_regexp);
}

/*
 * Get the next element from a list.
 */
NODE *
getlist(NODE **npp)
{
	register NODE *np;

	if ((np = *npp) == NNULL)
		return (np);
	if (np->n_type == COMMA) {
		*npp = np->n_right;
		return (np->n_left);
	} else {
		*npp = NNULL;
		return (np);
	}
}

/*
 * if statement.
 */
static int
s_if(NODE *np)
{
	register NODE *xp;
	register int test;

	test = exprtest(np->n_left);
	xp = np->n_right;
	if (xp->n_type != ELSE)
		awkerr(interr, "if/else");
	if (test)
		xp = xp->n_left;
	else
		xp = xp->n_right;
	return (action(xp));
}

/*
 * while and do{}while statements.
 */
static int
s_while(NODE *np)
{
	register int act = 0;

	if (np->n_type == DO)
		goto dowhile;
	for (;;) {
		if (exprtest(np->n_left) == 0)
			break;
	dowhile:
		if ((act = action(np->n_right)) != 0) {
			switch (act) {
			case BREAK:
				act = 0;
				break;

			case CONTINUE:
				act = 0;
				continue;
			}
			break;
		}
	}
	return (act);
}

/*
 * for statement.
 */
static int
s_for(NODE *np)
{
	register NODE *testnp, *incnp, *initnp;
	register int act = 0;
	NODE *listp;

	listp = np->n_left;
	initnp = getlist(&listp);
	testnp = getlist(&listp);
	incnp = getlist(&listp);
	if (initnp != NNULL)
		(void) exprreduce(initnp);
	for (;;) {
		if (exprtest(testnp) == 0)
			break;
		if ((act = action(np->n_right)) != 0) {
			switch (act) {
			case BREAK:
				act = 0;
				break;

			case CONTINUE:
				act = 0;
				goto clabel;
			}
			break;
		}
	clabel:
		if (incnp != NNULL)
			(void) exprreduce(incnp);
	}
	return (act);
}

/*
 * for variable in array statement.
 */
static int
s_forin(NODE *np)
{
	register NODE *left;
	register int act = 0;
	register NODE *var;
	register NODE **nnp;
	register NODE *statement;
	register int issymtab = 0;
	wchar_t *index;
	register int alen;
	int nbuck;

	left = np->n_left;
	statement = np->n_right;
	if (left->n_type != IN)
		awkerr(interr, "for (var in array)");
	if ((var = left->n_left)->n_type == PARM)
		var = var->n_next;
	np = left->n_right;
	if (np->n_type == PARM) {
		np = np->n_next;
		if (!(np->n_flags & FLARRAY))
			np = np->n_alink;
	}
	if (np == varSYMTAB) {
		issymtab++;
		np = NNULL;
		nbuck = 0;
	} else {
		/*
		 * At this point if the node is not actually an array
		 * check to see if it has already been established as
		 * a scalar. If it is a scalar then flag an error. If
		 * not then promote the object to an array type.
		 */
		if (np->n_type != ARRAY) {
			if (!isstring(np->n_flags) || np->n_string != _null)
				awkerr(notarray, np->n_name);
			else {
				/* promotion to array */
				promote(np);
				if (np->n_alink != NNULL)
					if (!(np->n_flags & FLARRAY))
						np = np->n_alink;
			}
		}
		/*
		 * Set up a pointer to the first node in the array list.
		 * Save this pointer on the delete stack. This information
		 * is used by the delete function to advance any pointers
		 * that might be pointing at a node which has been deleted.
		 * See the delsymtab() function for more information. Note
		 * that if the a_link field is nil, then just return 0 since
		 * this array has no elements yet.
		 */
		if ((*(nnp = next_forin) = np->n_alink) == 0)
			return (0);
		if (++next_forin > &forindex[NFORINLOOP])
			awkerr(toodeep, NFORINLOOP);
		/*
		 * array elements have names of the form
		 *	<name>]<index> (global arrays)
		 * or
		 *	<name>[<scope>]<index> (local arrays)
		 * We need to know the offset of the index portion of the
		 * name string in order to place it in the index variable so
		 * we look for the ']'. This is calculated here and then
		 * used below.
		 */
		for (alen = 0; (*nnp)->n_name[alen++] != ']'; )
			if ((*nnp)->n_name[alen] == '\0')
				awkerr(interr, "for: invalid array");
	}
	for (;;) {
		if (issymtab) {
			if ((left = symwalk(&nbuck, &np)) == NNULL)
				break;
			index = left->n_name;
		} else {
			if ((np = *nnp) == NNULL)
				break;
			index = np->n_name+alen;
			*nnp = np->n_alink;
		}
		strassign(var, index, FSTATIC, wcslen(index));
		if ((act = action(statement)) != 0) {
			switch (act) {
			case BREAK:
				act = 0;
				break;

			case CONTINUE:
				act = 0;
				continue;
			}
			break;
		}
	}
	next_forin--;
	return (act);
}

/*
 * Walk the symbol table using the same algorithm as arraynode.
 */
NODE *
symwalk(int *buckp, NODE **npp)
{
	register NODE *np;

	np = *npp;
	for (;;) {
		while (np == NNULL) {
			if (*buckp >= NBUCKET)
				return (*npp = NNULL);
			np = symtab[(*buckp)++];
		}
		if (np->n_type == VAR &&
		    (!isstring(np->n_flags) || np->n_string != _null)) {
			*npp = np->n_next;
			return (np);
		}
		np = np->n_next;
	}
	/* NOTREACHED */
}

/*
 * Test the result of an expression.
 */
static int
exprtest(NODE *np)
{
	register int t;

	if (np == NNULL)
		return (1);
	if (freelist != NNULL)
		freetemps();
	np = exprreduce(np);
	if (isint(t = np->n_flags)) {
		if (isstring(t))
			return (exprint(np) != 0);
		return (np->n_int != 0);
	}
	if (isreal(t)) {
		REAL rval;

		rval = isstring(t) ? exprreal(np) : np->n_real;
		return (rval != 0.0);
	}
	return (*(wchar_t *)exprstring(np) != '\0');
}

/*
 * Return malloc'ed space that holds the given name "[" scope "]" index ...
 * concatenated string.
 * The node (np) is the list of indices and 'array' is the array name.
 */
static wchar_t *
makeindex(NODE *np, wchar_t *array, int tag)
{
	static wchar_t tags[sizeof (int)];
	static wchar_t tag_chars[] = M_MB_L("0123456789ABCDEF");
	register wchar_t *cp;
	register NODE *index;
	register uint_t n;
	register int len;
	register wchar_t *indstr;
	register wchar_t *sep;
	register int seplen;
	register int taglen;


	/*
	 * calculate and create the tag string
	 */
	for (taglen = 0; tag; tag >>= 4)
		tags[taglen++] = tag_chars[tag & 0xf];
	/*
	 * Special (normal) case: only one index.
	 */
	if (np->n_type != COMMA) {
		wchar_t *ocp;
		size_t i;

		if (isleaf(np->n_flags) && np->n_type == PARM)
			np = np->n_next;
		if (isstring(np->n_flags)) {
			indstr = np->n_string;
			len = np->n_strlen;
		} else {
			indstr = exprstring(np);
			len = wcslen(indstr);
		}
		i = (n = wcslen(array)) + len + 3 + taglen;
		if (i < NINDEXBUF)
			ocp = indexbuf;
		else
			ocp = emalloc(i * sizeof (wchar_t));
		(void) memcpy(ocp, array, n * sizeof (wchar_t));
		cp = ocp+n;
		if (taglen) {
			*cp++ = '[';
			while (taglen)
				*cp++ = tags[--taglen];
		}
		*cp++ = ']';
		(void) memcpy(cp, indstr, (len+1) * sizeof (wchar_t));

		return (ocp);
	}
	n = 0;
	seplen = wcslen(sep = (wchar_t *)exprstring(varSUBSEP));
	while ((index = getlist(&np)) != NNULL) {
		indstr = exprstring(index);
		len = wcslen(indstr);
		if (n == 0) {
			cp = emalloc(sizeof (wchar_t) * ((n = wcslen(array)) +
				len + 3 + taglen));
			(void) memcpy(cp, array, n * sizeof (wchar_t));
			if (taglen) {
				cp[n++] = '[';
				while (taglen)
					cp[n++] = tags[--taglen];
			}
			cp[n++] = ']';
		} else {
			cp = erealloc(cp, (n+len+seplen+1) * sizeof (wchar_t));
			(void) memcpy(cp+n, sep, seplen * sizeof (wchar_t));
			n += seplen;
		}
		(void) memcpy(cp+n, indstr, (len+1) * sizeof (wchar_t));
		n += len;
	}
	return (cp);
}


/*
 * Promote a node to an array. In the simplest case, just set the
 * node type field to ARRAY. The more complicated case involves walking
 * a list of variables that haven't been determined yet as scalar or array.
 * This routine plays with the pointers to avoid recursion.
 */
void
promote(NODE *n)
{
	register NODE *prev = NNULL;
	register NODE *next;

	/*
	 * walk down the variable chain, reversing the pointers and
	 * setting each node to type array.
	 */
	while ((n->n_flags & FLARRAY) && (n->n_alink != n)) {
		n->n_type = ARRAY;
		next = n->n_alink;
		n->n_alink = prev;
		prev = n;
		n = next;
	}

	/*
	 * If the final entity on the chain is a local variable, then
	 * reset it's alink field to NNULL - normally it points back
	 * to itself - this is used in other parts of the code to
	 * reduce the number of conditionals when handling locals.
	 */
	n->n_type = ARRAY;
	if (n->n_flags & FLARRAY)
		n->n_alink = NNULL;

	/*
	 * Now walk back up the list setting the alink to point to
	 * the last entry in the chain and clear the 'local array'
	 * flag.
	 */
	while (prev != NNULL) {
		prev->n_flags &= ~FLARRAY;
		next = prev->n_alink;
		prev->n_alink = n;
		prev = next;
	}
}