/*
 * eval.c - gawk bytecode interpreter
 */

/*
 * Copyright (C) 1986, 1988, 1989, 1991-2019, 2021, 2022, 2023,
 * the Free Software Foundation, Inc.
 *
 * This file is part of GAWK, the GNU implementation of the
 * AWK Programming 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 3 of the License, 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

#include "awk.h"
#include <math.h>

NODE **fcall_list = NULL;
long fcall_count = 0;
int currule = 0;
IOBUF *curfile = NULL;		/* current data file */
bool exiting = false;

int (*interpret)(INSTRUCTION *);
#define MAX_EXEC_HOOKS	10
static int num_exec_hook = 0;
static Func_pre_exec pre_execute[MAX_EXEC_HOOKS];
static Func_post_exec post_execute = NULL;

static double fix_nan_sign(double left, double right, double result);

extern void frame_popped();

int OFSlen;
int ORSlen;
int OFMTidx;
int CONVFMTidx;

static NODE *node_Boolean[2];

/* This rather ugly macro is for VMS C */
#ifdef C
#undef C
#endif
#define C(c) ((char)c)
/*
 * This table is used by the regexp routines to do case independent
 * matching. Basically, every ascii character maps to itself, except
 * uppercase letters map to lower case ones. This table has 256
 * entries, for ISO 8859-1. Note also that if the system this
 * is compiled on doesn't use 7-bit ascii, casetable[] should not be
 * defined to the linker, so gawk should not load.
 *
 * Do NOT make this array static, it is used in several spots, not
 * just in this file.
 *
 * 6/2004:
 * This table is also used for IGNORECASE for == and !=, and index().
 * Although with GLIBC, we could use tolower() everywhere and RE_ICASE
 * for the regex matcher, precomputing this table once gives us a
 * performance improvement.  I also think it's better for portability
 * to non-GLIBC systems.  All the world is not (yet :-) GNU/Linux.
 */
#if 'a' == 97	/* it's ascii */
char casetable[] = {
	'\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007',
	'\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017',
	'\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027',
	'\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037',
	/* ' '     '!'     '"'     '#'     '$'     '%'     '&'     ''' */
	'\040', '\041', '\042', '\043', '\044', '\045', '\046', '\047',
	/* '('     ')'     '*'     '+'     ','     '-'     '.'     '/' */
	'\050', '\051', '\052', '\053', '\054', '\055', '\056', '\057',
	/* '0'     '1'     '2'     '3'     '4'     '5'     '6'     '7' */
	'\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067',
	/* '8'     '9'     ':'     ';'     '<'     '='     '>'     '?' */
	'\070', '\071', '\072', '\073', '\074', '\075', '\076', '\077',
	/* '@'     'A'     'B'     'C'     'D'     'E'     'F'     'G' */
	'\100', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
	/* 'H'     'I'     'J'     'K'     'L'     'M'     'N'     'O' */
	'\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
	/* 'P'     'Q'     'R'     'S'     'T'     'U'     'V'     'W' */
	'\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
	/* 'X'     'Y'     'Z'     '['     '\'     ']'     '^'     '_' */
	'\170', '\171', '\172', '\133', '\134', '\135', '\136', '\137',
	/* '`'     'a'     'b'     'c'     'd'     'e'     'f'     'g' */
	'\140', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
	/* 'h'     'i'     'j'     'k'     'l'     'm'     'n'     'o' */
	'\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
	/* 'p'     'q'     'r'     's'     't'     'u'     'v'     'w' */
	'\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
	/* 'x'     'y'     'z'     '{'     '|'     '}'     '~' */
	'\170', '\171', '\172', '\173', '\174', '\175', '\176', '\177',

	/* Latin 1: */
	/*
	 * 4/2019: This is now overridden; in single byte locales
	 * we call load_casetable from main and it fills in the values
	 * based on the current locale. In particular, we want LC_ALL=C
	 * to work correctly for values >= 0200.
	 */
	C('\200'), C('\201'), C('\202'), C('\203'), C('\204'), C('\205'), C('\206'), C('\207'),
	C('\210'), C('\211'), C('\212'), C('\213'), C('\214'), C('\215'), C('\216'), C('\217'),
	C('\220'), C('\221'), C('\222'), C('\223'), C('\224'), C('\225'), C('\226'), C('\227'),
	C('\230'), C('\231'), C('\232'), C('\233'), C('\234'), C('\235'), C('\236'), C('\237'),
	C('\240'), C('\241'), C('\242'), C('\243'), C('\244'), C('\245'), C('\246'), C('\247'),
	C('\250'), C('\251'), C('\252'), C('\253'), C('\254'), C('\255'), C('\256'), C('\257'),
	C('\260'), C('\261'), C('\262'), C('\263'), C('\264'), C('\265'), C('\266'), C('\267'),
	C('\270'), C('\271'), C('\272'), C('\273'), C('\274'), C('\275'), C('\276'), C('\277'),
	C('\340'), C('\341'), C('\342'), C('\343'), C('\344'), C('\345'), C('\346'), C('\347'),
	C('\350'), C('\351'), C('\352'), C('\353'), C('\354'), C('\355'), C('\356'), C('\357'),
	C('\360'), C('\361'), C('\362'), C('\363'), C('\364'), C('\365'), C('\366'), C('\327'),
	C('\370'), C('\371'), C('\372'), C('\373'), C('\374'), C('\375'), C('\376'), C('\337'),
	C('\340'), C('\341'), C('\342'), C('\343'), C('\344'), C('\345'), C('\346'), C('\347'),
	C('\350'), C('\351'), C('\352'), C('\353'), C('\354'), C('\355'), C('\356'), C('\357'),
	C('\360'), C('\361'), C('\362'), C('\363'), C('\364'), C('\365'), C('\366'), C('\367'),
	C('\370'), C('\371'), C('\372'), C('\373'), C('\374'), C('\375'), C('\376'), C('\377'),
};
#elif defined(USE_EBCDIC)
char casetable[] = {
 /*00  NU    SH    SX    EX    PF    HT    LC    DL */
      0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
 /*08              SM    VT    FF    CR    SO    SI */
      0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
 /*10  DE    D1    D2    TM    RS    NL    BS    IL */
      0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
 /*18  CN    EM    CC    C1    FS    GS    RS    US */
      0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
 /*20  DS    SS    FS          BP    LF    EB    EC */
      0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
 /*28              SM    C2    EQ    AK    BL       */
      0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
 /*30              SY          PN    RS    UC    ET */
      0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
 /*38                    C3    D4    NK          SU */
      0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
 /*40  SP                                           */
      0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
 /*48             CENT    .     <     (     +     | */
      0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
 /*50   &                                           */
      0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
 /*58               !     $     *     )     ;     ^ */
      0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
 /*60   -     /                                     */
      0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
 /*68               |     ,     %     _     >     ? */
      0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
 /*70                                               */
      0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
 /*78         `     :     #     @     '     =     " */
      0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
 /*80         a     b     c     d     e     f     g */
      0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
 /*88   h     i           {                         */
      0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
 /*90         j     k     l     m     n     o     p */
      0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
 /*98   q     r           }                         */
      0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
 /*A0         ~     s     t     u     v     w     x */
      0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
 /*A8   y     z                       [             */
      0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
 /*B0                                               */
      0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
 /*B8                                 ]             */
      0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
 /*C0   {     A     B     C     D     E     F     G */
      0xC0, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
 /*C8   H     I                                     */
      0x88, 0x89, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
 /*D0   }     J     K     L     M     N     O     P */
      0xD0, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
 /*D8   Q     R                                     */
      0x98, 0x99, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
 /*E0   \           S     T     U     V     W     X */
      0xE0, 0xE1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
 /*E8   Y     Z                                     */
      0xA8, 0xA9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
 /*F0   0     1     2     3     4     5     6     7 */
      0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
 /*F8   8     9                                     */
      0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
#else
#include "You lose. You will need a translation table for your character set."
#endif

#undef C

/* load_casetable --- for a non-ASCII locale, redo the table */

void
load_casetable(void)
{
#if defined(LC_CTYPE)
	int i;
	static bool loaded = false;

	if (loaded || do_traditional)
		return;

	loaded = true;

#ifndef USE_EBCDIC
	/* use of isalpha is ok here (see is_alpha in awkgram.y) */
	for (i = 0200; i <= 0377; i++) {
		if (isalpha(i) && islower(i) && i != toupper(i))
			casetable[i] = toupper(i);
		else
			casetable[i] = i;
	}
#endif
#endif
}

/*
 * This table maps node types to strings for debugging.
 * KEEP IN SYNC WITH awk.h!!!!
 */

static const char *const nodetypes[] = {
	"Node_illegal",
	"Node_val",
	"Node_regex",
	"Node_dynregex",
	"Node_var",
	"Node_var_array",
	"Node_var_new",
	"Node_elem_new",
	"Node_param_list",
	"Node_func",
	"Node_ext_func",
	"Node_builtin_func",
	"Node_array_ref",
	"Node_array_tree",
	"Node_array_leaf",
	"Node_dump_array",
	"Node_arrayfor",
	"Node_frame",
	"Node_instruction",
	"Node_final --- this should never appear",
	NULL
};


/*
 * This table maps Op codes to strings.
 * KEEP IN SYNC WITH awk.h!!!!
 */

static struct optypetab {
	char *desc;
	char *operator;
} optypes[] = {
	{ "Op_illegal", NULL },
	{ "Op_times", " * " },
	{ "Op_times_i", " * " },
	{ "Op_quotient", " / " },
	{ "Op_quotient_i", " / " },
	{ "Op_mod", " % " },
	{ "Op_mod_i", " % " },
	{ "Op_plus", " + " },
	{ "Op_plus_i", " + " },
	{ "Op_minus", " - " },
	{ "Op_minus_i", " - " },
	{ "Op_exp", " ^ " },
	{ "Op_exp_i", " ^ " },
	{ "Op_concat", " " },
	{ "Op_line_range", NULL },
	{ "Op_cond_pair", ", " },
	{ "Op_subscript", "[]" },
	{ "Op_sub_array", "[]" },
	{ "Op_preincrement", "++" },
	{ "Op_predecrement", "--" },
	{ "Op_postincrement", "++" },
	{ "Op_postdecrement", "--" },
	{ "Op_unary_minus", "-" },
	{ "Op_unary_plus", "+" },
	{ "Op_field_spec", "$" },
	{ "Op_not", "! " },
	{ "Op_assign", " = " },
	{ "Op_store_var", " = " },
	{ "Op_store_sub", " = " },
	{ "Op_store_field", " = " },
	{ "Op_store_field_exp", " = " },
	{ "Op_assign_times", " *= " },
	{ "Op_assign_quotient", " /= " },
	{ "Op_assign_mod", " %= " },
	{ "Op_assign_plus", " += " },
	{ "Op_assign_minus", " -= " },
	{ "Op_assign_exp", " ^= " },
	{ "Op_assign_concat", " " },
	{ "Op_and", " && " },
	{ "Op_and_final", NULL },
	{ "Op_or", " || " },
	{ "Op_or_final", NULL },
	{ "Op_equal", " == " },
	{ "Op_notequal", " != " },
	{ "Op_less", " < " },
	{ "Op_greater", " > " },
	{ "Op_leq", " <= " },
	{ "Op_geq", " >= " },
	{ "Op_match", " ~ " },
	{ "Op_match_rec", NULL },
	{ "Op_nomatch", " !~ " },
	{ "Op_rule", NULL },
	{ "Op_K_case", "case" },
	{ "Op_K_default", "default" },
	{ "Op_K_break", "break" },
	{ "Op_K_continue", "continue" },
	{ "Op_K_print", "print" },
	{ "Op_K_print_rec", "print" },
	{ "Op_K_printf", "printf" },
	{ "Op_K_next", "next" },
	{ "Op_K_exit", "exit" },
	{ "Op_K_return", "return" },
	{ "Op_K_return_from_eval", "return" },
	{ "Op_K_delete", "delete" },
	{ "Op_K_delete_loop", NULL },
	{ "Op_K_getline_redir", "getline" },
	{ "Op_K_getline", "getline" },
	{ "Op_K_nextfile", "nextfile" },
	{ "Op_K_namespace", "@namespace" },
	{ "Op_builtin", NULL },
	{ "Op_sub_builtin", NULL },
	{ "Op_ext_builtin", NULL },
	{ "Op_in_array", " in " },
	{ "Op_func_call", NULL },
	{ "Op_indirect_func_call", NULL },
	{ "Op_push", NULL },
	{ "Op_push_arg", NULL },
	{ "Op_push_arg_untyped", NULL },
	{ "Op_push_i", NULL },
	{ "Op_push_re", NULL },
	{ "Op_push_array", NULL },
	{ "Op_push_param", NULL },
	{ "Op_push_lhs", NULL },
	{ "Op_subscript_lhs", "[]" },
	{ "Op_field_spec_lhs", "$" },
	{ "Op_no_op", NULL },
	{ "Op_pop", NULL },
	{ "Op_jmp", NULL },
	{ "Op_jmp_true", NULL },
	{ "Op_jmp_false", NULL },
	{ "Op_get_record", NULL },
	{ "Op_newfile", NULL },
	{ "Op_arrayfor_init", NULL },
	{ "Op_arrayfor_incr", NULL },
	{ "Op_arrayfor_final", NULL },
	{ "Op_var_update", NULL },
	{ "Op_var_assign", NULL },
	{ "Op_field_assign", NULL },
	{ "Op_subscript_assign", NULL },
	{ "Op_after_beginfile", NULL },
	{ "Op_after_endfile", NULL },
	{ "Op_func", NULL },
	{ "Op_comment", NULL },
	{ "Op_exec_count", NULL },
	{ "Op_breakpoint", NULL },
	{ "Op_lint", NULL },
	{ "Op_lint_plus", NULL },
	{ "Op_atexit", NULL },
	{ "Op_stop", NULL },
	{ "Op_token", NULL },
	{ "Op_symbol", NULL },
	{ "Op_list", NULL },
	{ "Op_K_do", "do" },
	{ "Op_K_for", "for" },
	{ "Op_K_arrayfor", "for" },
	{ "Op_K_while", "while" },
	{ "Op_K_switch", "switch" },
	{ "Op_K_if", "if" },
	{ "Op_K_else", "else" },
	{ "Op_K_function", "function" },
	{ "Op_cond_exp", NULL },
	{ "Op_parens", NULL },
	{ "Op_final --- this should never appear", NULL },
	{ NULL, NULL },
};

/* nodetype2str --- convert a node type into a printable value */

const char *
nodetype2str(NODETYPE type)
{
	static char buf[40];

	if (type >= Node_illegal && type <= Node_final)
		return nodetypes[(int) type];

	sprintf(buf, _("unknown nodetype %d"), (int) type);
	return buf;
}

/* opcode2str --- convert an opcode type into a printable value */

const char *
opcode2str(OPCODE op)
{
	if (op >= Op_illegal && op < Op_final)
		return optypes[(int) op].desc;
	fatal(_("unknown opcode %d"), (int) op);
	return NULL;
}

/* op2str --- convert an opcode type to corresponding operator or keyword */

const char *
op2str(OPCODE op)
{
	if (op >= Op_illegal && op < Op_final) {
		if (optypes[(int) op].operator != NULL)
			return optypes[(int) op].operator;
		else
			fatal(_("opcode %s not an operator or keyword"),
					optypes[(int) op].desc);
	} else
		fatal(_("unknown opcode %d"), (int) op);
	return NULL;
}


/* flags2str --- make a flags value readable */

const char *
flags2str(int flagval)
{
	static const struct flagtab values[] = {
		{ MALLOC, "MALLOC" },
		{ STRING, "STRING" },
		{ STRCUR, "STRCUR" },
		{ NUMCUR, "NUMCUR" },
		{ NUMBER, "NUMBER" },
		{ USER_INPUT, "USER_INPUT" },
		{ BOOLVAL, "BOOL" },
		{ INTLSTR, "INTLSTR" },
		{ NUMINT, "NUMINT" },
		{ INTIND, "INTIND" },
		{ WSTRCUR, "WSTRCUR" },
		{ MPFN,	"MPFN" },
		{ MPZN,	"MPZN" },
		{ NO_EXT_SET, "NO_EXT_SET" },
		{ NULL_FIELD, "NULL_FIELD" },
		{ ARRAYMAXED, "ARRAYMAXED" },
		{ HALFHAT, "HALFHAT" },
		{ XARRAY, "XARRAY" },
		{ NUMCONSTSTR, "NUMCONSTSTR" },
		{ REGEX, "REGEX" },
		{ 0,	NULL },
	};

	return genflags2str(flagval, values);
}

/* genflags2str --- general routine to convert a flag value to a string */

const char *
genflags2str(int flagval, const struct flagtab *tab)
{
	static char buffer[BUFSIZ];
	char *sp;
	int i, space_left, space_needed;

	sp = buffer;
	space_left = BUFSIZ;
	for (i = 0; tab[i].name != NULL; i++) {
		if ((flagval & tab[i].val) != 0) {
			/*
			 * note the trick, we want 1 or 0 for whether we need
			 * the '|' character.
			 */
			space_needed = (strlen(tab[i].name) + (sp != buffer));
			if (space_left <= space_needed)
				fatal(_("buffer overflow in genflags2str"));

			if (sp != buffer) {
				*sp++ = '|';
				space_left--;
			}
			strcpy(sp, tab[i].name);
			/* note ordering! */
			space_left -= strlen(sp);
			sp += strlen(sp);
		}
	}

	*sp = '\0';
	return buffer;
}

/* posix_compare --- compare strings using strcoll */

static int
posix_compare(NODE *s1, NODE *s2)
{
	int ret;

	if (gawk_mb_cur_max == 1) {
		char save1, save2;
		const char *p1, *p2;

		save1 = s1->stptr[s1->stlen];
		s1->stptr[s1->stlen] = '\0';

		save2 = s2->stptr[s2->stlen];
		s2->stptr[s2->stlen] = '\0';

		p1 = s1->stptr;
		p2 = s2->stptr;

		for (;;) {
			size_t len;

			ret = strcoll(p1, p2);
			if (ret != 0)
				break;

			len = strlen(p1);
			p1 += len + 1;
			p2 += len + 1;

			if (p1 == s1->stptr + s1->stlen + 1) {
				if (p2 != s2->stptr + s2->stlen + 1)
					ret = -1;
				break;
			}
			if (p2 == s2->stptr + s2->stlen + 1) {
				ret = 1;
				break;
			}
		}

		s1->stptr[s1->stlen] = save1;
		s2->stptr[s2->stlen] = save2;
	}
	else {
		/* Similar logic, using wide characters */
		const wchar_t *p1, *p2;

		(void) force_wstring(s1);
		(void) force_wstring(s2);

		p1 = s1->wstptr;
		p2 = s2->wstptr;

		for (;;) {
			size_t len;

			ret = wcscoll(p1, p2);
			if (ret != 0)
				break;

			len = wcslen(p1);
			p1 += len + 1;
			p2 += len + 1;

			if (p1 == s1->wstptr + s1->wstlen + 1) {
				if (p2 != s2->wstptr + s2->wstlen + 1)
					ret = -1;
				break;
			}
			if (p2 == s2->wstptr + s2->wstlen + 1) {
				ret = 1;
				break;
			}
		}
	}

	return ret;
}


/* cmp_nodes --- compare two nodes, returning negative, 0, positive */

int
cmp_nodes(NODE *t1, NODE *t2, bool use_strcmp)
{
	int ret = 0;
	size_t len1, len2;
	int l, ldiff;

	if (t1 == t2)
		return 0;

	(void) fixtype(t1);
	(void) fixtype(t2);

	if ((t1->flags & NUMBER) != 0 && (t2->flags & NUMBER) != 0)
		return cmp_numbers(t1, t2);

	(void) force_string(t1);
	(void) force_string(t2);
	len1 = t1->stlen;
	len2 = t2->stlen;
	ldiff = len1 - len2;
	if (len1 == 0 || len2 == 0)
		return ldiff;

	if (do_posix && ! use_strcmp)
		return posix_compare(t1, t2);

	l = (ldiff <= 0 ? len1 : len2);
	if (IGNORECASE) {
		const unsigned char *cp1 = (const unsigned char *) t1->stptr;
		const unsigned char *cp2 = (const unsigned char *) t2->stptr;
		char save1 = t1->stptr[t1->stlen];
		char save2 = t2->stptr[t2->stlen];


		if (gawk_mb_cur_max > 1) {
			t1->stptr[t1->stlen] = t2->stptr[t2->stlen] = '\0';
			ret = strncasecmpmbs((const unsigned char *) cp1,
					     (const unsigned char *) cp2, l);
			t1->stptr[t1->stlen] = save1;
			t2->stptr[t2->stlen] = save2;
		} else {
			/* Could use tolower() here; see discussion above. */
			for (ret = 0; l-- > 0 && ret == 0; cp1++, cp2++)
				ret = casetable[*cp1] - casetable[*cp2];
		}
	} else
		ret = memcmp(t1->stptr, t2->stptr, l);

	ret = ret == 0 ? ldiff : ret;
	return ret;
}

/* push_frame --- push a frame NODE onto stack */

static void
push_frame(NODE *f)
{
	static long max_fcall;

	/* NB: frame numbering scheme as in GDB. frame_ptr => frame #0. */

	fcall_count++;
	if (fcall_list == NULL) {
		max_fcall = 10;
		emalloc(fcall_list, NODE **, (max_fcall + 1) * sizeof(NODE *), "push_frame");
	} else if (fcall_count == max_fcall) {
		max_fcall *= 2;
		erealloc(fcall_list, NODE **, (max_fcall + 1) * sizeof(NODE *), "push_frame");
	}

	if (fcall_count > 1)
		memmove(fcall_list + 2, fcall_list + 1, (fcall_count - 1) * sizeof(NODE *));
	fcall_list[1] = f;
}


/* pop_frame --- pop off a frame NODE*/

static void
pop_frame()
{
	if (fcall_count > 1)
		memmove(fcall_list + 1, fcall_list + 2, (fcall_count - 1) * sizeof(NODE *));
	fcall_count--;
	assert(fcall_count >= 0);
	if (do_debug)
		frame_popped();
}


/* dump_fcall_stack --- print a backtrace of the awk function calls */

void
dump_fcall_stack(FILE *fp)
{
	NODE *f, *func;
	long i = 0, k = 0;

	if (fcall_count == 0)
		return;
	fprintf(fp, _("\n\t# Function Call Stack:\n\n"));

	/* current frame */
	func = frame_ptr->func_node;
	fprintf(fp, "\t# %3ld. %s\n", k++, func->vname);

	/* outer frames except main */
	for (i = 1; i < fcall_count; i++) {
		f = fcall_list[i];
		func = f->func_node;
		fprintf(fp, "\t# %3ld. %s\n", k++, func->vname);
	}

	fprintf(fp, "\t# %3ld. -- main --\n", k);
}


/* set_IGNORECASE --- update IGNORECASE as appropriate */

void
set_IGNORECASE()
{
	static bool warned = false;

	if ((do_lint_extensions || do_traditional) && ! warned) {
		warned = true;
		lintwarn(_("`IGNORECASE' is a gawk extension"));
	}

	if (do_traditional)
		IGNORECASE = false;
   	else
		IGNORECASE = boolval(IGNORECASE_node->var_value);
	set_RS();	/* set_RS() calls set_FS() if need be, for us */
}

/* set_BINMODE --- set translation mode (OS/2, DOS, others) */

void
set_BINMODE()
{
	static bool warned = false;
	char *p;
	NODE *v = fixtype(BINMODE_node->var_value);

	if ((do_lint_extensions || do_traditional) && ! warned) {
		warned = true;
		lintwarn(_("`BINMODE' is a gawk extension"));
	}
	if (do_traditional)
		BINMODE = TEXT_TRANSLATE;
	else if ((v->flags & NUMBER) != 0) {
		BINMODE = get_number_si(v);
		/* Make sure the value is rational. */
		if (BINMODE < TEXT_TRANSLATE)
			BINMODE = TEXT_TRANSLATE;
		else if (BINMODE > BINMODE_BOTH)
			BINMODE = BINMODE_BOTH;
	} else if ((v->flags & STRING) != 0) {
		p = v->stptr;

		/*
		 * Allow only one of the following:
		 * "0", "1", "2", "3",
		 * "r", "w", "rw", "wr"
		 * ANYTHING ELSE goes to 3. So there.
		 */
		switch (v->stlen) {
		case 1:
			switch (p[0]) {
			case '0':
			case '1':
			case '2':
			case '3':
				BINMODE = p[0] - '0';
				break;
			case 'r':
				BINMODE = BINMODE_INPUT;
				break;
			case 'w':
				BINMODE = BINMODE_OUTPUT;
				break;
			default:
				BINMODE = BINMODE_BOTH;
				goto bad_value;
				break;
			}
			break;
		case 2:
			switch (p[0]) {
			case 'r':
				BINMODE = BINMODE_BOTH;
				if (p[1] != 'w')
					goto bad_value;
				break;
			case 'w':
				BINMODE = BINMODE_BOTH;
				if (p[1] != 'r')
					goto bad_value;
				break;
			}
			break;
		default:
	bad_value:
			lintwarn(_("BINMODE value `%s' is invalid, treated as 3"), p);
			break;
		}
	} else
		BINMODE = 3;		/* shouldn't happen */
}

/* set_OFS --- update OFS related variables when OFS assigned to */

void
set_OFS()
{
	static bool first = true;
	size_t new_ofs_len;

	if (first)	/* true when called from init_vars() in main() */
		first = false;
	else {
		/* rebuild $0 using OFS that was current when $0 changed */
		if (! field0_valid) {
			get_field(UNLIMITED - 1, NULL);
			rebuild_record();
		}
	}

	/*
	 * Save OFS value for use in building record and in printing.
	 * Can't just have OFS point into the OFS_node since it's
	 * already updated when we come into this routine, and we need
	 * the old value to rebuild the record (see above).
	 */
	OFS_node->var_value = force_string(OFS_node->var_value);
	new_ofs_len = OFS_node->var_value->stlen;

	if (OFS == NULL)
		emalloc(OFS, char *, new_ofs_len + 1, "set_OFS");
	else if (OFSlen < new_ofs_len)
		erealloc(OFS, char *, new_ofs_len + 1, "set_OFS");

	memcpy(OFS, OFS_node->var_value->stptr, OFS_node->var_value->stlen);
	OFSlen = new_ofs_len;
	OFS[OFSlen] = '\0';
}

/* set_ORS --- update ORS related variables when ORS assigned to */

void
set_ORS()
{
	ORS_node->var_value = force_string(ORS_node->var_value);
	ORS = ORS_node->var_value->stptr;
	ORSlen = ORS_node->var_value->stlen;
}

/* fmt_ok --- is the conversion format a valid one? */

NODE **fmt_list = NULL;
static int fmt_ok(NODE *n);
static int fmt_index(NODE *n);

static int
fmt_ok(NODE *n)
{
	NODE *tmp = force_string(n);
	const char *p = tmp->stptr;

#if ! defined(PRINTF_HAS_F_FORMAT) || PRINTF_HAS_F_FORMAT != 1
	static const char float_formats[] = "efgEG";
#else
	static const char float_formats[] = "efgEFG";
#endif
#if defined(HAVE_LOCALE_H)
	static const char flags[] = " +-#'";
#else
	static const char flags[] = " +-#";
#endif

	// We rely on the caller to zero-terminate n->stptr.

	if (*p++ != '%')
		return 0;
	while (*p && strchr(flags, *p) != NULL)	/* flags */
		p++;
	while (*p && isdigit((unsigned char) *p))	/* width - %*.*g is NOT allowed */
		p++;
	if (*p == '\0' || (*p != '.' && ! isdigit((unsigned char) *p)))
		return 0;
	if (*p == '.')
		p++;
	while (*p && isdigit((unsigned char) *p))	/* precision */
		p++;
	if (*p == '\0' || strchr(float_formats, *p) == NULL)
		return 0;
	if (*++p != '\0')
		return 0;
	return 1;
}

/* fmt_index --- track values of OFMT and CONVFMT to keep semantics correct */

static int
fmt_index(NODE *n)
{
	int ix = 0;
	static int fmt_num = 4;
	static int fmt_hiwater = 0;
	char save;

	if (fmt_list == NULL)
		emalloc(fmt_list, NODE **, fmt_num*sizeof(*fmt_list), "fmt_index");
	n = force_string(n);

	save = n->stptr[n->stlen];
	n->stptr[n->stlen] = '\0';

	while (ix < fmt_hiwater) {
		if (cmp_nodes(fmt_list[ix], n, true) == 0)
			return ix;
		ix++;
	}

	/* not found */
	if (do_lint && ! fmt_ok(n))
		lintwarn(_("bad `%sFMT' specification `%s'"),
			    n == CONVFMT_node->var_value ? "CONV"
			  : n == OFMT_node->var_value ? "O"
			  : "", n->stptr);

	n->stptr[n->stlen] = save;

	if (fmt_hiwater >= fmt_num) {
		fmt_num *= 2;
		erealloc(fmt_list, NODE **, fmt_num * sizeof(*fmt_list), "fmt_index");
	}
	fmt_list[fmt_hiwater] = dupnode(n);
	return fmt_hiwater++;
}

/* set_OFMT --- track OFMT correctly */

void
set_OFMT()
{
	OFMTidx = fmt_index(OFMT_node->var_value);
	OFMT = fmt_list[OFMTidx]->stptr;
}

/* set_CONVFMT --- track CONVFMT correctly */

void
set_CONVFMT()
{
	CONVFMTidx = fmt_index(CONVFMT_node->var_value);
	CONVFMT = fmt_list[CONVFMTidx]->stptr;
}

/* set_LINT --- update LINT as appropriate */

void
set_LINT()
{
#ifndef NO_LINT
	int old_lint = do_lint;
	NODE *n = fixtype(LINT_node->var_value);

	/* start with clean defaults */
	lintfunc = r_warning;
	do_flags &= ~(DO_LINT_ALL|DO_LINT_INVALID);

	if ((n->flags & STRING) != 0) {
		const char *lintval;
		size_t lintlen;

		lintval = n->stptr;
		lintlen = n->stlen;
		if (lintlen > 0) {
			if (lintlen == 7 && strncmp(lintval, "invalid", 7) == 0)
				do_flags |= DO_LINT_INVALID;
			else if (lintlen == 6 && strncmp(lintval, "no-ext", 6) == 0)
				do_flags &= ~DO_LINT_EXTENSIONS;
			else {
				do_flags |= DO_LINT_ALL;
				if (lintlen == 5 && strncmp(lintval, "fatal", 5) == 0)
					lintfunc = r_fatal;
			}
		}
	} else {
		if (! is_zero(n))
			do_flags |= DO_LINT_ALL;
	}

	/* explicitly use warning() here, in case lintfunc == r_fatal */
	if (old_lint != do_lint && old_lint && ! do_lint)
		warning(_("turning off `--lint' due to assignment to `LINT'"));

	/* inform plug-in api of change */
	update_ext_api();
#endif /* ! NO_LINT */
}

/* set_TEXTDOMAIN --- update TEXTDOMAIN variable when TEXTDOMAIN assigned to */

void
set_TEXTDOMAIN()
{
	NODE *tmp;

	tmp = TEXTDOMAIN_node->var_value = force_string(TEXTDOMAIN_node->var_value);
	TEXTDOMAIN = tmp->stptr;
	/*
	 * Note: don't call textdomain(); this value is for
	 * the awk program, not for gawk itself.
	 */
}

/* update_ERRNO_int --- update the value of ERRNO based on argument */

void
update_ERRNO_int(int errcode)
{
	const char *cp;

	update_PROCINFO_num("errno", errcode);
	if (errcode) {
		cp = strerror(errcode);
		cp = gettext(cp);
	} else
		cp = "";
	unref(ERRNO_node->var_value);
	ERRNO_node->var_value = make_string(cp, strlen(cp));
}

/* update_ERRNO_string --- update ERRNO */

void
update_ERRNO_string(const char *string)
{
	update_PROCINFO_num("errno", 0);
	unref(ERRNO_node->var_value);
	size_t len = strlen(string);
#if defined(USE_EBCDIC) && defined(ELIDE_IBM_ERROR_CODE)
	// skip over leading IBM error code
	// N.B. This code is untested
	if (isupper(string[0]) && isupper(string[1])) {
		while (*string && *string != ' ')
			string++;

		while (*string && *string == ' ')
			string++;

		len = strlen(string);
		if (string[len-1] == '.')
			len--;	// remove the final '.'
	}
#endif
	ERRNO_node->var_value = make_string(string, len);
}

/* unset_ERRNO --- eliminate the value of ERRNO */

void
unset_ERRNO(void)
{
	update_PROCINFO_num("errno", 0);
	unref(ERRNO_node->var_value);
	ERRNO_node->var_value = dupnode(Nnull_string);
}

/* update_NR --- update the value of NR */

void
update_NR()
{
#ifdef HAVE_MPFR
	if (is_mpg_number(NR_node->var_value))
		(void) mpg_update_var(NR_node);
	else
#endif
	if (NR_node->var_value->numbr != NR) {
		unref(NR_node->var_value);
		NR_node->var_value = make_number(NR);
	}
}

/* update_NF --- update the value of NF */

void
update_NF()
{
	long l;

	l = get_number_si(NF_node->var_value);
	if (NF == -1 || l != NF) {
		if (NF == -1)
			(void) get_field(UNLIMITED - 1, NULL); /* parse record */
		unref(NF_node->var_value);
		NF_node->var_value = make_number(NF);
	}
}

/* update_FNR --- update the value of FNR */

void
update_FNR()
{
#ifdef HAVE_MPFR
	if (is_mpg_number(FNR_node->var_value))
		(void) mpg_update_var(FNR_node);
	else
#endif
	if (FNR_node->var_value->numbr != FNR) {
		unref(FNR_node->var_value);
		FNR_node->var_value = make_number(FNR);
	}
}


NODE *frame_ptr;        /* current frame */
STACK_ITEM *stack_ptr = NULL;
STACK_ITEM *stack_bottom;
STACK_ITEM *stack_top;
static unsigned long STACK_SIZE = 256;    /* initial size of stack */
int max_args = 0;       /* maximum # of arguments to printf, print, sprintf,
                         * or # of array subscripts, or adjacent strings
                         * to be concatenated.
                         */
NODE **args_array = NULL;

/* grow_stack --- grow the size of runtime stack */

/* N.B. stack_ptr points to the topmost occupied location
 *      on the stack, not the first free location.
 */

STACK_ITEM *
grow_stack()
{
	STACK_SIZE *= 2;
	erealloc(stack_bottom, STACK_ITEM *, STACK_SIZE * sizeof(STACK_ITEM), "grow_stack");
	stack_top = stack_bottom + STACK_SIZE - 1;
	stack_ptr = stack_bottom + STACK_SIZE / 2;
	return stack_ptr;
}

/*
 * r_get_lhs:
 * This returns a POINTER to a node pointer (var's value).
 * used to store the var's new value.
 */

NODE **
r_get_lhs(NODE *n, bool reference)
{
	bool isparam = false;

	if (n->type == Node_param_list) {
		isparam = true;
		n = GET_PARAM(n->param_cnt);
	}

	switch (n->type) {
	case Node_var_array:
		fatal(_("attempt to use array `%s' in a scalar context"),
				array_vname(n));
		return NULL;	// silence compiler warnings
	case Node_array_ref:
		if (n->orig_array->type == Node_var_array)
			fatal(_("attempt to use array `%s' in a scalar context"),
					array_vname(n));
		if (n->orig_array->type != Node_var) {
			n->orig_array->type = Node_var;
			n->orig_array->var_value = dupnode(Nnull_string);
		}
		/* fall through */
	case Node_var_new:
		n->type = Node_var;
		n->var_value = dupnode(Nnull_string);
		break;

	case Node_elem_new:
		efree(n->stptr);
		n->stptr = NULL;
		n->stlen = 0;
		n->type = Node_var;
		n->var_value = dupnode(Nnull_string);
		break;

	case Node_var:
		break;

	default:
		cant_happen("unexpected variable type %s", nodetype2str(n->type));
	}

	if (do_lint && reference && var_uninitialized(n))
		lintwarn((isparam ?
			_("reference to uninitialized argument `%s'") :
			_("reference to uninitialized variable `%s'")),
				n->vname);
	return & n->var_value;
}


/* r_get_field --- get the address of a field node */

NODE **
r_get_field(NODE *n, Func_ptr *assign, bool reference)
{
	long field_num;
	NODE **lhs;

	if (assign)
		*assign = NULL;
	if (do_lint) {
		if ((fixtype(n)->flags & NUMBER) == 0) {
			lintwarn(_("attempt to field reference from non-numeric value"));
			if (n->stlen == 0)
				lintwarn(_("attempt to field reference from null string"));
		}
	}

	(void) force_number(n);
	field_num = get_number_si(n);

	if (field_num < 0)
		fatal(_("attempt to access field %ld"), field_num);

	if (field_num == 0 && field0_valid) {		/* short circuit */
		lhs = &fields_arr[0];
		if (assign)
			*assign = reset_record;
	} else
		lhs = get_field(field_num, assign);
	if (do_lint && reference && ((*lhs)->flags & NULL_FIELD) != 0)
		lintwarn(_("reference to uninitialized field `$%ld'"),
			      field_num);
	return lhs;
}


/*
 * calc_exp_posint --- calculate x^n for positive integral n,
 * using exponentiation by squaring without recursion.
 */

static AWKNUM
calc_exp_posint(AWKNUM x, long n)
{
	AWKNUM mult = 1;

	while (n > 1) {
		if ((n % 2) == 1)
			mult *= x;
		x *= x;
		n /= 2;
	}
	return mult * x;
}

/* calc_exp --- calculate x1^x2 */

AWKNUM
calc_exp(AWKNUM x1, AWKNUM x2)
{
	long lx;

	if ((lx = x2) == x2) {		/* integer exponent */
		if (lx == 0)
			return 1;
		return (lx > 0) ? calc_exp_posint(x1, lx)
				: 1.0 / calc_exp_posint(x1, -lx);
	}
	return (AWKNUM) pow((double) x1, (double) x2);
}


/* setup_frame --- setup new frame for function call */

static INSTRUCTION *
setup_frame(INSTRUCTION *pc)
{
	NODE *r = NULL;
	NODE *m, *f, *fp;
	NODE **sp = NULL;
	int pcount, arg_count, i, j;

	f = pc->func_body;
	pcount = f->param_cnt;
	fp = f->fparms;
	arg_count = (pc + 1)->expr_count;

	if (pcount > 0) {
		ezalloc(sp, NODE **, pcount * sizeof(NODE *), "setup_frame");
	}

	/* check for extra args */
	if (arg_count > pcount) {
		warning(
			_("function `%s' called with more arguments than declared"),
       			f->vname);
		do {
			r = POP();
			if (r->type == Node_val)
				DEREF(r);
		} while (--arg_count > pcount);
	}

	for (i = 0, j = arg_count - 1; i < pcount; i++, j--) {
		getnode(r);
		memset(r, 0, sizeof(NODE));
		sp[i] = r;

		if (i >= arg_count) {
			/* local variable */
			r->type = Node_var_new;
			r->vname = fp[i].param;
			continue;
		}

		m = PEEK(j); /* arguments in reverse order on runtime stack */

		if (m->type == Node_param_list)
			m = GET_PARAM(m->param_cnt);

		/* $0 needs to be passed by value to a function */
		if (m == fields_arr[0]) {
			NODE *copy;

			copy = dupnode(m);	// will be real copy
			DEREF(m);
			m = copy;
		}

		switch (m->type) {
		case Node_var_new:
		case Node_var_array:
		case Node_elem_new:
			r->type = Node_array_ref;
			r->orig_array = r->prev_array = m;
			break;

		case Node_array_ref:
			r->type = Node_array_ref;
			r->orig_array = m->orig_array;
			r->prev_array = m;
			break;

		case Node_var:
			/* Untyped (Node_var_new) variable as param became a
			 * scalar during evaluation of expression for a
			 * subsequent param.
			 */
			r->type = Node_var;
			r->var_value = dupnode(Nnull_string);
			break;

		case Node_val:
			r->type = Node_var;
			r->var_value = m;
			break;

		case Node_func:
		case Node_builtin_func:
		case Node_ext_func:
			r->type = Node_var;
			r->var_value = make_string(m->vname, strlen(m->vname));
			break;

		default:
			cant_happen("unexpected parameter type %s", nodetype2str(m->type));
		}
		r->vname = fp[i].param;
	}

	stack_adj(-arg_count);	/* adjust stack pointer */

	if (pc->opcode == Op_indirect_func_call) {
		r = POP();	/* indirect var */
		DEREF(r);
	}

	frame_ptr->vname = source;	/* save current source */

	if (do_profile || do_debug)
		push_frame(frame_ptr);

	/* save current frame in stack */
	PUSH(frame_ptr);

	/* setup new frame */
	getnode(frame_ptr);
	frame_ptr->type = Node_frame;
	frame_ptr->stack = sp;
	frame_ptr->prev_frame_size = (stack_ptr - stack_bottom); /* size of the previous stack frame */
	frame_ptr->func_node = f;
	frame_ptr->vname = NULL;
	frame_ptr->reti = pc; /* on return execute pc->nexti */

	return f->code_ptr;
}


/* restore_frame --- clean up the stack and update frame */

static INSTRUCTION *
restore_frame(NODE *fp)
{
	NODE *r;
	NODE **sp;
	int n;
	NODE *func;
	INSTRUCTION *ri;

	func = frame_ptr->func_node;
	n = func->param_cnt;
	sp = frame_ptr->stack;

	for (; n > 0; n--) {
		r = *sp++;
		if (r->type == Node_var)     /* local variable */
			DEREF(r->var_value);
		else if (r->type == Node_var_array)     /* local array */
			assoc_clear(r);
		freenode(r);
	}

	if (frame_ptr->stack != NULL)
		efree(frame_ptr->stack);
	ri = frame_ptr->reti;     /* execution in calling frame
	                           * resumes from ri->nexti.
	                           */
	freenode(frame_ptr);
	if (do_profile || do_debug)
		pop_frame();

	/* restore frame */
	frame_ptr = fp;
	/* restore source */
	source = fp->vname;
	fp->vname = NULL;

	return ri->nexti;
}


/* free_arrayfor --- free 'for (var in array)' related data */

static inline void
free_arrayfor(NODE *r)
{
	if (r->for_list != NULL) {
		NODE *n;
		size_t num_elems = r->for_list_size;
		NODE **list = r->for_list;
		while (num_elems > 0) {
			n = list[--num_elems];
			unref(n);
		}
		efree(list);
	}
	freenode(r);
}


/*
 * unwind_stack --- pop items off the run-time stack;
 *	'n' is the # of items left in the stack.
 */

INSTRUCTION *
unwind_stack(long n)
{
	NODE *r;
	INSTRUCTION *cp = NULL;
	STACK_ITEM *sp;

	if (stack_empty())
		return NULL;

	sp = stack_bottom + n;

	if (stack_ptr < sp)
		return NULL;

	while ((r = POP()) != NULL) {
		switch (r->type) {
		case Node_frame:
			cp = restore_frame(r);
			break;
		case Node_arrayfor:
			free_arrayfor(r);
			break;
		case Node_val:
			DEREF(r);
			break;
		case Node_instruction:
			freenode(r);
			break;
		default:
			/*
			 * Check `exiting' and don't produce an error for
			 * cases like:
			 *	func     _fn0() { exit }
			 *	BEGIN { ARRAY[_fn0()] }
			 */
			if (in_main_context() && ! exiting)
				fatal(_("unwind_stack: unexpected type `%s'"),
						nodetype2str(r->type));
			/* else
				* Node_var_array,
				* Node_param_list,
				* Node_var (e.g: trying to use scalar for array)
				* Node_regex/Node_dynregex
				* ?
			 */
			break;
		}

		if (stack_ptr < sp)
			break;
	}
	return cp;
}


/* pop_fcall --- pop off the innermost frame */
#define pop_fcall()	unwind_stack(frame_ptr->prev_frame_size)

/* pop the run-time stack */
#define pop_stack()	(void) unwind_stack(0)


static inline bool
eval_condition(NODE *t)
{
	if (t == node_Boolean[false])
		return false;

	if (t == node_Boolean[true])
		return true;

	return boolval(t);
}

static bool cmp_doubles(const NODE *t1, const NODE *t2, scalar_cmp_t comparison_type);
extern bool mpg_cmp_as_numbers(const NODE *t1, const NODE *t2, scalar_cmp_t comparison_type);

/* cmp_scalars -- compare two nodes on the stack */

static bool
cmp_scalars(scalar_cmp_t comparison_type)
{
	NODE *t1, *t2;
	int di;
	bool ret;

	t2 = POP_SCALAR();
	t1 = TOP();

	t1 = elem_new_to_scalar(t1);
	t2 = elem_new_to_scalar(t2);

	if (t1->type == Node_var_array) {
		DEREF(t2);
		fatal(_("attempt to use array `%s' in a scalar context"), array_vname(t1));
	}

	if ((t1->flags & STRING) != 0 || (t2->flags & STRING) != 0) {
		bool use_strcmp = (comparison_type == SCALAR_EQ || comparison_type == SCALAR_NEQ);
		di = cmp_nodes(t1, t2, use_strcmp);

		switch (comparison_type) {
		case SCALAR_EQ:
			ret = (di == 0);
			break;
		case SCALAR_NEQ:
			ret = (di != 0);
			break;
		case SCALAR_LT:
			ret = (di < 0);
			break;
		case SCALAR_LE:
			ret = (di <= 0);
			break;
		case SCALAR_GT:
			ret = (di > 0);
			break;
		case SCALAR_GE:
			ret = (di >= 0);
			break;
		}
	} else {
		fixtype(t1);
		fixtype(t2);

#ifdef HAVE_MPFR
		if (do_mpfr)
			ret = mpg_cmp_as_numbers(t1, t2, comparison_type);
		else
#endif
			ret = cmp_doubles(t1, t2, comparison_type);
	}

	DEREF(t1);
	DEREF(t2);
	return ret;
}


/* cmp_doubles --- compare two doubles */

static bool
cmp_doubles(const NODE *t1, const NODE *t2, scalar_cmp_t comparison_type)
{
	/*
	 * This routine provides numeric comparisons that should work
	 * the same as in C.  It should NOT be used for sorting.
	 */

	bool t1_nan = isnan(t1->numbr);
	bool t2_nan = isnan(t2->numbr);
	int ret;

	if ((t1_nan || t2_nan) && comparison_type != SCALAR_NEQ)
		return false;

	switch (comparison_type) {
	case SCALAR_EQ:
		ret = (t1->numbr == t2->numbr);
		break;
	case SCALAR_NEQ:
		ret = (t1->numbr != t2->numbr);
		break;
	case SCALAR_LT:
		ret = (t1->numbr < t2->numbr);
		break;
	case SCALAR_LE:
		ret = (t1->numbr <= t2->numbr);
		break;
	case SCALAR_GT:
		ret = (t1->numbr > t2->numbr);
		break;
	case SCALAR_GE:
		ret = (t1->numbr >= t2->numbr);
		break;
	}

	return ret;
}


/* op_assign --- assignment operators excluding = */

static void
op_assign(OPCODE op)
{
	NODE **lhs;
	NODE *t1, *t2;
	AWKNUM x = 0.0, x1, x2;

	lhs = POP_ADDRESS();
	t1 = *lhs;
	x1 = force_number(t1)->numbr;

	t2 = TOP_SCALAR();
	x2 = force_number(t2)->numbr;
	DEREF(t2);

	switch (op) {
	case Op_assign_plus:
		x = x1 + x2;
		break;
	case Op_assign_minus:
		x = x1 - x2;
		break;
	case Op_assign_times:
		x = x1 * x2;
		break;
	case Op_assign_quotient:
		if (x2 == (AWKNUM) 0) {
			decr_sp();
			fatal(_("division by zero attempted in `/='"));
		}
		x = x1 / x2;
		break;
	case Op_assign_mod:
		if (x2 == (AWKNUM) 0) {
			decr_sp();
			fatal(_("division by zero attempted in `%%='"));
		}
#ifdef HAVE_FMOD
		x = fmod(x1, x2);
#else   /* ! HAVE_FMOD */
		(void) modf(x1 / x2, &x);
		x = x1 - x2 * x;
#endif  /* ! HAVE_FMOD */
		break;
	case Op_assign_exp:
		x = calc_exp((double) x1, (double) x2);
		break;
	default:
		break;
	}

	if (t1->valref == 1 && t1->flags == (MALLOC|NUMCUR|NUMBER)) {
		/* optimization */
		t1->numbr = x;
	} else {
		unref(t1);
		t1 = *lhs = make_number(x);
	}

	UPREF(t1);
	REPLACE(t1);
}

/* PUSH_CODE --- push a code onto the runtime stack */

void
PUSH_CODE(INSTRUCTION *cp)
{
	NODE *r;
	getnode(r);
	r->type = Node_instruction;
	r->code_ptr = cp;
	PUSH(r);
}

/* POP_CODE --- pop a code off the runtime stack */

INSTRUCTION *
POP_CODE()
{
	NODE *r;
	INSTRUCTION *cp;
	r = POP();
	cp = r->code_ptr;
	freenode(r);
	return cp;
}


/*
 * Implementation of BEGINFILE and ENDFILE requires saving an execution
 * state and the ability to return to that state. The state is
 * defined by the instruction triggering the BEGINFILE/ENDFILE rule, the
 * run-time stack, the rule and the source file. The source line is available in
 * the instruction and hence is not considered a part of the execution state.
 */


typedef struct exec_state {
	struct exec_state *next;

	INSTRUCTION *cptr;  /* either getline (Op_K_getline) or the
	                     * implicit "open-file, read-record" loop (Op_newfile).
	                     */

	int rule;           /* rule for the INSTRUCTION */

	long stack_size;    /* For this particular usage, it is sufficient to save
	                     * only the size of the call stack. We do not
	                     * store the actual stack pointer to avoid problems
	                     * in case the stack gets realloc-ed.
	                     */

	const char *source; /* source file for the INSTRUCTION */
} EXEC_STATE;

static EXEC_STATE exec_state_stack;

/* push_exec_state --- save an execution state on stack */

static void
push_exec_state(INSTRUCTION *cp, int rule, char *src, STACK_ITEM *sp)
{
	EXEC_STATE *es;

	emalloc(es, EXEC_STATE *, sizeof(EXEC_STATE), "push_exec_state");
	es->rule = rule;
	es->cptr = cp;
	es->stack_size = (sp - stack_bottom) + 1;
	es->source = src;
	es->next = exec_state_stack.next;
	exec_state_stack.next = es;
}


/* pop_exec_state --- pop one execution state off the stack */

static INSTRUCTION *
pop_exec_state(int *rule, char **src, long *sz)
{
	INSTRUCTION *cp;
	EXEC_STATE *es;

	es = exec_state_stack.next;
	if (es == NULL)
		return NULL;
	cp = es->cptr;
	if (rule != NULL)
		*rule = es->rule;
	if (src != NULL)
		*src = (char *) es->source;
	if (sz != NULL)
		*sz = es->stack_size;
	exec_state_stack.next = es->next;
	efree(es);
	return cp;
}


/* register_exec_hook --- add exec hooks in the interpreter. */

int
register_exec_hook(Func_pre_exec preh, Func_post_exec posth)
{
	int pos = 0;

	/*
	 * multiple post-exec hooks aren't supported. post-exec hook is mainly
	 * for use by the debugger.
	 */

	if (! preh || (post_execute && posth))
		return false;

	if (num_exec_hook == MAX_EXEC_HOOKS)
		return false;

	/*
	 * Add to the beginning of the array but do not displace the
	 * debugger hook if it exists.
	 */
	if (num_exec_hook > 0) {
		pos = !! do_debug;
		if (num_exec_hook > pos)
			memmove(pre_execute + pos + 1, pre_execute + pos,
					(num_exec_hook - pos) * sizeof (preh));
	}
	pre_execute[pos] = preh;
	num_exec_hook++;

	if (posth)
		post_execute = posth;

	return true;
}


/* interpreter routine when not debugging */
#include "interpret.h"

/* interpreter routine with exec hook(s). Used when debugging and/or with MPFR. */
#define r_interpret h_interpret
#define EXEC_HOOK 1
#include "interpret.h"
#undef EXEC_HOOK
#undef r_interpret


void
init_interpret()
{
	long newval;

	if ((newval = getenv_long("GAWK_STACKSIZE")) > 0)
		STACK_SIZE = newval;

	emalloc(stack_bottom, STACK_ITEM *, STACK_SIZE * sizeof(STACK_ITEM), "grow_stack");
	stack_ptr = stack_bottom - 1;
	stack_top = stack_bottom + STACK_SIZE - 1;

	/* initialize frame pointer */
	getnode(frame_ptr);
	frame_ptr->type = Node_frame;
	frame_ptr->stack = NULL;
	frame_ptr->func_node = NULL;	/* in main */
	frame_ptr->vname = NULL;

	/* initialize true and false nodes */
	node_Boolean[false] = make_number(0.0);
	node_Boolean[true] = make_number(1.0);
	if (! is_mpg_number(node_Boolean[false])) {
		node_Boolean[false]->flags |= NUMINT;
		node_Boolean[true]->flags |= NUMINT;
	}

	/*
	 * Select the interpreter routine. The version without
	 * any exec hook support (r_interpret) is faster by about
	 * 5%, or more depending on the opcodes.
	 */

	if (num_exec_hook > 0)
		interpret = h_interpret;
	else
		interpret = r_interpret;
}

/* elem_new_to_scalar --- convert Node_elem_new to untyped scalar */

NODE *
elem_new_to_scalar(NODE *n)
{
	if (n->type != Node_elem_new)
		return n;

	if (n->valref > 1) {
		unref(n);
		return dupnode(Nnull_string);
	}

	n->type = Node_val;

	return n;
}

/* fix_nan_sign --- fix NaN sign on RiscV */

// See the thread starting at
// https://lists.gnu.org/archive/html/bug-gawk/2022-09/msg00005.html
// for why we need this function.

static double
fix_nan_sign(double left, double right, double result)
{
	if (isnan(left) && signbit(left))
		return copysign(result, -1.0);
	else if (isnan(right) && signbit(right))
		return copysign(result, -1.0);
	else
		return result;
}
