/*
 * debug.c - gawk debugger
 */

/*
 * Copyright (C) 2004, 2010-2013, 2016-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 "cmd.h"

#ifndef O_RDONLY
#include <fcntl.h>	/* open() */
#endif

#ifdef __MINGW32__
#define execvp(p,a) w32_execvp(p,a)
int w32_execvp(const char *, char **);
#endif

extern bool exiting;
extern SRCFILE *srcfiles;
extern INSTRUCTION *rule_list;
extern INSTRUCTION *code_block;
extern NODE **fcall_list;
extern long fcall_count;
extern FILE *output_fp;
extern IOBUF *curfile;
extern const char *command_file;
extern const char *get_spec_varname(Func_ptr fptr);
extern int zzparse(void);
#define read_command()		(void) zzparse()

extern const char *redir2str(int redirtype);

static char *linebuf = NULL;	/* used to print a single line of source */
static size_t linebuf_len;

FILE *out_fp;
const char *dbg_prompt;
const char *commands_prompt = "> ";	/* breakpoint or watchpoint commands list */
const char *eval_prompt = "@> ";	/* awk statement(s) */

bool input_from_tty = false;
int input_fd;

static SRCFILE *cur_srcfile;
static long cur_frame = 0;
static INSTRUCTION *cur_pc;
static int cur_rule = 0;

static bool prog_running = false;

struct condition {
	INSTRUCTION *code;
	AWK_CONTEXT *ctxt;
	char *expr;
};

struct commands_item {
	struct commands_item *next;
	struct commands_item *prev;
	int cmd;
	char *cmd_string;
	CMDARG *arg;
};

/* breakpoint structure */
typedef struct break_point {
	struct break_point *next;
	struct break_point *prev;
	int number;

	long ignore_count;
	long hit_count;
	char *src;
	INSTRUCTION *bpi;	/* Op_breakpoint */

	struct commands_item commands;  /* list of commands to run */
	bool silent;

	struct condition cndn;

	short flags;
#define BP_ENABLE       1
#define BP_ENABLE_ONCE  2		/* enable once */
#define BP_TEMP         4
#define BP_IGNORE       8

} BREAKPOINT;

static BREAKPOINT breakpoints = { &breakpoints, &breakpoints, 0 };

#ifdef HAVE_LIBREADLINE
/* do_save -- save command */
static int sess_history_base = 0;
#endif

#ifndef HAVE_HISTORY_LIST
#define HIST_ENTRY void
#define history_list()	NULL
#endif


/* 'list' command */
static int last_printed_line = 0;
static int last_print_count;	/* # of lines printed */

/* watch or display item */
struct list_item {
	struct list_item *next;
	struct list_item *prev;
	int number;     /* item number */

	NODE *symbol;   /* variable or function param */
	NODE **subs;    /* subscripts */
	int num_subs;	/* subscript(dimension) count */
	char *sname;	/* symbol or param name */

	long fcall_count;

	struct commands_item commands;
	int silent;
	struct condition cndn;

	/* This is for the value of the watched item */
	union {
		NODE *n;
		long l;
	} value[2];
#define cur_value value[0].n
#define cur_size  value[0].l
#define old_value value[1].n
#define old_size  value[1].l

	int flags;
#define PARAM           1
#define SUBSCRIPT       2
#define FIELD_NUM       4
#define OLD_IS_ARRAY    8    /* old item is array */
#define CUR_IS_ARRAY    16   /* current item is array */
};

#define IS_PARAM(d)	(((d)->flags & PARAM) != 0)
#define IS_SUBSCRIPT(d)	(((d)->flags & SUBSCRIPT) != 0)
#define IS_FIELD(d)	(((d)->flags & FIELD_NUM) != 0)
#define WATCHING_ARRAY(d)	(((d)->flags & CUR_IS_ARRAY) != 0)

static struct list_item display_list = { &display_list, &display_list, 0 };
static struct list_item watch_list = { &watch_list, &watch_list, 0 };


/* Structure to maintain data for processing debugger commands */

static struct {
	long fcall_count;    /* 'finish', 'until', 'next', 'step', 'nexti' commands */
	int sourceline;      /* source line number last
	                      * time we stopped execution,
	                      * used by next, until and step commands
	                      */
	char *source;        /* next, until and step */

	INSTRUCTION *pc;     /* 'until' and 'return' commands */
	int repeat_count;    /* 'step', 'next', 'stepi', 'nexti' commands */
	bool print_frame;    /* print frame info,  'finish' and 'until' */
	bool print_ret;      /* print returned value, 'finish' */
	int break_point;     /* non-zero (breakpoint number) if stopped at break point */
	int watch_point;     /* non-zero (watchpoint number) if stopped at watch point */

	int (*check_func)(INSTRUCTION **);      /* function to decide when to suspend
	                                         * awk interpreter and return control
	                                         * to debugger command interpreter.
	                                         */

	enum argtype command;		 /* command type */
} stop;


/* restart related stuff */
extern char **d_argv;	/* copy of argv array */
static bool need_restart = false;
enum { BREAK=1, WATCH, DISPLAY, HISTORY, OPTION };
static const char *const env_variable[] = {
	"",
	"DGAWK_BREAK",
	"DGAWK_WATCH",
	"DGAWK_DISPLAY",
	"DGAWK_HISTORY",
	"DGAWK_OPTION",
};
static void serialize_list(int type);
static void unserialize_list(int type);
static const char *commands_string = NULL;
static int commands_string_len = 0;
static char line_sep;
#define FSEP	(char)'\037'
#define RSEP	(char)'\036'
#define CSEP	(char)'\035'


/* debugger option */
struct dbg_option {
	const char *name;
	int *num_val;
	const char **str_val;
	void (*assign)(const char *);
	const char *help_txt;
};

#define DEFAULT_HISTFILE	"./.gawk_history"
#define DEFAULT_OPTFILE		"./.gawkrc"
#define DEFAULT_PROMPT		"gawk> "
#define DEFAULT_LISTSIZE	15
#define DEFAULT_HISTSIZE	100

static void set_gawk_output(const char *file);
static void set_prompt(const char *value);
static void set_listsize(const char *value);
static void set_trace(const char *value);
static void set_save_history(const char *value);
static void set_save_options(const char *value);
static void set_history_size(const char *value);
static const char *options_file = DEFAULT_OPTFILE;
#ifdef HAVE_LIBREADLINE
static const char *history_file = DEFAULT_HISTFILE;
#endif

/* debugger option related variables */

static const char *output_file = "/dev/stdout";  /* gawk output redirection */
const char *dgawk_prompt = NULL;                 /* initialized in interpret */
static int list_size = DEFAULT_LISTSIZE;   /* # of lines that 'list' prints */
static int do_trace = false;
static int do_save_history = true;
static int do_save_options = true;
static int history_size = DEFAULT_HISTSIZE;  /* max # of lines in history file */

static const struct dbg_option option_list[] = {
{"history_size", &history_size, NULL, &set_history_size,
	gettext_noop("set or show the number of lines to keep in history file") },
{"listsize", &list_size, NULL, &set_listsize,
	gettext_noop("set or show the list command window size") },
{"outfile", NULL, &output_file, &set_gawk_output,
	gettext_noop("set or show gawk output file") },
{"prompt", NULL, &dgawk_prompt, &set_prompt,
	gettext_noop("set or show debugger prompt"), },
{"save_history", &do_save_history, NULL, &set_save_history,
	gettext_noop("(un)set or show saving of command history (value=on|off)") },
{"save_options", &do_save_options, NULL, &set_save_options,
	gettext_noop("(un)set or show saving of options (value=on|off)") },
{"trace", &do_trace, NULL, &set_trace,
	gettext_noop("(un)set or show instruction tracing (value=on|off)") },
{0, NULL, NULL, NULL, 0},
};

static void save_options(const char *file);


/* pager */
jmp_buf pager_quit_tag;
int pager_quit_tag_valid = 0;
static int screen_width = INT_MAX;	/* no of columns */
static int screen_height = INT_MAX;	/* no of rows */
static int pager_lines_printed = 0;	/* no of lines printed so far */

static void restart(bool run) ATTRIBUTE_NORETURN;
static void close_all(void);
static int open_readfd(const char *file);
static int find_lines(SRCFILE *s);
static SRCFILE *source_find(char *src);
static int print_lines(char *src, int start_line, int nlines);
static void print_symbol(NODE *r, bool isparam);
static NODE *find_frame(long num);
static NODE *find_param(const char *name, long num, char **pname);
static NODE *find_symbol(const char *name, char **pname);
static NODE *find_array(const char *name);
static void print_field(long field_num);
static int print_function(INSTRUCTION *pc, void *);
static void print_frame(NODE *func, char *src, int srcline);
static void print_numbered_frame(long num);
static void print_cur_frame_and_sourceline(void);
static INSTRUCTION *find_rule(char *src, long lineno);
static INSTRUCTION *mk_breakpoint(char *src, int srcline);
static int execute_commands(struct commands_item *commands);
static void delete_commands_item(struct commands_item *c);
static NODE *execute_code(volatile INSTRUCTION *code);
static int pre_execute_code(INSTRUCTION **pi);
static int parse_condition(int type, int num, char *expr);
static BREAKPOINT *add_breakpoint(INSTRUCTION *prevp, INSTRUCTION *ip, char *src, bool silent);
static BREAKPOINT *set_breakpoint_next(INSTRUCTION *rp, INSTRUCTION *ip);
static BREAKPOINT *set_breakpoint_at(INSTRUCTION *rp, int lineno, bool silent);
static void delete_breakpoint(BREAKPOINT *b);
static BREAKPOINT *find_breakpoint(long num);
static void display(struct list_item *d);
static struct list_item *find_item(struct list_item *list, long num);
static struct list_item *add_item(struct list_item *list, int type, NODE *symbol, char *pname);
static void delete_item(struct list_item *d);
static int breakpoint_triggered(BREAKPOINT *b);
static int watchpoint_triggered(struct list_item *w);
static void print_instruction(INSTRUCTION *pc, Func_print print_func, FILE *fp, int in_dump);
static void print_ns_list(INSTRUCTION *pc, Func_print print_func, FILE *fp, int in_dump);
static int print_code(INSTRUCTION *pc, void *x);
static void next_command();
static void debug_post_execute(INSTRUCTION *pc);
static int debug_pre_execute(INSTRUCTION **pi);
static char *g_readline(const char *prompt);
static int prompt_yes_no(const char *, char , int , FILE *);
static struct pf_data {
	Func_print print_func;
	bool defn;
	FILE *fp;
} pf_data;

char * (*read_a_line)(const char *) = 0;	/* reads a line of input */

struct command_source
{
	int fd;
	int is_tty;
	char * (*read_func)(const char *);
	int (*close_func)(int);
	int eof_status;		/* see push_cmd_src */
	int cmd;		/* D_source or 0 */
	char *str;		/* sourced file */
	struct command_source *next;
};

static struct command_source *cmd_src = NULL;

#define PUSH_BINDING(stack, tag, val)	\
if (val++) \
	memcpy((char *) (stack), (const char *) tag, sizeof(jmp_buf))
#define POP_BINDING(stack, tag, val)	\
if (--val) \
	memcpy((char *) tag, (const char *) (stack), sizeof(jmp_buf))


#define CHECK_PROG_RUNNING() \
	do { \
		if (! prog_running) { \
			d_error(_("program not running")); \
			return false; \
		} \
	} while (false)

// On z/OS, one needs to use %#p to get the leading 0x in the output.
// Having that makes it consistent with Linux and makes the use of
// helper scripts easier.
#ifdef USE_EBCDIC
#define PTRFMT	"%#p"
#else
#define PTRFMT	"%p"
#endif


/* g_readline --  read a line of text; the interface is like 'readline' but
 *		without	any command-line editing; used when not compiled with
 *		readline support and/or input is not from terminal (prompt set to NULL).
 */

static char *
g_readline(const char *prompt)
{
	char *line;
	size_t line_size = 100;
	static char buf[2];
	char *p, *end;
	int n;

	if (input_from_tty && prompt && *prompt)
		fprintf(out_fp, "%s", prompt);

	emalloc(line, char *, line_size + 1, "g_readline");
	p = line;
	end = line + line_size;
	while ((n = read(input_fd, buf, 1)) > 0) {
		if (buf[0] == '\n') {
			if (p > line && p[-1] == '\r')
				p--;
			break;
		}
		if (p == end) {
			erealloc(line, char *, 2 * line_size + 1, "g_readline");
			p = line + line_size;
			line_size *= 2;
			end = line + line_size;
		}
		*p++ = buf[0];
	}
	if (n == -1 || (n == 0 && p == line)) {
		efree(line);
		return NULL;
	}
	*p = '\0';
	return line;
}


/* d_error --- print an error message */

void
d_error(const char *mesg, ...)
{
	va_list args;
	va_start(args, mesg);
	fprintf(out_fp, _("error: "));
	vfprintf(out_fp, mesg, args);
	fprintf(out_fp, "\n");
	va_end(args);
}

/* find_lines --- find the positions of the lines in the source file. */

static int
find_lines(SRCFILE *s)
{
	char *buf, *p, *end;
	int n;
	int ofs = 0;
	int *pos;
	int pos_size;
	int maxlen = 0;
	int numlines = 0;
	char lastchar = '\0';

	emalloc(buf, char *, s->bufsize, "find_lines");
	pos_size = s->srclines;
	emalloc(s->line_offset, int *, (pos_size + 2) * sizeof(int), "find_lines");
	pos = s->line_offset;
	pos[0] = 0;

	while ((n = read(s->fd, buf, s->bufsize)) > 0) {
		end = buf + n;
		lastchar = buf[n - 1];
		p = buf;
		while (p < end) {
			if (*p++ == '\n') {
				if (++numlines > pos_size) {
					erealloc(s->line_offset, int *, (2 * pos_size + 2) * sizeof(int), "find_lines");
					pos = s->line_offset + pos_size;
					pos_size *= 2;
				}
				*++pos = ofs + (p - buf);
				if ((pos[0] - pos[-1]) > maxlen)
					maxlen = pos[0] - pos[-1];	/* length including NEWLINE */
			}
		}
		ofs += n;
	}
	efree(buf);

	if (n == -1) {
		d_error(_("cannot read source file `%s': %s"),
					s->src, strerror(errno));
		return -1;
	}
	if (ofs <= 0) {
		fprintf(out_fp, _("source file `%s' is empty.\n"), s->src);
		return -1;
	}

	if (lastchar != '\n') {
		/* fake a NEWLINE at end */
		*++pos = ofs + 1;
		numlines++;
		if ((pos[0] - pos[-1]) > maxlen)
			maxlen = pos[0] - pos[-1];
	}
	s->maxlen = maxlen;
	s->srclines = numlines;
	return 0;
}

/* source_find --- return the SRCFILE struct for the source 'src' */

static SRCFILE *
source_find(char *src)
{
	SRCFILE *s;
	struct stat sbuf;
	char *path;
	int errno_val = 0;

	if (src == NULL || *src == '\0') {
		d_error(_("no current source file"));
		return NULL;
	}

	if (cur_srcfile->src == src)  /* strcmp(cur_srcfile->src, src) == 0 */
		return cur_srcfile;

	for (s = srcfiles->next; s != srcfiles; s = s->next) {
		if ((s->stype == SRC_FILE || s->stype == SRC_INC)
				&& strcmp(s->src, src) == 0)
			return s;
	}

	path = find_source(src, & sbuf, & errno_val, false);
	if (path != NULL) {
		for (s = srcfiles->next; s != srcfiles; s = s->next) {
			if ((s->stype == SRC_FILE || s->stype == SRC_INC)
			    		&& files_are_same(path, s)) {
				efree(path);
				return s;
			}
		}
		efree(path);
	}

	d_error(_("cannot find source file named `%s': %s"), src, strerror(errno_val));
	return NULL;
}

/* print_lines --- print source lines, and update 'cur_srcfile' */

static int
print_lines(char *src, int start_line, int nlines)
{
	SRCFILE *s;
	int *pos;
	int i;
	struct stat sbuf;

	s = source_find(src);
	if (s == NULL)
		return -1;
	if (s->fd <= INVALID_HANDLE && (s->fd = srcopen(s)) <= INVALID_HANDLE) {
		d_error(_("cannot open source file `%s' for reading: %s"),
				src, strerror(errno));
		return -1;
	}

	if (fstat(s->fd, &sbuf) == 0 && s->mtime < sbuf.st_mtime) {
		fprintf(out_fp, _("warning: source file `%s' modified since program compilation.\n"),
				src);
		efree(s->line_offset);
		s->line_offset = NULL;
		s->mtime = sbuf.st_mtime;

		/* reopen source file */
		close(s->fd);
		s->fd = INVALID_HANDLE;
		if ((s->fd = srcopen(s)) <= INVALID_HANDLE) {
			d_error(_("cannot open source file `%s' for reading: %s"),
					src, strerror(errno));
			return -1;
		}
	}

	/* set binary mode so that byte offset calculations will be right */
	os_setbinmode(s->fd, O_BINARY);

	if (s->line_offset == NULL && find_lines(s) != 0)
		return -1;
  	if (start_line < 1 || start_line > s->srclines) {
		d_error(_("line number %d out of range; `%s' has %d lines"),
					start_line, src, s->srclines);
		return -1;
	}

	assert(nlines > 0);
	if ((start_line + nlines - 1) > s->srclines)
		nlines = s->srclines - start_line + 1;

	pos = s->line_offset;
	if (lseek(s->fd, (off_t) pos[start_line - 1], SEEK_SET) < 0) {
		d_error("%s: %s", src, strerror(errno));
		return -1;
	}

	if (linebuf == NULL) {
		emalloc(linebuf, char *, s->maxlen + 20, "print_lines"); /* 19 for line # */
		linebuf_len = s->maxlen;
	} else if (linebuf_len < s->maxlen) {
		erealloc(linebuf, char *, s->maxlen + 20, "print_lines");
		linebuf_len = s->maxlen;
	}

	for (i = start_line; i < start_line + nlines; i++) {
		int supposed_len, len;
		char *p;

		sprintf(linebuf, "%-8d", i);

		/* mark the line about to be executed with =>; nlines > 1
	 	 * condition makes sure that we are in list command
		 */
		if (nlines > 1) {
			BREAKPOINT *b;
			bool has_bpt = false;
			for (b = breakpoints.prev; b != &breakpoints; b = b->prev) {
				if (src == b->src && i == b->bpi->source_line) {
					has_bpt = true;
					break;
				}
			}
			if (prog_running && src == source && i == sourceline) {
				if (has_bpt)
					sprintf(linebuf, "%-4d:b=>", i);
				else
					sprintf(linebuf, "%-4d  =>", i);
			} else if (has_bpt)
				sprintf(linebuf, "%-4d:b  ", i);
		}

		p = linebuf + strlen(linebuf);
		supposed_len = pos[i] - pos[i - 1];
		len = read(s->fd, p, supposed_len);
		switch (len) {
		case -1:
			d_error(_("cannot read source file `%s': %s"),
						src, strerror(errno));
			return -1;

		case 0:
			d_error(_("unexpected eof while reading file `%s', line %d"),
						src, i);
			return -1;

		default:
			if (i == s->srclines && p[len - 1] != '\n')
				p[len++] = '\n';
#if 0
			if (len != supposed_len || p[len - 1] != '\n') {
				d_error(_("source file `%s' modified since start of program execution"),
						src);
				return -1;
			}
#endif
			len += (p - linebuf);
			if (fwrite(linebuf, sizeof(char), len, out_fp) != len)
				return -1;
		}
	}

	if (cur_srcfile != s) {
		if (cur_srcfile->fd != INVALID_HANDLE) {
			close(cur_srcfile->fd);
			cur_srcfile->fd = INVALID_HANDLE;
		}
		cur_srcfile = s;
	}
	return (i - 1);		/* no of lines printed */
}

/* do_list --- list command */

int
do_list(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	long line_first, line_last;
	long count = list_size;
	INSTRUCTION *rp;
	char *src = cur_srcfile->src;

	line_first = last_printed_line + 1;		/* default or no arg */
	if (arg == NULL)	/* list or list + */
		goto list;

	switch (arg->type) {
	case D_int:		/* list n or list - */
		if (arg->a_int < 0) {		/* list - */
			line_first = last_printed_line - last_print_count - list_size + 1;
			if (line_first < 1) {
				if (last_printed_line != last_print_count)
					line_first = 1;
				else
					return false;
			}
		} else {
line:
			line_first = arg->a_int - list_size / 2;
			if (line_first < 1)
				line_first = 1;
		}
		break;

	case D_range:	/* list m-n */
range:
		line_first = arg->a_int;
		arg = arg->next;
		assert(arg != NULL);
		assert(arg->type == D_int);
		count = arg->a_int - line_first + 1;
		break;

	case D_string:
		src = arg->a_string;
		if (arg->next != NULL) {
			arg = arg->next;
			if (arg->type == D_int)	/* list file:n */
				goto line;
			else if (arg->type == D_range)	/* list file:m-n */
				goto range;
			else if (arg->type == D_func)	/* list file:function */
				goto func;
			else
				line_first = 1;
		} else
			line_first = 1;
		break;

	case D_func:	/* list function */
func:
		rp = arg->a_node->code_ptr;
		src = rp->source_file;
		line_first = rp->source_line - list_size / 2;
		if (line_first < 1)
			line_first = 1;
		break;

	default:
		break;
 	}

list:
	line_last = print_lines(src, line_first, count);
	if (line_last != -1) {
		last_printed_line = line_last;
		last_print_count = line_last - line_first + 1;
	}
	return false;
}

/* do_info --- info command */

int
do_info(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	NODE **table;

	if (arg == NULL || arg->type != D_argument)
		return false;

	switch (arg->a_argument) {
	case A_SOURCE:
		fprintf(out_fp, _("Current source file: %s\n"), cur_srcfile->src);
		fprintf(out_fp, _("Number of lines: %d\n"), cur_srcfile->srclines);
		break;

	case A_SOURCES:
	{
		SRCFILE *s;
		for (s = srcfiles->next; s != srcfiles; s = s->next) {
			fprintf(out_fp, _("Source file (lines): %s (%d)\n"),
					(s->stype == SRC_FILE || s->stype == SRC_INC) ? s->src
			 		                                      : "cmd. line",
					s->srclines);
		}
	}
		break;

	case A_BREAK:
		initialize_pager(out_fp);
		if (setjmp(pager_quit_tag) == 0) {
			BREAKPOINT *b;
			struct commands_item *c;

			gprintf(out_fp, _("Number  Disp  Enabled  Location\n\n"));
			for (b = breakpoints.prev; b != &breakpoints; b = b->prev) {
				const char *disp = "keep";
				if ((b->flags & BP_ENABLE_ONCE) != 0)
					disp = "dis";
				else if ((b->flags & BP_TEMP) != 0)
					disp = "del";
				gprintf(out_fp, "%-6d  %-4.4s  %-7.7s  file %s, line #%d\n",
						b->number, disp, (b->flags & BP_ENABLE) != 0 ? "yes" : "no",
					 	b->src,	b->bpi->source_line);
				if (b->hit_count > 0)
					gprintf(out_fp, _("\tnumber of hits = %ld\n"), b->hit_count);
				if ((b->flags & BP_IGNORE) != 0)
					gprintf(out_fp, _("\tignore next %ld hit(s)\n"), b->ignore_count);
				if (b->cndn.code != NULL)
					gprintf(out_fp, _("\tstop condition: %s\n"), b->cndn.expr);
				if (b->commands.next != &b->commands)
					gprintf(out_fp, _("\tcommands:\n"));
				for (c = b->commands.next; c != &b->commands; c = c->next) {
					gprintf(out_fp, "\t%s\n", c->cmd_string);
					if (c->cmd == D_eval) {
						char *start, *end;
						CMDARG *a = c->arg;
						start = strchr(a->a_string, '{');
						end = strrchr(a->a_string, '}');
						if (start == NULL || end == NULL)
							continue;
						start++;
						*end = '\0';
						gprintf(out_fp, "%s", start);	/* FIXME: translate ? */
						*end = '}';
					}
				}
			}
		}
		break;

	case A_FRAME:
		CHECK_PROG_RUNNING();
		fprintf(out_fp, _("Current frame: "));
		print_numbered_frame(cur_frame);
		if (cur_frame < fcall_count) {
			fprintf(out_fp, _("Called by frame: "));
			print_numbered_frame(cur_frame + 1);
		}
		if (cur_frame > 0) {
			fprintf(out_fp, _("Caller of frame: "));
			print_numbered_frame(cur_frame - 1);
		}
		break;

	case A_ARGS:
	case A_LOCALS:
	{
		NODE *f, *func;
		INSTRUCTION *pc;
		int arg_count, pcount;
		int i, from, to;

		CHECK_PROG_RUNNING();
		f = find_frame(cur_frame);
		func = f->func_node;
		if (func == NULL) {
			/* print ARGV ? */
			fprintf(out_fp, _("None in main().\n"));
			return false;
		}

		pcount = func->param_cnt;              /* # of defined params */

		pc = (INSTRUCTION *) f->reti;          /* Op_func_call instruction */
		arg_count = (pc + 1)->expr_count;      /* # of arguments supplied */

		if (arg_count > pcount)                /* extra args */
			arg_count = pcount;
		if (arg->a_argument == A_ARGS) {
			from = 0;
			to = arg_count - 1;
		} else {
			from = arg_count;
			to = pcount - 1;
		}

		for (i = from; i <= to; i++) {
			NODE *r;
			r = f->stack[i];
			if (r->type == Node_array_ref)
				r = r->orig_array;
			fprintf(out_fp, "%s = ", func->fparms[i].param);
			print_symbol(r, true);
		}
		if (to < from)
			fprintf(out_fp, "%s",
				arg->a_argument == A_ARGS ?
					_("No arguments.\n") :
					_("No locals.\n"));
	}
		break;

	case A_VARIABLES:
		table = variable_list();
		initialize_pager(out_fp);
		if (setjmp(pager_quit_tag) == 0) {
			gprintf(out_fp, _("All defined variables:\n\n"));
			print_vars(table, gprintf, out_fp);
		}
		efree(table);
		break;

	case A_FUNCTIONS:
		table = function_list(true);
		initialize_pager(out_fp);
		if (setjmp(pager_quit_tag) == 0) {
			gprintf(out_fp, _("All defined functions:\n\n"));
			pf_data.print_func = gprintf;
			pf_data.fp = out_fp;
			pf_data.defn = true;
			(void) foreach_func(table,
			            (int (*)(INSTRUCTION *, void *)) print_function,
			            &pf_data);
		}
		efree(table);
		break;

	case A_DISPLAY:
	case A_WATCH:
		initialize_pager(out_fp);
		if (setjmp(pager_quit_tag) == 0) {
			struct list_item *d, *list;

			if (arg->a_argument == A_DISPLAY) {
				list = &display_list;
				gprintf(out_fp, _("Auto-display variables:\n\n"));
			} else {
				list = &watch_list;
				gprintf(out_fp, _("Watch variables:\n\n"));
			}
			for (d = list->prev; d != list; d = d->prev) {
				int i;
				struct commands_item *c;
				NODE *symbol = d->symbol;

				if (IS_SUBSCRIPT(d)) {
					gprintf(out_fp, "%d:\t%s",  d->number, d->sname);
					for (i = 0; i < d->num_subs; i++) {
						NODE *sub;
						sub = d->subs[i];
						gprintf(out_fp, "[\"%.*s\"]", (int) sub->stlen, sub->stptr);
					}
					gprintf(out_fp, "\n");
				} else if (IS_FIELD(d))
					gprintf(out_fp, "%d:\t$%ld\n", d->number, get_number_si(symbol));
				else
					gprintf(out_fp, "%d:\t%s\n", d->number, d->sname);
				if (d->cndn.code != NULL)
					gprintf(out_fp, _("\tstop condition: %s\n"), d->cndn.expr);
				if (d->commands.next != &d->commands)
					gprintf(out_fp, _("\tcommands:\n"));
				for (c = d->commands.next; c != &d->commands; c = c->next) {
					gprintf(out_fp, "\t%s\n", c->cmd_string);
					if (c->cmd == D_eval) {
						char *start, *end;
						CMDARG *a = c->arg;
						start = strchr(a->a_string, '{');
						end = strrchr(a->a_string, '}');
						if (start == NULL || end == NULL)
							continue;
						start++;
						*end = '\0';
						gprintf(out_fp, "%s", start);	/* FIXME: translate ? */
						*end = '}';
					}
				}

			}
		}
		break;

	default:
		break;
	}

	return false;
}

/* print_symbol --- print a symbol table entry */

static void
print_symbol(NODE *r, bool isparam)
{
	switch (r->type) {
	case Node_var_new:
		fprintf(out_fp, "untyped variable\n");
		break;
	case Node_elem_new:
		fprintf(out_fp, "untyped element\n");
		break;
	case Node_var:
		if (! isparam && r->var_update)
			r->var_update();
		valinfo(r->var_value, fprintf, out_fp);
		break;
	case Node_var_array:
		fprintf(out_fp, "array, %ld elements\n", (long) assoc_length(r));
		break;
	case Node_func:
		fprintf(out_fp, "`function'\n");
		break;
	default:
		break;
	}
}

/* find_frame --- find frame given a frame number */

static NODE *
find_frame(long num)
{
	assert(num >= 0);
	if (num == 0)
		return frame_ptr;

	assert(prog_running == true);
	assert(num <= fcall_count);
	assert(fcall_list[num] != NULL);
	return fcall_list[num];
}

/* find_param --- find a function parameter in a given frame number */

static NODE *
find_param(const char *name, long num, char **pname)
{
	NODE *r = NULL;
	NODE *f;
	char *fparam;

	if (pname)
		*pname = NULL;

	if (num < 0 || num > fcall_count || name == NULL)
		return NULL;
	f = find_frame(num);
	if (f->func_node != NULL) {		/* in function */
		NODE *func;
		int i, pcount;

		func = f->func_node;
		pcount = func->param_cnt;
		for (i = 0; i < pcount; i++) {
			fparam = func->fparms[i].param;
			if (strcmp(name, fparam) == 0) {
				r = f->stack[i];
				if (r->type == Node_array_ref)
					r = r->orig_array;
				if (pname)
					*pname = fparam;
				break;
			}
		}
	}
	return r;
}

/* find_symbol --- find a symbol in current context */

static
NODE *find_symbol(const char *name, char **pname)
{
	NODE *r = NULL;

	if (pname)
		*pname = NULL;
	if (prog_running)
		r = find_param(name, cur_frame, pname);
	if (r == NULL)
		r = lookup(name); // for now, require fully qualified name
	if (r == NULL)
		fprintf(out_fp, _("no symbol `%s' in current context\n"), name);
	return r;
}

/* find_array -- find an array in current context */

static NODE *
find_array(const char *name)
{
	NODE *r;
	r = find_symbol(name, NULL);
	if (r != NULL && r->type != Node_var_array) {
		fprintf(out_fp, _("`%s' is not an array\n"), name);
		return NULL;
	}
	return r;
}

/* print_field --- print the value of $n */

static void
print_field(long field_num)
{
	NODE **lhs;
	lhs = get_field(field_num, NULL);
	if (*lhs == Null_field || *lhs == Nnull_string)
		fprintf(out_fp, _("$%ld = uninitialized field\n"), field_num);
	else {
		fprintf(out_fp, "$%ld = ", field_num);
		valinfo(*lhs, fprintf, out_fp);
	}
}

/* print_array --- print the contents of an array */

static int
print_array(volatile NODE *arr, char *arr_name)
{
	NODE *subs;
	NODE **list;
	int i;
	size_t num_elems = 0;
	volatile NODE *r;
	volatile int ret = 0;
	volatile jmp_buf pager_quit_tag_stack;

	if (assoc_empty((NODE *) arr)) {
		gprintf(out_fp, _("array `%s' is empty\n"), arr_name);
		return 0;
	}

	num_elems = assoc_length((NODE *) arr);

	/* sort indices, sub_arrays are also sorted! */
	list = assoc_list((NODE *) arr, "@ind_str_asc", SORTED_IN);

	PUSH_BINDING(pager_quit_tag_stack, pager_quit_tag, pager_quit_tag_valid);
	if (setjmp(pager_quit_tag) == 0) {
		for (i = 0; ret == 0 && i < num_elems; i++) {
			subs = list[i];
			r = *assoc_lookup((NODE *) arr, subs);
			if (r->type == Node_var_array)
				ret = print_array(r, r->vname);
			else {
				gprintf(out_fp, "%s[\"%.*s\"] = ", arr_name, (int) subs->stlen, subs->stptr);
				valinfo((NODE *) r, gprintf, out_fp);
			}
		}
	} else
		ret = 1;

	POP_BINDING(pager_quit_tag_stack, pager_quit_tag, pager_quit_tag_valid);

	for (i = 0; i < num_elems; i++)
		unref(list[i]);
	efree(list);

	return ret;
}

/* print_subscript --- print an array element */

static void
print_subscript(NODE *arr, char *arr_name, CMDARG *a, int count)
{
	NODE *r, *subs;

	subs = a->a_node;
	r = in_array(arr, subs);
	if (r == NULL)
		fprintf(out_fp, _("subscript \"%.*s\" is not in array `%s'\n"), (int) subs->stlen, subs->stptr, arr_name);
	else if (r->type == Node_var_array) {
		if (count > 1)
			print_subscript(r, r->vname, a->next, count - 1);
		else {
			/* print # of elements in array */
			fprintf(out_fp, "%s = ", r->vname);
			print_symbol(r, false);
		}
	} else {
		fprintf(out_fp, "%s[\"%.*s\"] = ", arr_name, (int) subs->stlen, subs->stptr);
		valinfo(r, fprintf, out_fp);
	}
}

/* do_print_var --- print command */

int
do_print_var(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	NODE *r;
	CMDARG *a;
	char *name, *pname;

	for (a = arg; a != NULL; a = a->next) {
		switch (a->type) {
		case D_variable:
			name = a->a_string;
			if ((r = find_symbol(name, &pname)) != NULL) {
				fprintf(out_fp, "%s = ", name);
				print_symbol(r, (pname != NULL));
			}
			break;

		case D_subscript:
			assert(a->a_count > 0);
			name = a->a_string;
			r = find_array(name);
			if (r != NULL)
				print_subscript(r, name, a->next, a->a_count);
			break;

		case D_array:
			name = a->a_string;
			if ((r = find_array(name)) != NULL) {
				int count = a->a_count;
				for (; count > 0; count--) {
					NODE *value, *subs;
					a = a->next;
					subs = a->a_node;
					value = in_array(r, subs);
					if (value == NULL) {
						fprintf(out_fp, _("subscript \"%.*s\" is not in array `%s'\n"),
									(int) subs->stlen, subs->stptr, name);
						break;
					} else if (value->type != Node_var_array) {
						fprintf(out_fp, _("`%s[\"%.*s\"]' is not an array\n"),
									name, (int) subs->stlen, subs->stptr);
						break;
					} else {
						r = value;
						name = r->vname;
					}
				}
				if (count == 0) {
					initialize_pager(out_fp);
					print_array((volatile NODE *) r, name);
				}
			}
			break;

		case D_field:
			print_field(get_number_si(a->a_node));
			break;

		default:
			/* notably D_node, subscript for invalid array name; skip */
			break;
		}
	}
	return false;
}

/* do_set_var --- set command */

int
do_set_var(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	NODE *r, *val;
	NODE **lhs;
	char *name, *pname;

	switch (arg->type) {
	case D_variable:
		name = arg->a_string;
		arg = arg->next;
		val = arg->a_node;

		if ((r = find_symbol(name, &pname)) == NULL)
			break;

		switch (r->type) {
		case Node_var_new:
		case Node_elem_new:
			r->type = Node_var;
			r->var_value = dupnode(Nnull_string);
			/* fall through */
		case Node_var:
			lhs = &r->var_value;
			unref(*lhs);
			*lhs = dupnode(val);
			if (pname == NULL && r->var_assign != NULL)
				r->var_assign();
			fprintf(out_fp, "%s = ", name);
			print_symbol(r, (pname != NULL));
			break;

		default:
			d_error(_("`%s' is not a scalar variable"), name);
			break;
		}
		break;

	case D_subscript:
	{
		NODE *subs, *value;
		int count = arg->a_count;
		NODE *newval;

		assert(count > 0);
		name = arg->a_string;
		r = find_array(name);
		if (r == NULL)
			break;
		for (; count > 0; count--) {
			arg = arg->next;
			subs = arg->a_node;
			value = in_array(r, subs);

			if (count == 1) {
				if (value != NULL && value->type == Node_var_array)
					d_error(_("attempt to use array `%s[\"%.*s\"]' in a scalar context"),
								name, (int) subs->stlen, subs->stptr);
				else {
					arg = arg->next;
					val = arg->a_node;
					newval = dupnode(val);
					// subs should not be freed, so
					// use dupnode in call to assoc_set.
					assoc_set(r, dupnode(subs), newval);
					fprintf(out_fp, "%s[\"%.*s\"] = ", name, (int) subs->stlen, subs->stptr);
					valinfo(newval, fprintf, out_fp);
				}
			} else {
				if (value == NULL) {
					NODE *array;
					array = make_array();
					array->vname = estrdup(subs->stptr, subs->stlen);
					array->parent_array = r;
					// subs should not be freed, so
					// use dupnode in call to assoc_set.
					assoc_set(r, dupnode(subs), array);
					r = array;
				} else if (value->type != Node_var_array) {
					d_error(_("attempt to use scalar `%s[\"%.*s\"]' as array"),
							name, (int) subs->stlen, subs->stptr);
					break;
				} else {
					r = value;
					name = r->vname;
				}
			}
		}
	}
		break;

	case D_field:
	{
		long field_num;
		Func_ptr assign = NULL;

		field_num = get_number_si(arg->a_node);
		assert(field_num >= 0);
		arg = arg->next;
		val = arg->a_node;
		lhs = get_field(field_num, &assign);
		if (assign)
			/* call assign to copy fields before unref frees $0 */
			assign();
		unref(*lhs);
		*lhs = dupnode(val);
		print_field(field_num);
	}
		break;

	default:
		break;
	}
	return false;
}

/* find_item --- find an item in the watch/display list */

static struct list_item *
find_item(struct list_item *list, long num)
{
	struct list_item *d;

	if (num <= 0)
		return NULL;
	for (d = list->next; d != list; d = d->next) {
		if (d->number == num)
			return d;
	}
	return NULL;
}

/* delete_item --- delete an item from the watch/display list */

static void
delete_item(struct list_item *d)
{
	struct commands_item *c;
	int i;

	if (IS_SUBSCRIPT(d)) {
		for (i = 0; i < d->num_subs; i++)
			unref(d->subs[i]);
		efree(d->subs);
	} else if (IS_FIELD(d))
		unref(d->symbol);

	if ((d->flags & CUR_IS_ARRAY) == 0)
		unref(d->cur_value);
	if ((d->flags & OLD_IS_ARRAY) == 0)
		unref(d->old_value);

	/* delete commands */
	for (c = d->commands.next; c != &d->commands; c = c->next) {
		c = c->prev;
		delete_commands_item(c->next);
	}

	free_context(d->cndn.ctxt, false);
	if (d->cndn.expr != NULL)
		efree(d->cndn.expr);

	d->next->prev = d->prev;
	d->prev->next = d->next;
	efree(d);
}

/* add_item --- craete a watch/display item and add it to the list */

static struct list_item *
add_item(struct list_item *list, int type, NODE *symbol, char *pname)
{
	struct list_item *d;

	ezalloc(d, struct list_item *, sizeof(struct list_item), "add_item");
	d->commands.next = d->commands.prev = &d->commands;

	d->number = ++list->number;
	d->sname = symbol->vname;
	if (pname != NULL) {	/* function param */
		d->sname = pname;
		d->flags |= PARAM;
		d->fcall_count = fcall_count - cur_frame;
	}

	if (type == D_field) {
		/* field number */
		d->symbol = symbol;
		d->flags |= FIELD_NUM;
	} else if (type == D_subscript) {
		/* subscript */
		d->symbol = symbol;
		d->flags |= SUBSCRIPT;
	} else {
		/* array or variable */
		d->symbol = symbol;
	}

	/* add to list */
	d->next = list->next;
	d->prev = list;
	list->next = d;
	d->next->prev = d;
	return d;
}

/* do_add_item --- add an item to the watch/display list */

static struct list_item *
do_add_item(struct list_item *list, CMDARG *arg)
{
	NODE *symbol = NULL;
	char *name, *pname = NULL;
	struct list_item *item = NULL;

	switch (arg->type) {
	case D_subscript:
	case D_variable:
		name = arg->a_string;
		if ((symbol = find_symbol(name, &pname)) == NULL)
			return NULL;
		if (symbol->type == Node_func) {
			d_error(_("`%s' is a function"), name);
			return NULL;
		}
		if (arg->type == D_subscript && symbol->type != Node_var_array) {
			d_error(_("`%s' is not an array\n"), name);
			return NULL;
		}

		item = add_item(list, arg->type, symbol, pname);
		if (item != NULL && arg->type == D_subscript) {
			NODE **subs;
			int count = arg->a_count;
			int i;

			assert(count > 0);
			emalloc(subs, NODE **, count * sizeof(NODE *), "do_add_item");
			for (i = 0; i < count; i++) {
				arg = arg->next;
				subs[i] = dupnode(arg->a_node);
				subs[i] = force_string(subs[i]);
			}
			item->subs = subs;
			item->num_subs = count;
		}
		break;

	case D_field:
		symbol = dupnode(arg->a_node);
		item = add_item(list, D_field, symbol, NULL);
		break;

	default:
		break;
	}

	/* watch condition if any */
	if (list == &watch_list) {
		arg = arg->next;
		if (item != NULL && arg != NULL) {
			if (parse_condition(D_watch, item->number, arg->a_string) == 0)
				arg->a_string = NULL;	/* don't let free_cmdarg free it */
			else
				fprintf(out_fp, _("watchpoint %d is unconditional\n"), item->number);
		}
	}
	return item;
}

/* do_delete_item --- delete a watch/display item from list. */

static void
do_delete_item(struct list_item *list, CMDARG *arg)
{
	if (arg == NULL) {
		while (list->next != list)
			delete_item(list->next);
	}

	for (; arg != NULL; arg = arg->next) {
		struct list_item *d;
		if (arg->type == D_range) {
			long i, j;

			i = arg->a_int;
			arg = arg->next;
			j = arg->a_int;
			if (j > list->number)
				j = list->number;
			for (; i <= j; i++) {
				if ((d = find_item(list, i)) != NULL)
					delete_item(d);
			}
		} else {
			if ((d = find_item(list, arg->a_int)) == NULL) {
				/* split into two for easier message translation */
				if (list == &display_list)
					d_error(_("no display item numbered %ld"),
						arg->a_int);
				else
					d_error(_("no watch item numbered %ld"),
						arg->a_int);
			} else
				delete_item(d);
		}
	}
}

/* display --- print an item from the auto-display list */

static void
display(struct list_item *d)
{
	NODE *symbol;

	symbol = d->symbol;
	if (IS_PARAM(d)	&& (d->fcall_count != (fcall_count - cur_frame)))
		return;

	if (IS_SUBSCRIPT(d)) {
		NODE *sub, *r;
		int i = 0, count = d->num_subs;
		for (i = 0; i < count; i++) {
			sub = d->subs[i];
			r = in_array(symbol, sub);
			if (r == NULL) {
				fprintf(out_fp, _("%d: subscript \"%.*s\" is not in array `%s'\n"),
							d->number, (int) sub->stlen, sub->stptr, d->sname);
				break;
			}
			if (r->type == Node_var_array) {
				symbol = r;
				if (i == count - 1)	/* it's a sub-array */
					goto print_sym;		/* print # of elements in sub-array */
			} else {
				if (i != count - 1)
					return;		/* FIXME msg and delete item ? */
				fprintf(out_fp, "%d: %s[\"%.*s\"] = ", d->number,
							d->sname, (int) sub->stlen, sub->stptr);
				valinfo(r, fprintf, out_fp);
			}
		}
	} else if (IS_FIELD(d)) {
		NODE *r = d->symbol;
		fprintf(out_fp, "%d: ", d->number);
		print_field(get_number_si(r));
	} else {
print_sym:
		fprintf(out_fp, "%d: %s = ", d->number, d->sname);
		print_symbol(symbol, IS_PARAM(d));
	}
}


/* do_display --- display command */

int
do_display(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	struct list_item *d;

	if (arg == NULL) {
		/* display all items */
		for (d = display_list.prev; d != &display_list; d = d->prev)
			display(d);
		return false;
	}

	if ((d = do_add_item(&display_list, arg)) != NULL)
		display(d);

	return false;
}

/* do_undisplay --- undisplay command */

int
do_undisplay(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	do_delete_item(&display_list, arg);
	return false;
}

/* condition_triggered --- test if a condition expression is true */

static int
condition_triggered(struct condition *cndn)
{
	NODE *r;
	int di;

	assert(cndn != NULL);
	if (cndn->code == NULL)
		return true;

	push_context(cndn->ctxt);
	r = execute_code((volatile INSTRUCTION *) cndn->code);
	pop_context();  /* switch to prev context */
	if (r == NULL)      /* fatal error */
		return false;   /* not triggered */

	force_number(r);
	di = ! is_zero(r);
	DEREF(r);
	return di;
}


static int
find_subscript(struct list_item *item, NODE **ptr)
{
	NODE *symbol = item->symbol;
	NODE *sub, *r;
	int i = 0, count = item->num_subs;

	r = *ptr = NULL;
	for (i = 0; i < count; i++) {
		sub = item->subs[i];
		r = in_array(symbol, sub);
		if (r == NULL)
			return 0;
		if (r->type == Node_var_array)
			symbol = r;
		else if (i < count - 1)
			return -1;
	}
	if (r != NULL)
		*ptr = r;
	return 0;
}

/* cmp_val --- compare values of watched item, returns true if different; */

static int
cmp_val(struct list_item *w, NODE *old, NODE *new)
{
		/*
		 *	case    old     new     result
		 *	------------------------------
		 *	1:      NULL    ARRAY   true
		 *	2:      NULL    SCALAR  true
		 *	3:      NULL    NULL    false
		 *	4:      SCALAR  SCALAR  cmp_node
		 *	5:      SCALAR  ARRAY   true
		 *	6:      SCALAR  NULL    true
		 *	7:      ARRAY   SCALAR  true
		 *	8:      ARRAY   ARRAY   compare size
		 *	9:      ARRAY   NULL    true
		 */

	if (WATCHING_ARRAY(w)) {
		long size = 0;
		if (! new)		/* 9 */
			return true;
		if (new->type == Node_val)	/* 7 */
			return true;
		/* new->type == Node_var_array */	/* 8 */
		size = assoc_length(new);
		if (w->cur_size == size)
			return false;
		return true;
	}

	if (! old && ! new)	/* 3 */
		return false;
	if ((! old && new)	/* 1, 2 */
			|| (old && ! new))	/* 6 */
		return true;

	if (new->type == Node_var_array)	/* 5 */
		return true;
	return cmp_nodes(old, new, true);	/* 4 */
}

/* watchpoint_triggered --- check if we should stop at this watchpoint;
 *                          update old and current values accordingly.
 */

static int
watchpoint_triggered(struct list_item *w)
{
	NODE *symbol;
	NODE *t1, *t2;

	symbol = w->symbol;
	if (IS_PARAM(w) && (w->fcall_count != (fcall_count - cur_frame)))
		return 0; /* parameter with same name in a different function */
	if (! condition_triggered(&w->cndn))
		return 0;

	t1 = w->cur_value;
	t2 = (NODE *) 0;
	if (IS_SUBSCRIPT(w))
		(void) find_subscript(w, &t2);
	else if (IS_FIELD(w)) {
		long field_num;
		field_num = get_number_si(w->symbol);
		t2 = *get_field(field_num, NULL);
	} else {
		switch (symbol->type) {
		case Node_var:
			t2 = symbol->var_value;
			break;
		case Node_var_array:
			t2 = symbol;
			break;
		case Node_var_new:
		case Node_elem_new:
			break;
		default:
			cant_happen("unexpected symbol type %s", nodetype2str(symbol->type));
		}
	}

	if (! cmp_val(w, t1, t2))
		return 0;

	/* update old and current values */

	if ((w->flags & OLD_IS_ARRAY) == 0)
		unref(w->old_value);
	w->flags &= ~OLD_IS_ARRAY;
	if (WATCHING_ARRAY(w)) {	/* 7, 8, 9 */
		w->old_size = w->cur_size;
		w->flags |= OLD_IS_ARRAY;
		if (! t2) {
			w->flags &= ~CUR_IS_ARRAY;
			w->cur_value = 0;
		} else if (t2->type == Node_val) {
			w->flags &= ~CUR_IS_ARRAY;
			w->cur_value = dupnode(t2);
		} else
			w->cur_size = (t2->type == Node_var_array) ? assoc_length(t2) : 0;
	} else if (! t1) { /* 1, 2 */
		w->old_value = 0;
		/* new != NULL */
		if (t2->type == Node_val)
			w->cur_value = dupnode(t2);
		else {
			w->flags |= CUR_IS_ARRAY;
			w->cur_size = (t2->type == Node_var_array) ? assoc_length(t2) : 0;
		}
	} else /* if (t1->type == Node_val) */ {	/* 4, 5, 6 */
		w->old_value = w->cur_value;
		if (! t2)
			w->cur_value = 0;
		else if (t2->type == Node_var_array) {
			w->flags |= CUR_IS_ARRAY;
			w->cur_size = assoc_length(t2);
		} else
			w->cur_value = dupnode(t2);
	}

	return w->number;
}

/* initialize_watch_item --- initialize current value of a watched item */

static int
initialize_watch_item(struct list_item *w)
{
	NODE *t, *r;
	NODE *symbol = w->symbol;

	if (IS_SUBSCRIPT(w)) {
		if (find_subscript(w, &r) == -1) {
			d_error(_("attempt to use scalar value as array"));
			return -1;
		}

		if (r == NULL)
			w->cur_value = (NODE *) 0;
		else if (r->type == Node_var_array) { /* it's a sub-array */
			w->flags |= CUR_IS_ARRAY;
			w->cur_size = assoc_length(r);
		} else
			w->cur_value = dupnode(r);
	} else if (IS_FIELD(w)) {
		long field_num;
		t = w->symbol;
		field_num = get_number_si(t);
		r = *get_field(field_num, NULL);
		w->cur_value = dupnode(r);
	} else {
		if (symbol->type == Node_var_new || symbol->type == Node_elem_new)
			w->cur_value = (NODE *) 0;
		else if (symbol->type == Node_var) {
			r = symbol->var_value;
			w->cur_value = dupnode(r);
		} else if (symbol->type == Node_var_array) {
			w->flags |= CUR_IS_ARRAY;
			w->cur_size = assoc_length(symbol);
		} else if (symbol->type == Node_val && (symbol->flags & REGEX) != 0) {
			w->cur_value = dupnode(symbol);
		} /* else
			can't happen */
	}
	return 0;
}

/* do_watch --- watch command */

int
do_watch(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	struct list_item *w;
	NODE *symbol, *sub;
	int i;

	w = do_add_item(&watch_list, arg);
	if (w == NULL)
		return false;

	if (initialize_watch_item(w) == -1) {
		delete_item(w);
		return false;
	}

	fprintf(out_fp, "Watchpoint %d: ", w->number);
	symbol = w->symbol;

	/* FIXME: common code also in print_watch_item */
	if (IS_SUBSCRIPT(w)) {
		fprintf(out_fp, "%s", w->sname);
		for (i = 0; i < w->num_subs; i++) {
			sub = w->subs[i];
			fprintf(out_fp, "[\"%.*s\"]", (int) sub->stlen, sub->stptr);
		}
		fprintf(out_fp, "\n");
	} else if (IS_FIELD(w))
		fprintf(out_fp, "$%ld\n", get_number_si(symbol));
	else
		fprintf(out_fp, "%s\n", w->sname);

	return false;
}

/* do_unwatch --- unwatch command */

int
do_unwatch(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	do_delete_item(&watch_list, arg);
	return false;
}

/* callback from pop_frame in eval.c */

void
frame_popped()
{
	struct list_item *item;

	/* delete all out of scope watchpoints */
	for (item = watch_list.next; item != &watch_list; item = item->next) {
		if (IS_PARAM(item) && (item->fcall_count > fcall_count)) {
			fprintf(out_fp,
				_("Watchpoint %d deleted because parameter is out of scope.\n"),
				item->number);
			item = item->prev;
			delete_item(item->next);
		}
	}

	/* delete all out of scope display items */
	for (item = display_list.next; item != &display_list; item = item->next) {
		if (IS_PARAM(item) && (item->fcall_count > fcall_count)) {
			fprintf(out_fp,
				_("Display %d deleted because parameter is out of scope.\n"),
				item->number);
			item = item->prev;
			delete_item(item->next);
		}
	}
}

/* print_function --- print function name, parameters, and optionally
 *                file and line number.
 */

static int
print_function(INSTRUCTION *pc, void *x)
{
	NODE *func;
	int i, pcount;
	struct pf_data *data = (struct pf_data *) x;
	int defn = data->defn;
	Func_print print_func = data->print_func;
	FILE *fp = data->fp;

	func = pc->func_body;
	pcount = func->param_cnt;

	print_func(fp, "%s(", func->vname);
	for (i = 0; i < pcount; i++) {
		print_func(fp, "%s", func->fparms[i].param);
		if (i < pcount - 1)
			print_func(fp, ", ");
	}
	print_func(fp, ")");
	if (defn)
		print_func(fp, _(" in file `%s', line %d\n"),
				pc->source_file, pc->source_line);
	return 0;
}

/* print_frame --- print function name, parameters,
 *                 source and line number of where it is
 *                 executing.
 */

static void
print_frame(NODE *func, char *src, int srcline)
{
	if (func == NULL)
		fprintf(out_fp, "main()");
	else {
		pf_data.print_func = fprintf;
		pf_data.fp = out_fp;
		pf_data.defn = false;
		(void) print_function(func->code_ptr, &pf_data);
	}
	fprintf(out_fp, _(" at `%s':%d"), src, srcline);
}

/* print_numbered_frame --- print a frame given its number */

static void
print_numbered_frame(long num)
{
	NODE *f;

	assert(prog_running == true);
	f = find_frame(num);
	if (num == 0) {
		fprintf(out_fp, "#%ld\t ", num);
		print_frame(f->func_node, source, sourceline);
	} else {
		fprintf(out_fp, _("#%ld\tin "), num);
		print_frame(f->func_node, f->vname,
			((INSTRUCTION *) find_frame(num - 1)->reti)->source_line);
	}
	fprintf(out_fp, "\n");
}

/* do_backtrace --- backtrace command */

int
do_backtrace(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	long cur = 0;
	long last = fcall_count;

	CHECK_PROG_RUNNING();
	if (arg != NULL && arg->type == D_int) {
		long count = arg->a_int;

		/* frame_ptr (frame #0),  fcall_list[1, 2, ... fcall_count] => total count */
		if (count >= 0) {
			/* toward outermost frame #fcall_count */
			last = count - 1;
			if (last > fcall_count)
				 last = fcall_count;
		} else {
			/* toward innermost frame #0 */
			cur = 1 + fcall_count + count;
			if (cur < 0)
				cur = 0;
		}
	}

	for (; cur <= last; cur++) {
		print_numbered_frame(cur);
	}
	if (cur <= fcall_count)
		fprintf(out_fp, _("More stack frames follow ...\n"));
	return false;
}

/* print_cur_frame_and_sourceline --- print current frame, and
 *                                    current source line.
 */

static void
print_cur_frame_and_sourceline()
{
	NODE *f;
	int srcline;
	char *src;

	assert(prog_running == true);
	f = find_frame(cur_frame);
	if (cur_frame == 0) {
		src = source;
		srcline = sourceline;
	} else {
		f = find_frame(cur_frame);
		src = f->vname;
		srcline = ((INSTRUCTION *) find_frame(cur_frame - 1)->reti)->source_line;
	}

	fprintf(out_fp, (cur_frame > 0 ? _("#%ld\tin ") : "#%ld\t "), cur_frame);
	print_frame(f->func_node, src, srcline);
	fprintf(out_fp, "\n");
	print_lines(src, srcline, 1);
	last_printed_line = srcline - list_size / 2;
	if (last_printed_line < 0)
		last_printed_line = 0;
}

/* do_frame --- frame command */

int
do_frame(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	CHECK_PROG_RUNNING();
	if (arg && arg->type == D_int) {
		if (arg->a_int < 0 || arg->a_int > fcall_count) {
			d_error(_("invalid frame number"));
			return false;
		}
		cur_frame = arg->a_int;
	}
	print_cur_frame_and_sourceline();
	return false;
}

/* do_up --- up command */

int
do_up(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	CHECK_PROG_RUNNING();
	if (arg != NULL && arg->type == D_int)
		cur_frame += arg->a_int;
	else
		cur_frame++;
	if (cur_frame < 0)
		cur_frame = 0;
	else if (cur_frame > fcall_count)
		cur_frame = fcall_count;
	print_cur_frame_and_sourceline();
	return false;
}

/* do_down --- down command */

int
do_down(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	CHECK_PROG_RUNNING();
	if (arg != NULL && arg->type == D_int)
		cur_frame -= arg->a_int;
	else
		cur_frame--;
	if (cur_frame < 0)
		cur_frame = 0;
	else if (cur_frame > fcall_count)
		cur_frame = fcall_count;
	print_cur_frame_and_sourceline();
	return false;
}

/* find_rule --- find a rule or function in file 'src' containing
 *               source line 'lineno'
 */

static INSTRUCTION *
find_rule(char *src, long lineno)
{
	INSTRUCTION *rp;

	/*
	 * FIXME: The check for zero and code that goes with it
	 * are probably fragile.  A break with no arguments can
	 * cause this in certain cases. Try to review how this works.
	 */
	if (lineno == 0) {
		for (rp = rule_list->nexti; rp != NULL; rp = rp->nexti) {
			if ((rp - 1)->source_file == src && (rp - 1)->source_line > 0)
				return (rp - 1);
		}
	} else {
		for (rp = rule_list->nexti; rp != NULL; rp = rp->nexti) {
			if ((rp - 1)->source_file == src
					&& lineno >= (rp + 1)->first_line
					&& lineno <= (rp + 1)->last_line)
				return (rp - 1);
		}
	}
	return NULL;
}

/* mk_breakpoint --- create a breakpoint instruction and the corresponding
 *                   breakpoint structure.
 */

static INSTRUCTION *
mk_breakpoint(char *src, int srcline)
{
	INSTRUCTION *bp;
	BREAKPOINT *b;

	bp = bcalloc(Op_breakpoint, 1, srcline);
	emalloc(b, BREAKPOINT *, sizeof(BREAKPOINT), "mk_breakpoint");
	memset(&b->cndn, 0, sizeof(struct condition));
	b->commands.next = b->commands.prev = &b->commands;
	b->silent = false;


	b->number = ++watch_list.number;	/* breakpoints and watchpoints use same counter */
	b->ignore_count = 0;
	b->hit_count = 0;
	b->flags = BP_ENABLE;
	b->src = src;
	bp->break_pt = b;
	b->bpi = bp;

	/* prepend to list */
	b->next = breakpoints.next;
	b->prev = &breakpoints;
	breakpoints.next = b;
	b->next->prev = b;
	return bp;
}

/* delete_breakpoint --- delete a breakpoint structure and
 *                       disable the breakpoint instruction.
 */

static void
delete_breakpoint(BREAKPOINT *b)
{
	INSTRUCTION *pc = b->bpi;
	struct commands_item *c;

	/* N.B.: easiest thing to do is to turn Op_breakpoint into a no-op;
	 * deleteing the instruction is not that simple,
	 * since could have reference to it somewhere else (e.g. cur_pc).
	 */

	pc->opcode = Op_no_op;
	pc->source_line = 0;
	pc->break_pt = NULL;

	/* delete commands */
	for (c = b->commands.next; c != &b->commands; c = c->next) {
		c = c->prev;
		delete_commands_item(c->next);
	}

	free_context(b->cndn.ctxt, false);
	if (b->cndn.expr != NULL)
		efree(b->cndn.expr);

	/* remove from list */
	b->next->prev = b->prev;
	b->prev->next = b->next;
	efree(b);
}

/* find_breakpoint --- find the breakpoint structure from a breakpoint number */

static BREAKPOINT *
find_breakpoint(long num)
{
	BREAKPOINT *b;

	if (num <= 0)
		return NULL;

	for (b = breakpoints.next; b != &breakpoints; b = b->next) {
		if (b->number == num)
			return b;
	}
	return NULL;
}

/* add_breakpoint --- add a breakpoint instruction between PREVP and IP */

static BREAKPOINT *
add_breakpoint(INSTRUCTION *prevp, INSTRUCTION *ip, char *src, bool silent)
{
	BREAKPOINT *b;
	INSTRUCTION *bp;
	int lineno = ip->source_line;

	/* add new breakpoint instruction at the end of
	 * already set breakpoints at this line number.
	 */

	while (ip->opcode == Op_breakpoint && ip->source_line == lineno) {
		if (! silent) {
			b = ip->break_pt;
			/*
			 * This is more verbose that it might otherwise be,
			 * in order to provide easily translatable strings.
			 */
			if ((b->flags & BP_ENABLE) != 0) {
				if ((b->flags & BP_IGNORE) != 0)
					fprintf(out_fp,
			_("Note: breakpoint %d (enabled, ignore next %ld hits), also set at %s:%d"),
						b->number,
						b->ignore_count,
						b->src,
						lineno);
				else
					fprintf(out_fp,
			_("Note: breakpoint %d (enabled), also set at %s:%d"),
						b->number,
						b->src,
						lineno);
			} else {
				if ((b->flags & BP_IGNORE) != 0)
					fprintf(out_fp,
			_("Note: breakpoint %d (disabled, ignore next %ld hits), also set at %s:%d"),
						b->number,
						b->ignore_count,
						b->src,
						lineno);
				else
					fprintf(out_fp,
			_("Note: breakpoint %d (disabled), also set at %s:%d"),
						b->number,
						b->src,
						lineno);
			}
		}
		prevp = ip;
		ip = ip->nexti;
	}

	assert(ip->source_line == lineno);

	bp = mk_breakpoint(src, lineno);
	prevp->nexti = bp;
	bp->nexti = ip;
	b = bp->break_pt;
	if (! silent)
		fprintf(out_fp, _("Breakpoint %d set at file `%s', line %d\n"),
						b->number, src, lineno);
	return b;
}

/* set_breakpoint_at --- set a breakpoint at given line number*/

static BREAKPOINT *
set_breakpoint_at(INSTRUCTION *rp, int lineno, bool silent)
{
	INSTRUCTION *ip, *prevp;

	for (prevp = rp, ip = rp->nexti; ip; prevp = ip, ip = ip->nexti) {
		if (ip->opcode == Op_K_case) {
			INSTRUCTION *i1, *i2;

			/* Special case: the code line numbers for a switch do not form
			 * a monotonically increasing sequence. Check if the line # is between
			 * the first and last statements of the case block before continuing
			 * the search.
			 */
			for (i2 = ip->stmt_start, i1 = i2->nexti; i2 != ip->stmt_end;
								i2 = i1, i1 = i1->nexti) {
				if (i1->source_line >= lineno)
					return add_breakpoint(i2, i1, rp->source_file, silent);
				if (i1 == ip->stmt_end)
					break;
			}
		}

		if (ip->source_line >= lineno)
			return add_breakpoint(prevp, ip, rp->source_file, silent);
		if (ip == (rp + 1)->lasti)
			break;
	}
	return NULL;
}

/* set_breakpoint_next --- set a breakpoint at the next instruction */

static BREAKPOINT *
set_breakpoint_next(INSTRUCTION *rp, INSTRUCTION *ip)
{
	INSTRUCTION *prevp;

	if (ip == (rp + 1)->lasti)
		return NULL;
	prevp = ip;
	if (ip->opcode != Op_breakpoint)
		ip = ip->nexti;
	for (; ip; prevp = ip, ip = ip->nexti) {
		if (ip->source_line > 0)
			return add_breakpoint(prevp, ip, rp->source_file, false);
		if (ip == (rp + 1)->lasti)
			break;
	}
	return NULL;
}

/* set_breakpoint --- set a breakpoint */

static int
set_breakpoint(CMDARG *arg, bool temporary)
{
	int lineno;
	BREAKPOINT *b = NULL;
	INSTRUCTION *rp, *ip;
	NODE *func;
	SRCFILE *s = cur_srcfile;
	char *src = cur_srcfile->src;

	if (arg == NULL) {
/*
* (From GDB Documentation):
*
* When called without any arguments, break sets a breakpoint at the next instruction
* to be executed in the selected stack frame (see section Examining the Stack).
* In any selected frame but the innermost, this makes your program stop as soon
* as control returns to that frame. This is similar to the effect of a finish command
* in the frame inside the selected frame--except that finish does not leave an
* active breakpoint. If you use break without an argument in the innermost frame,
* GDB stops the next time it reaches the current location; this may be useful
* inside loops.
* GDB normally ignores breakpoints when it resumes execution, until at least
* one instruction has been executed. If it did not do this,
* you would be unable to proceed past a breakpoint without first disabling the
* breakpoint. This rule applies whether or not the breakpoint already existed
* when your program stopped.
*/
		CHECK_PROG_RUNNING();
		if (cur_frame == 0) {
			src = source;
			ip = cur_pc;
		} else {
			NODE *f;
			f = find_frame(cur_frame);
			src = f->vname;
			ip = (INSTRUCTION *) find_frame(cur_frame - 1)->reti;  /* Op_func_call */
		}
		rp = find_rule(src, ip->source_line);
		assert(rp != NULL);
		if ((b = set_breakpoint_next(rp, ip)) == NULL)
			fprintf(out_fp, _("cannot set breakpoint in file `%s'\n"), src);
		else {
			if (cur_frame == 0) {	/* stop next time */
				b->flags |= BP_IGNORE;
				b->ignore_count = 1;
			}
			if (temporary)
				b->flags |= BP_TEMP;
		}
		return false;
	}

	/* arg != NULL */

	switch (arg->type) {
	case D_string:	/* break filename:lineno|function */
		s = source_find(arg->a_string);
		arg = arg->next;
		if (s == NULL || arg == NULL
				|| (arg->type != D_int && arg->type != D_func))
			return false;
		src = s->src;
		if (arg->type == D_func) /* break filename:function */
			goto func;
		else
			/* fall through */
	case D_int:		/* break lineno */
		lineno = (int) arg->a_int;
		if (lineno <= 0 || lineno > s->srclines)
			d_error(_("line number %d in file `%s' is out of range"), lineno, src);
		else {
			rp = find_rule(src, lineno);
			if (rp == NULL)
				fprintf(out_fp, _("internal error: cannot find rule\n"));
			if (rp == NULL || (b = set_breakpoint_at(rp, lineno, false)) == NULL)
				fprintf(out_fp, _("cannot set breakpoint at `%s':%d\n"),
						src, lineno);
			if (b != NULL && temporary)
				b->flags |= BP_TEMP;
		}
		break;

	case D_func:	/* break function */
func:
		func = arg->a_node;
		rp = func->code_ptr;
		if ((b = set_breakpoint_at(rp, rp->source_line, false)) == NULL)
			fprintf(out_fp, _("cannot set breakpoint in function `%s'\n"),
						func->vname);
		else {
			if (temporary)
				b->flags |= BP_TEMP;
			lineno = b->bpi->source_line;
		}
		break;

	default:
		return false;
	}
	/* condition if any */
	arg = arg->next;
	if (b != NULL && arg != NULL) {
		if (parse_condition(D_break, b->number, arg->a_string) == 0)
			arg->a_string = NULL;	/* don't let free_cmdarg free it */
		else
			fprintf(out_fp, _("breakpoint %d set at file `%s', line %d is unconditional\n"),
							b->number, src, lineno);
	}
	return false;
}


/* breakpoint_triggered --- check if we should stop at this breakpoint */

static int
breakpoint_triggered(BREAKPOINT *b)
{
	if ((b->flags & BP_ENABLE) == 0)
		return 0;
	if ((b->flags & BP_IGNORE) != 0) {
		if (--b->ignore_count <= 0)
			b->flags &= ~BP_IGNORE;
		return 0;
	}

	if (! condition_triggered(&b->cndn))
		return 0;

	b->hit_count++;
	if ((b->flags & BP_ENABLE_ONCE) != 0) {
		b->flags &= ~BP_ENABLE_ONCE;
		b->flags &= ~BP_ENABLE;
	}
	return b->number;
}

/* do_breakpoint --- break command */

int
do_breakpoint(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	return set_breakpoint(arg, false);
}

/* do_tmp_breakpoint --- tbreak command */

int
do_tmp_breakpoint(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	return set_breakpoint(arg, true);
}

/* do_clear --- clear command */

int
do_clear(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	int lineno;
	BREAKPOINT *b;
	INSTRUCTION *rp, *ip;
	NODE *func;
	SRCFILE *s = cur_srcfile;
	char *src = cur_srcfile->src;
	int bp_found = 0;

	if (arg == NULL) {	/* clear */
		CHECK_PROG_RUNNING();
		if (cur_frame == 0) {
			lineno = sourceline;
			src = source;
		} else {
			NODE *f;
			f = find_frame(cur_frame);
			src = f->vname;
			lineno = ((INSTRUCTION *) find_frame(cur_frame - 1)->reti)->source_line;
		}
		goto delete_bp;
	}

	switch (arg->type) {
	case D_string:	/* clear filename:lineno|function */
		s = source_find(arg->a_string);
		arg = arg->next;
		if (s == NULL || arg == NULL ||
				(arg->type != D_int && arg->type != D_func))
			return false;
		src = s->src;
		if (arg->type == D_func)
			goto func;
		/* fall through */
	case D_int:	/* clear lineno */
		lineno = (int) arg->a_int;
		if (lineno <= 0 || lineno > s->srclines) {
			d_error(_("line number %d in file `%s' out of range"), lineno, src);
			return false;
		}
		break;

	case D_func:	/* clear function */
func:
		func = arg->a_node;
		rp = func->code_ptr;
		for (ip = rp->nexti; ip; ip = ip->nexti) {
			if (ip->source_line <= 0)
				continue;
			if (ip->opcode != Op_breakpoint)
				break;
			b = ip->break_pt;
			if (++bp_found == 1)
				fprintf(out_fp, _("Deleted breakpoint %d"), b->number);
			else
				fprintf(out_fp, ", %d", b->number);
			delete_breakpoint(b);
		}
		if (bp_found == 0)
			fprintf(out_fp, _("No breakpoint(s) at entry to function `%s'\n"),
					func->vname);
		else
			fprintf(out_fp, "\n");
		/* fall through */
	default:
		return false;
	}

delete_bp:
	rp = find_rule(src, lineno);
	if (rp != NULL) {
		for (ip = rp->nexti; ip; ip = ip->nexti) {
			if (ip->opcode == Op_breakpoint	&& ip->source_line == lineno) {
				b = ip->break_pt;
				if (++bp_found == 1)
					fprintf(out_fp, _("Deleted breakpoint %d"), b->number);
				else
					fprintf(out_fp, ", %d", b->number);
				delete_breakpoint(b);
			}
			if (ip == (rp + 1)->lasti)
				break;
		}
	}

	if (bp_found == 0)
		fprintf(out_fp, _("No breakpoint at file `%s', line #%d\n"),
					src, (int) lineno);
	else
		fprintf(out_fp, "\n");
	return false;
}

/* enable_breakpoint --- enable a breakpoint and set its disposition */

static inline void
enable_breakpoint(BREAKPOINT *b, short disp)
{
	b->flags &= ~(BP_ENABLE_ONCE|BP_TEMP);
	b->flags |= BP_ENABLE;
	if (disp)
		b->flags |= disp;
}

/* do_enable_breakpoint --- enable command */

int
do_enable_breakpoint(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	BREAKPOINT *b;
	short disp = 0;

	if (arg != NULL && arg->type == D_argument) {
		if (arg->a_argument == A_DEL)	/* del */
			disp = BP_TEMP;
		else						/* once */
			disp = BP_ENABLE_ONCE;
		arg = arg->next;
	}

	if (arg == NULL) {	/* enable [once|del] */
		for (b = breakpoints.next; b != &breakpoints; b = b->next)
			enable_breakpoint(b, disp);
	}

	for (; arg != NULL; arg = arg->next) {
		if (arg->type == D_range) {
			long i, j;

			i = arg->a_int;
			arg = arg->next;
			j = arg->a_int;
			if (j > breakpoints.number)
				j = breakpoints.number;
			for (; i <= j; i++) {
				if ((b = find_breakpoint(i)) != NULL)
					enable_breakpoint(b, disp);
			}
		} else {
			assert(arg->type == D_int);
			if ((b = find_breakpoint(arg->a_int)) == NULL)
				d_error(_("invalid breakpoint number"));
			else
				enable_breakpoint(b, disp);
		}
	}
	return false;
}

/* do_delete_breakpoint --- delete command */

int
do_delete_breakpoint(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	if (arg == NULL) {
		bool delete_all = true;
		delete_all = prompt_yes_no(
					_("Delete all breakpoints? (y or n) "),
					_("y")[0], true, out_fp);

		if (delete_all) {
			while (breakpoints.next != &breakpoints)
				delete_breakpoint(breakpoints.next);
		}
	}

	for (; arg != NULL; arg = arg->next) {
		BREAKPOINT *b;
		if (arg->type == D_range) {
			long i, j;

			i = arg->a_int;
			arg = arg->next;
			j = arg->a_int;
			if (j > breakpoints.number)
				j = breakpoints.number;
			for (; i <= j; i++) {
				if ((b = find_breakpoint(i)) != NULL)
					delete_breakpoint(b);
			}
		} else {
			if ((b = find_breakpoint(arg->a_int)) == NULL)
				d_error(_("invalid breakpoint number"));
			else
				delete_breakpoint(b);
		}
	}
	return false;
}

/* do_ignore_breakpoint --- ignore command */

int
do_ignore_breakpoint(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	BREAKPOINT *b;

	if (arg == NULL || arg->type != D_int
			|| arg->next == NULL || arg->next->type != D_int)
		return false;

	if ((b = find_breakpoint(arg->a_int)) == NULL)
		d_error(_("invalid breakpoint number"));
	else {
		b->ignore_count = arg->next->a_int;
		if (b->ignore_count > 0) {
			b->flags |= BP_IGNORE;
			fprintf(out_fp, _("Will ignore next %ld crossing(s) of breakpoint %d.\n"),
					b->ignore_count, b->number);
		} else {
			b->flags &= ~BP_IGNORE;
 			fprintf(out_fp, _("Will stop next time breakpoint %d is reached.\n"),
					b->number);
		}
	}
	return false;
}

/* do_disable_breakpoint --- disable command */

int
do_disable_breakpoint(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	BREAKPOINT *b;

	if (arg == NULL) {
		/* disable all */
		for (b = breakpoints.next; b != &breakpoints; b = b->next)
			b->flags &= ~BP_ENABLE;
	}

	for (; arg != NULL; arg = arg->next) {
		if (arg->type == D_range) {
			long i, j;

			i = arg->a_int;
			arg = arg->next;
			j = arg->a_int;
			if (j > breakpoints.number)
				j = breakpoints.number;
			for (; i <= j; i++)
				if ((b = find_breakpoint(i)) != NULL)
					b->flags &= ~BP_ENABLE;
		} else {
			if ((b = find_breakpoint(arg->a_int)) == NULL)
				d_error(_("invalid breakpoint number"));
			else
				b->flags &= ~BP_ENABLE;
		}
	}
	return false;
}

#ifdef HAVE_LIBREADLINE

/* get_function --- function definition in current context */

NODE *
get_function()
{
	NODE *func;

	if (! prog_running)
		return NULL;
	func = find_frame(cur_frame)->func_node;
	return func;
}

/* initialize_readline --- initialize readline */

static void
initialize_readline()
{
	/* tell readline which stream to use for output,
	 * default input stream is stdin.
	 */
	rl_outstream = out_fp;

	/* allow conditional parsing of the ~/.inputrc file. */
	rl_readline_name = "gawk";

	/* our completion function. */
	rl_attempted_completion_function = command_completion;

	read_a_line = readline;
}
#else
#define initialize_readline()	/* nothing */
#endif


/* init_debug --- register debugger exec hooks */

void
init_debug()
{
	register_exec_hook(debug_pre_execute, debug_post_execute);
}


/* debug_prog --- debugger entry point */

int
debug_prog(INSTRUCTION *pc)
{
	char *run;

	input_fd = fileno(stdin);
	out_fp = stdout;
	if (os_isatty(input_fd))
		input_from_tty = true;
	if (input_fd == 0 && input_from_tty)
		initialize_readline();

	if (! read_a_line)
		read_a_line = g_readline;

	push_cmd_src(input_fd, input_from_tty, read_a_line, 0, 0, EXIT_FATAL);

	setbuf(out_fp, (char *) NULL);
	for (cur_srcfile = srcfiles->prev; cur_srcfile != srcfiles;
			cur_srcfile = cur_srcfile->prev) {
		if (cur_srcfile->stype == SRC_FILE
			|| cur_srcfile->stype == SRC_INC)
			break;
	}

	if (cur_srcfile == srcfiles) {
		fprintf(out_fp, _("Can only debug programs provided with the `-f' option.\n"));
		exit(EXIT_FAILURE);
	}

	dgawk_prompt = estrdup(DEFAULT_PROMPT, strlen(DEFAULT_PROMPT));
	dbg_prompt = dgawk_prompt;

	memset(&stop, 0, sizeof(stop));
	stop.command = D_illegal;

	if ((run = getenv("DGAWK_RESTART")) != NULL) {
		/* We are restarting; restore state (breakpoints, history etc.)
		 * passed as environment variables and optionally execute the run command.
		 */
		unserialize_list(BREAK);
		unserialize_list(WATCH);
		unserialize_list(DISPLAY);
		unserialize_list(HISTORY);
		unserialize_list(OPTION);
		unsetenv("DGAWK_RESTART");
		fprintf(out_fp, _("Restarting ...\n"));
		if (strcasecmp(run, "true") == 0)
			(void) do_run(NULL, 0);

	} else if (command_file != NULL) {
		/* run commands from a file (--debug=file  or -D file) */
		int fd;
		fd = open_readfd(command_file);
		if (fd == INVALID_HANDLE) {
			fprintf(stderr, _("cannot open source file `%s' for reading: %s"),
						command_file, strerror(errno));
			exit(EXIT_FAILURE);
		}
		push_cmd_src(fd, false, g_readline, close, 0, EXIT_FAILURE);
		cmd_src->str = estrdup(command_file, strlen(command_file));

	} else {
		int fd;

#ifdef HAVE_LIBREADLINE
		(void) read_history(history_file);
		sess_history_base = history_length;
#endif

		/* read saved options */
		fd = open_readfd(options_file);
		if (fd > INVALID_HANDLE)
			push_cmd_src(fd, false, g_readline, close, 0, EXIT_SUCCESS);
	}

	/* start the command interpreter */
	read_command();	/* yyparse */
	return EXIT_SUCCESS;
}


/* N.B.: ignore breakpoints and watchpoints for return command */

/* check_watchpoint --- check if any watchpoint triggered */

static int
check_watchpoint()
{
	struct list_item *w;

	if (stop.command == D_return)
		return false;
	for (w = watch_list.prev; w != &watch_list; w = w->prev) {
		int wnum = watchpoint_triggered(w);
		if (wnum > 0) {
			stop.watch_point = wnum;
			stop.print_frame = true;
			return true;
		}
	}
	return false;
}

/* check_breakpoint --- check if breakpoint triggered */

static int
check_breakpoint(INSTRUCTION **pi)
{
	INSTRUCTION *pc;

	pc = *pi;
	if (stop.command == D_return)
		return false;
	if (pc->opcode == Op_breakpoint) {
		int bnum;
		*pi = pc->nexti;    /* skip past the breakpoint instruction;
		                     * interpreter doesn't process Op_breakpoint.
		                     */
		bnum = breakpoint_triggered(pc->break_pt);
		if (bnum > 0) {
			stop.break_point = bnum;
			stop.print_frame = true;
			return true;
		}
	}
	return false;
}

/* restart --- restart the debugger */

static void
restart(bool run)
{
	/* save state in the environment after serialization */
	serialize_list(BREAK);
	serialize_list(WATCH);
	serialize_list(DISPLAY);
	serialize_list(HISTORY);
	serialize_list(OPTION);

	/* tell the new process to restore state from the environment */
	setenv("DGAWK_RESTART", (run ? "true" : "false"), 1);

	/* close all open files */
	close_all();

	/* start a new process replacing the current process */
	execvp(d_argv[0], d_argv);

	/* execvp failed !!! */
	fprintf(out_fp, _("Failed to restart debugger"));
	exit(EXIT_FAILURE);
}

/* do_run --- run command */

int
do_run(CMDARG *arg ATTRIBUTE_UNUSED, int cmd ATTRIBUTE_UNUSED)
{
	if (prog_running) {
		if (! input_from_tty)
			need_restart = true;	/* handled later */
		else {
			need_restart = prompt_yes_no(
			         _("Program already running. Restart from beginning (y/n)? "),
			         _("y")[0], false, out_fp);

			if (! need_restart) {
				fprintf(out_fp, _("Program not restarted\n"));
				return false;
			}
		}
	}

	if (need_restart) {
		/* avoid endless cycles of restarting */
		if (command_file != NULL) {
			/* input_from_tty = false */
			fprintf(stderr, _("error: cannot restart, operation not allowed\n"));
			exit(EXIT_FAILURE);
		}

		if (cmd_src->cmd == D_source) {
			/* input_from_tty = false */
			fprintf(out_fp, _("error (%s): cannot restart, ignoring rest of the commands\n"), cmd_src->str);
			pop_cmd_src();
			return false;
		}

		restart(true);	/* does not return */
	}

	fprintf(out_fp, _("Starting program:\n"));

	prog_running = true;
	fatal_tag_valid = 1;
	if (setjmp(fatal_tag) == 0)
		(void) interpret(code_block);

	fatal_tag_valid = 0;
	prog_running = false;
	fprintf(out_fp, (! exiting && exit_val != EXIT_SUCCESS)
				? _("Program exited abnormally with exit value: %d\n")
				: _("Program exited normally with exit value: %d\n"),
			exit_val);
	need_restart = true;
	return false;
}

/* do_quit --- quit command */

int
do_quit(CMDARG *arg ATTRIBUTE_UNUSED, int cmd ATTRIBUTE_UNUSED)
{
	bool terminate = true;
	if (prog_running)
		terminate = prompt_yes_no(
		            _("The program is running. Exit anyway (y/n)? "),
		            _("y")[0], true, out_fp);
	if (terminate) {
		close_all();
		do_trace = false; 	/* don't save 'trace on' */

#ifdef HAVE_LIBREADLINE
		if (do_save_history && input_from_tty) {
			int ret;
			ret = write_history(history_file);
			if (ret == 0 && history_length > history_size)
				(void) history_truncate_file(history_file, history_size);
		}
#endif
		if (do_save_options && input_from_tty)
			save_options(options_file);

		exit(exit_val);
	}
	return false;
}

/* do_continue --- continue command */

int
do_continue(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	BREAKPOINT *b;

	CHECK_PROG_RUNNING();
	if (! arg || arg->type != D_int)
		return true;

	/* arg is breakpoint ignore count if stopped at a breakpoint */
	if (! stop.break_point) {
		fprintf(out_fp, _("Not stopped at any breakpoint; argument ignored.\n"));
		return true;
	}
	b = find_breakpoint(stop.break_point);
	if (b == NULL) {
		d_error(_("invalid breakpoint number %d"), stop.break_point);
		return false;
	}
	b->flags |= BP_IGNORE;
	b->ignore_count = arg->a_int;
	fprintf(out_fp, _("Will ignore next %ld crossings of breakpoint %d.\n"),
				b->ignore_count, stop.break_point);
	return true;
}

/* next_step --- common code for next and step commands */

static int
next_step(CMDARG *arg, int cmd)
{
	CHECK_PROG_RUNNING();
	if (arg != NULL && arg->type == D_int)
		stop.repeat_count = arg->a_int;
	else
		stop.repeat_count = 1;
	stop.command = (enum argtype) cmd;
	return true;
}

/* check_step --- process step command, return true if stopping */

static int
check_step(INSTRUCTION **pi)
{
	if (fcall_count != stop.fcall_count) {
		stop.fcall_count = fcall_count;
		stop.sourceline = sourceline;
		stop.source = source;
		stop.print_frame = true;
		return (--stop.repeat_count == 0);
	}

	if (source != stop.source) {
		stop.source = source;
		stop.sourceline = sourceline;
		return (--stop.repeat_count == 0);
	}

	if (sourceline != stop.sourceline) {
		stop.sourceline = sourceline;
		return (--stop.repeat_count == 0);
	}
	return false;
}

/* do_step -- process step command, return true if stopping */

int
do_step(CMDARG *arg, int cmd)
{
	int ret;
	ret = next_step(arg, cmd);
	if (ret) {
		stop.fcall_count = fcall_count;
		stop.source = source;
		stop.sourceline = sourceline;
		stop.check_func = check_step;
	}
	return ret;
}

/* do_stepi -- process stepi command, return true if stopping */

static int
check_stepi(INSTRUCTION **pi)
{
	return (--stop.repeat_count == 0);
}

/* do_stepi -- stepi command */

int
do_stepi(CMDARG *arg, int cmd)
{
	int ret;
	ret = next_step(arg, cmd);
	if (ret)
		stop.check_func = check_stepi;
	return ret;
}


/* check_next -- process next command returning true if stopping */

static int
check_next(INSTRUCTION **pi)
{
	/* make sure not to step inside function calls */

	if (fcall_count < stop.fcall_count) {
		stop.fcall_count = fcall_count;
		stop.sourceline = sourceline;
		stop.source = source;
		stop.print_frame = true;
		return (--stop.repeat_count == 0);
	}

	if (fcall_count == stop.fcall_count) {
		if (source != stop.source) {
			stop.source = source;
			stop.sourceline = sourceline;
			return (--stop.repeat_count == 0);
		}
		if (sourceline != stop.sourceline) {
			stop.sourceline = sourceline;
			return (--stop.repeat_count == 0);
		}
	}

#if 0
	/* redundant ? */
	if (fcall_count > stop.fcall_count) {
		stop.source = source;
		stop.sourceline = sourceline;
	}
#endif

	return false;
}

/* do_next -- next command */

int
do_next(CMDARG *arg, int cmd)
{
	int ret;

	ret = next_step(arg, cmd);
	if (ret) {
		stop.source = source;
		stop.sourceline = sourceline;
		stop.fcall_count = fcall_count;
		stop.check_func = check_next;
	}
	return ret;
}

/* check_nexti --- process nexti command, returns true if stopping */

static int
check_nexti(INSTRUCTION **pi)
{
	/* make sure not to step inside function calls */

	if (fcall_count < stop.fcall_count) {
		stop.print_frame = true;
		stop.fcall_count = fcall_count;
	}
	return (fcall_count == stop.fcall_count
			&& --stop.repeat_count == 0);
}

/* do_nexti -- nexti command */

int
do_nexti(CMDARG *arg, int cmd)
{
	int ret;

	ret = next_step(arg, cmd);
	if (ret) {
		stop.fcall_count = fcall_count;
		stop.check_func = check_nexti;
	}
	return ret;
}

/* check_finish --- process finish command, returns true if stopping */

static int
check_finish(INSTRUCTION **pi)
{
	if (fcall_count == stop.fcall_count) {
		stop.print_frame = true;
		return true;
	}
	return false;
}

/* do_finish --- finish command */

int
do_finish(CMDARG *arg ATTRIBUTE_UNUSED, int cmd)
{
	CHECK_PROG_RUNNING();
	if (cur_frame == fcall_count) {
		fprintf(out_fp,
			_("'finish' not meaningful in the outermost frame main()\n"));
		return false;
	}
	stop.fcall_count = fcall_count - cur_frame - 1;
	assert(stop.fcall_count >= 0);
	fprintf(out_fp, _("Run until return from "));
	print_numbered_frame(cur_frame);
	stop.check_func = check_finish;
	stop.command = (enum argtype) cmd;
	stop.print_ret = true;
	return true;
}

/* check_return --- process return, returns true if stopping */

static int
check_return(INSTRUCTION **pi)
{
	assert(fcall_count >= stop.fcall_count);

	if (fcall_count == stop.fcall_count) {
		stop.print_frame = true;
		return true;
	}

	if (fcall_count > stop.fcall_count) {	/* innermost frame just returned */
		/* force this one to return too */
		NODE *func;

		func = find_frame(cur_frame)->func_node;
		assert(func != NULL);
		*pi = (func->code_ptr + 1)->lasti;
		/* assert((*pi)->opcode == Op_K_return); */
	}

	return false;
}

/* do_return --- return command */

int
do_return(CMDARG *arg, int cmd)
{
	NODE *func, *n;

	CHECK_PROG_RUNNING();
	func = find_frame(cur_frame)->func_node;
	if (func == NULL) {
		fprintf(out_fp, _("'return' not meaningful in the outermost frame main()\n"));
		return false;
	}

	stop.fcall_count = fcall_count - cur_frame - 1;
	assert(stop.fcall_count >= 0);
	stop.pc = (func->code_ptr + 1)->lasti;
	assert(stop.pc->opcode == Op_K_return);
	stop.command = (enum argtype) cmd;

	stop.check_func = check_return;

	if (arg != NULL && arg->type == D_node)	/* optional return value */
		n = dupnode(arg->a_node);
	else
		n = dupnode(Nnull_string);
	PUSH(n);

	return true;
}

/* check_until --- process until, returns true if stopping */

static int
check_until(INSTRUCTION **pi)
{
	if (fcall_count < stop.fcall_count) { /* current stack frame returned */
		stop.print_frame = true;
		return true;
	} else if (fcall_count == stop.fcall_count) {
		if (stop.pc && *pi == stop.pc)		/* until location */
			return true;
		if (stop.sourceline > 0		/* until */
				&& source == stop.source
				&& sourceline > stop.sourceline)
			return true;
	}
	return false;
}

/* do_until --- until command */

int
do_until(CMDARG *arg, int cmd)
{
	SRCFILE *s = cur_srcfile;
	char *src = cur_srcfile->src;
	int lineno;
	INSTRUCTION *rp, *ip;
	NODE *func;

	CHECK_PROG_RUNNING();
	stop.pc = NULL;
	stop.sourceline = 0;

	if (arg == NULL) {	/* until without argument */

    /* GDB doc.: continue running until a source line past the current line,
     * in the current stack frame, is reached. Is used to avoid single
     * stepping through a loop more than once. ...
     * This means that when you reach the end of a loop after single
     * stepping though it, until makes your program continue execution
     * until it exits the loop. In contrast, a next command at the end
     * of a loop simply steps back to the beginning of the loop, which
     * forces you to step through the next iteration.
     */

		stop.source = source;
		stop.sourceline = sourceline;
		stop.fcall_count = fcall_count - cur_frame;
		stop.check_func = check_until;
		stop.command = (enum argtype) cmd;
		return true;
	}

    /* GDB: until location - continue running program until
     * either the specified location is reached, or the
     * current stack frame returns.
     */

	switch (arg->type) {
	case D_string:	/* until filename : lineno|function */
		s = source_find(arg->a_string);
		arg = arg->next;
		if (s == NULL || arg == NULL
				|| (arg->type != D_int && arg->type != D_func))
			return false;
		src = s->src;
		if (arg->type == D_func)
			goto func;
		/* fall through */
	case D_int:	/* until lineno */
		lineno = arg->a_int;
		if (lineno <= 0 || lineno > s->srclines) {
			d_error(_("line number %d in file `%s' out of range"),
						lineno, src);
			return false;
		}
		break;

	case D_func:	/* until function */
func:
		func = arg->a_node;
		rp = func->code_ptr;
		for (ip = rp->nexti; ip; ip = ip->nexti) {
			if (ip->opcode != Op_breakpoint && ip->source_line > 0) {
				stop.pc = ip;
				stop.fcall_count = fcall_count - cur_frame;
				stop.check_func = check_until;
				stop.command = (enum argtype) cmd;
				return true;
			}
		}
		fprintf(out_fp, _("cannot find specified location in function `%s'\n"),
				func->vname);
		/* fall through */
	default:
		return false;
	}

	if ((rp = find_rule(src, lineno)) == NULL) {
		d_error(_("invalid source line %d in file `%s'"), lineno, src);
		return false;
	}

	for (ip = rp->nexti; ip; ip = ip->nexti) {
		if (ip->opcode != Op_breakpoint && ip->source_line >= lineno) {
			stop.pc = ip;
			stop.fcall_count = fcall_count - cur_frame;
			stop.check_func = check_until;
			stop.command = (enum argtype) cmd;
			return true;
		}
		if (ip == (rp + 1)->lasti)
			break;
	}
	fprintf(out_fp, _("cannot find specified location %d in file `%s'\n"),
				lineno, src);
	return false;
}

/* print_watch_item --- print watched item name, old and current values */

static void
print_watch_item(struct list_item *w)
{
	NODE *symbol, *sub;
	int i;

	symbol = w->symbol;
	if (IS_SUBSCRIPT(w)) {
		fprintf(out_fp, "%s", w->sname);
		for (i = 0; i < w->num_subs; i++) {
			sub = w->subs[i];
			fprintf(out_fp, "[\"%.*s\"]", (int) sub->stlen, sub->stptr);
		}
		fprintf(out_fp, "\n");
	} else if (IS_FIELD(w))
		fprintf(out_fp, "$%ld\n", get_number_si(symbol));
	else
		fprintf(out_fp, "%s\n", w->sname);


#define print_value(X, S, V)                                        \
if (X)                                                              \
	fprintf(out_fp, "array, %ld elements\n", w->S);                 \
else if (! w->V)                                                    \
	fprintf(out_fp, IS_SUBSCRIPT(w) ?                               \
			_("element not in array\n") : _("untyped variable\n")); \
else                                                                \
	valinfo(w->V, fprintf, out_fp);

	fprintf(out_fp, "  Old value: ");
	print_value((w->flags & OLD_IS_ARRAY) != 0, old_size, old_value);
	fprintf(out_fp, "  New value: ");
	print_value((w->flags & CUR_IS_ARRAY) != 0, cur_size, cur_value);

#undef print_value
}

/* next_command --- (optionally) print stoppage location and reason;
 *                  also fetch next debug command from the user.
 */

static void
next_command()
{
	static int last_rule = 0;
	struct list_item *d = NULL, *w = NULL;
	BREAKPOINT *b = NULL;
	SRCFILE *s;

	if (source == NULL) {
		stop.command = D_illegal;
		stop.check_func = NULL;
		return;
	}

	if (stop.break_point) {
		b = find_breakpoint(stop.break_point);
		assert(b != NULL);
		if (b->silent)
			goto no_output;
	} else if (stop.watch_point) {
		w = find_item(&watch_list, stop.watch_point);
		if (w->silent)
			goto no_output;
	}

	if (cur_rule != last_rule) {
		fprintf(out_fp, _("Stopping in %s ...\n"), ruletab[cur_rule]);
		last_rule = cur_rule;
	}

	if (b != NULL)
		fprintf(out_fp, "Breakpoint %d, ", b->number);
	else if (w != NULL) {
		fprintf(out_fp, "Watchpoint %d: ", w->number);
		print_watch_item(w);
	}

	/* frame info */
	if (stop.print_frame) {
		print_frame(frame_ptr->func_node, source, sourceline);
		fprintf(out_fp, "\n");
		stop.print_frame = false;
	}

	(void) print_lines(source, sourceline, 1);

	/* automatic display of variables */
	for (d = display_list.prev; d != &display_list; d = d->prev)
		display(d);

no_output:
	/* update last_printed_line, so that  output of 'list' is
	 * centered around current sourceline
	 */

	last_printed_line = sourceline - list_size / 2;
	if (last_printed_line < 0)
		last_printed_line = 0;

	/* update current source file */
	s = source_find(source);
	if (cur_srcfile != s) {
		if (cur_srcfile->fd != INVALID_HANDLE) {
			close(cur_srcfile->fd);
			cur_srcfile->fd = INVALID_HANDLE;
		}
		cur_srcfile = s;
	}

	stop.command = D_illegal;
	stop.check_func = NULL;

	if (b != NULL) {
		int ret;
		ret = execute_commands(&b->commands);
		if ((b->flags & BP_TEMP) != 0)
			delete_breakpoint(b);
		if (ret)	/* resume execution */
			return;
	} else if (w != NULL && execute_commands(&w->commands))
		return;

	read_command();		/* zzparse */
}

/* debug_post_execute --- post_hook in the interpreter */

static void
debug_post_execute(INSTRUCTION *pc)
{
	if (! in_main_context())
		return;

	switch (pc->opcode) {
	case Op_K_next:
	case Op_K_nextfile:
	case Op_K_exit:
		if (stop.command == D_finish) {
			/* cancel finish command */
			stop.print_ret = false;
			stop.print_frame = false;
			stop.command = D_illegal;
			stop.check_func = NULL;
			fprintf(out_fp, _("'finish' not meaningful with non-local jump '%s'\n"),
							op2str(pc->opcode));
		} else if (stop.command == D_until) {
			/* cancel until command */
			stop.print_frame = false;
			stop.command = D_illegal;
			stop.check_func = NULL;
			fprintf(out_fp, _("'until' not meaningful with non-local jump '%s'\n"),
							op2str(pc->opcode));
		}
		break;

	case Op_K_return:
		if (stop.command == D_finish
				&& fcall_count == stop.fcall_count
				&& stop.print_ret
		) {
			NODE *r;
			/* print the returned value before it disappears. */
			r = TOP();
			fprintf(out_fp, "Returned value = ");
			valinfo(r, fprintf, out_fp);
			stop.print_ret = false;
		}
		break;

	case Op_newfile:
	case Op_get_record:
		return;

	default:
		break;
	}
}

/* debug_pre_execute --- pre_hook, called by the interpreter before execution;
 *                 checks if execution needs to be suspended and control
 *                 transferred to the debugger.
 */

static int
debug_pre_execute(INSTRUCTION **pi)
{
	static bool cant_stop = false;
	NODE *m;

	if (! in_main_context())
		return pre_execute_code(pi);

	cur_pc = *pi;
	stop.break_point = 0;
	stop.watch_point = 0;
	cur_frame = 0;

	if (do_trace
		&& cur_pc->opcode != Op_breakpoint
		&& stop.command != D_return
	)
		print_instruction(cur_pc, fprintf, out_fp, false);

/* N.B.: For Op_field_spec_lhs must execute instructions upto Op_field_assign
 * as a group before stopping. Otherwise, watch/print of field variables
 * yield surprising results. Ditto for Op_push_lhs for special variables
 * (upto Op_var_assign, the set_FOO routine).
 */

	switch (cur_pc->opcode) {
	case Op_field_spec_lhs:
		cant_stop = true;
		break;

	case Op_field_assign:
		cant_stop = false;
		return true; /* may stop at next instruction */

	case Op_push_lhs:
		m = cur_pc->memory;
		if (m->type == Node_var && m->var_assign)
			cant_stop = true;
		break;

	case Op_arrayfor_incr:	/* can have special var as array variable !!! */
		m = cur_pc->array_var;
		if (m->type == Node_var && m->var_assign)
			cant_stop = true;
		break;

	case Op_var_assign:
		cant_stop = false;
		return true; /* may stop at next instruction */

	case Op_rule:
		cur_rule = cur_pc->in_rule;
		return true;

	case Op_func:
	case Op_var_update:
		return true;

	case Op_breakpoint:
		break;	/* processed later in check_breakpoint() */

	default:
		if (cur_pc->source_line <= 0)
			return true;
		break;
	}

	if (cant_stop)
		return true;

	assert(sourceline > 0);

	/*
	 * 11/2015: This used to check breakpoints first, but that could
	 * produce strange behavior, where a watchpoint doesn't print until
	 * some time after the data changed.  This reworks things so that
	 * watchpoints are checked first. It's a bit of a hack, but
	 * the behavior for the user is more logical.
	 */
	if (check_watchpoint()) {
		next_command();	/* return to debugger interface */
		if (stop.command == D_return)
			*pi = stop.pc;	/* jump to this instruction */
		else if (cur_pc->opcode == Op_breakpoint)
			cur_pc = cur_pc->nexti;    /* skip past the breakpoint instruction */
	} else if (check_breakpoint(pi)
			|| (stop.check_func && stop.check_func(pi))) {
		next_command();	/* return to debugger interface */
		if (stop.command == D_return)
			*pi = stop.pc;	/* jump to this instruction */
	}

	/* if cur_pc == *pi, interpreter executes cur_pc;
	 * Otherwise, jumps to instruction *pi.
	 */
	return (cur_pc == *pi);
}

/* print_memory --- print a scalar value */

static void
print_memory(NODE *m, NODE *func, Func_print print_func, FILE *fp)
{
	switch (m->type) {
	case Node_val:
		if (m == Nnull_string)
			print_func(fp, "Nnull_string");
		else if ((m->flags & NUMBER) != 0) {
#ifdef HAVE_MPFR
			if ((m->flags & MPFN) != 0)
				print_func(fp, "%s", mpg_fmt("%R*g", ROUND_MODE, m->mpg_numbr));
			else if ((m->flags & MPZN) != 0)
				print_func(fp, "%s", mpg_fmt("%Zd", m->mpg_i));
			else
#endif
				print_func(fp, "%g", m->numbr);
		} else if ((m->flags & STRING) != 0)
			pp_string_fp(print_func, fp, m->stptr, m->stlen, '"', false);
		else if ((m->flags & REGEX) != 0) {
			print_func(fp, "@");
			pp_string_fp(print_func, fp, m->stptr, m->stlen, '/', false);
		} else
			print_func(fp, "-?-");
		print_func(fp, " [%s]", flags2str(m->flags));
		break;

	case Node_regex:
		pp_string_fp(print_func, fp, m->re_exp->stptr, m->re_exp->stlen, '/', false);
		break;

	case Node_dynregex:
		break;

	case Node_param_list:
		assert(func != NULL);
		print_func(fp, "%s", func->fparms[m->param_cnt].param);
		break;

	case Node_var:
	case Node_var_new:
	case Node_var_array:
		print_func(fp, "%s", m->vname);
		break;

	case Node_elem_new:
		print_func(fp, "element - %p", m);
		break;

	default:
		print_func(fp, "?");  /* can't happen */
	}
}

/* print_instruction --- print a bytecode */

static void
print_instruction(INSTRUCTION *pc, Func_print print_func, FILE *fp, int in_dump)
{
	int pcount = 0;
	static NODE *func = NULL;
	static int noffset = 0;

	if (noffset == 0) {
		static char buf[50];
		/* offset for 2nd to last lines in a multi-line output */
		noffset = sprintf(buf, "[      :" PTRFMT "] %-20.20s: ", (void *) pc,
				opcode2str(pc->opcode));
	}

	if (pc->opcode == Op_func) {
		func = pc->func_body;
		pcount = func->param_cnt;
		if (in_dump) {
			int j;
			print_func(fp, "\n\t# Function: %s (", func->vname);
			for (j = 0; j < pcount; j++) {
				print_func(fp, "%s", func->fparms[j].param);
				if (j < pcount - 1)
					print_func(fp, ", ");
			}
			print_func(fp, ")\n\n");
		}
	} else if (pc->opcode == Op_rule) {
		if (in_dump)
			print_func(fp, "\n\t# %s\n\n", ruletab[pc->in_rule]);
	}

	if (pc->opcode == Op_newfile)
		print_func(fp, "\n");

	if (pc->source_line <= 0)
		print_func(fp, "[      :" PTRFMT "] %-20.20s: ", pc, opcode2str(pc->opcode));
	else
		print_func(fp, "[%6d:" PTRFMT "] %-20.20s: ",
		                pc->source_line, pc, opcode2str(pc->opcode));

	if (prog_running && ! in_dump) {
		/* find Node_func if in function */
		func = find_frame(0)->func_node;
	}


	switch (pc->opcode) {
	case Op_K_if:
		print_func(fp, "[branch_if = " PTRFMT "] [branch_else = " PTRFMT "] [branch_else->lasti = " PTRFMT "]\n",
				pc->branch_if, pc->branch_else, pc->branch_else->lasti);
		break;

	case Op_K_else:
		print_func(fp, "[branch_end = " PTRFMT "]\n", pc->branch_end);
		break;

	case Op_K_while:
		print_func(fp, "[while_body = " PTRFMT "] [target_break = " PTRFMT "]\n", (pc+1)->while_body, pc->target_break);
		break;

	case Op_K_do:
		print_func(fp, "[doloop_cond = " PTRFMT "] [target_break = " PTRFMT "]", (pc+1)->doloop_cond, pc->target_break);
		if (pc->comment)
			print_func(fp, " [comment = " PTRFMT "]", pc->comment);
		print_func(fp, "\n");
		if (pc->comment)
			print_instruction(pc->comment, print_func, fp, in_dump);
		break;

	case Op_K_for:
		print_func(fp, "[forloop_cond = " PTRFMT "] ", (pc+1)->forloop_cond);
		/* fall through */
	case Op_K_arrayfor:
		print_func(fp, "[forloop_body = " PTRFMT "] ", (pc+1)->forloop_body);
		print_func(fp, "[target_break = " PTRFMT "] [target_continue = " PTRFMT "]", pc->target_break, pc->target_continue);
		if (pc->comment != NULL) {
			print_func(fp, " [comment = " PTRFMT "]\n", (pc)->comment);
			print_instruction(pc->comment, print_func, fp, in_dump);
		} else
			print_func(fp, "\n");
		break;

	case Op_K_switch:
	{
		bool need_newline = false;
		print_func(fp, "[switch_start = " PTRFMT "] [switch_end = " PTRFMT "]\n", (pc+1)->switch_start, (pc+1)->switch_end);
		if (pc->comment || (pc+1)->switch_end->comment)
			print_func(fp, "%*s", noffset, "");
		if (pc->comment) {
			print_func(fp, "[start_comment = " PTRFMT "]", pc->comment);
			need_newline = true;
		}
		if ((pc+1)->switch_end->comment) {
			print_func(fp, "[end_comment = " PTRFMT "]", (pc + 1)->switch_end->comment);
			need_newline = true;
		}
		if (need_newline)
			print_func(fp, "\n");
		if (pc->comment)
			print_instruction(pc->comment, print_func, fp, in_dump);
		if ((pc+1)->switch_end->comment)
			print_instruction((pc+1)->switch_end->comment, print_func, fp, in_dump);
	}
		break;

	case Op_K_default:
		print_func(fp, "[stmt_start = " PTRFMT "] [stmt_end = " PTRFMT "]", pc->stmt_start, pc->stmt_end);
		if (pc->comment) {
			print_func(fp, " [comment = " PTRFMT "]\n", pc->comment);
			print_instruction(pc->comment, print_func, fp, in_dump);
		} else
			print_func(fp, "\n");
		break;

	case Op_var_update:
		print_func(fp, "[update_%s()]\n", get_spec_varname(pc->update_var));
		break;

	case Op_var_assign:
		print_func(fp, "[set_%s()]", get_spec_varname(pc->assign_var));
		if (pc->assign_ctxt != 0)
			print_func(fp, " [assign_ctxt = %s]", opcode2str((OPCODE) pc->assign_ctxt));
		print_func(fp, "\n");
		break;

	case Op_field_assign:
		print_func(fp, "[%s]\n", pc->field_assign == reset_record ?
					"reset_record()" : "invalidate_field0()");
		break;

	case Op_field_spec_lhs:
		print_func(fp, "[target_assign = " PTRFMT "] [do_reference = %s]\n",
				pc->target_assign, pc->do_reference ? "true" : "false");
		break;

	case Op_func:
		print_func(fp, "[param_cnt = %d] [source_file = %s]", pcount,
				pc->source_file ? pc->source_file : "cmd. line");
		if (pc[3].nexti != NULL) {
			print_func(fp, "[ns_list = " PTRFMT "]\n", pc[3].nexti);
			print_ns_list(pc[3].nexti, print_func, fp, in_dump);
		} else
			print_func(fp, "\n");
		break;

	case Op_K_getline_redir:
		print_func(fp, "[into_var = %s] [redir_type = \"%s\"]\n",
		                pc->into_var ? "true" : "false",
		                redir2str(pc->redir_type));
		break;

	case Op_K_getline:
		print_func(fp, "[into_var = %s]\n", pc->into_var ? "true" : "false");
		print_func(fp, "%*s[target_beginfile = " PTRFMT "] [target_endfile = " PTRFMT "]\n",
		                noffset, "",
		                (pc + 1)->target_beginfile, (pc + 1)->target_endfile);
		break;

	case Op_K_print_rec:
		print_func(fp, "[redir_type = \"%s\"]\n", redir2str(pc->redir_type));
		break;

	case Op_K_print:
	case Op_K_printf:
		print_func(fp, "[expr_count = %ld] [redir_type = \"%s\"]\n",
		                pc->expr_count, redir2str(pc->redir_type));
		break;

	case Op_indirect_func_call:
	case Op_func_call:
		print_func(fp, "[func_name = %s] [arg_count = %ld]\n",
		                pc->func_name, (pc + 1)->expr_count);
		break;

	case Op_K_nextfile:
		print_func(fp, "[target_newfile = " PTRFMT "] [target_endfile = " PTRFMT "]\n",
		                pc->target_newfile, pc->target_endfile);
		break;

	case Op_newfile:
		print_func(fp, "[target_jmp = " PTRFMT "] [target_endfile = " PTRFMT "]\n",
		                pc->target_jmp, pc->target_endfile);
		print_func(fp, "%*s[target_get_record = " PTRFMT "]\n",
		                noffset, "", (pc + 1)->target_get_record);
		break;

	case Op_get_record:
		print_func(fp, "[target_newfile = " PTRFMT "]\n", pc->target_newfile);
		break;

	case Op_jmp:
	case Op_jmp_false:
	case Op_jmp_true:
	case Op_and:
	case Op_or:
	case Op_K_next:
	case Op_arrayfor_init:
	case Op_K_break:
	case Op_K_continue:
		print_func(fp, "[target_jmp = " PTRFMT "]\n", pc->target_jmp);
		break;

	case Op_K_exit:
		print_func(fp, "[target_end = " PTRFMT "] [target_atexit = " PTRFMT "]\n",
						pc->target_end, pc->target_atexit);
		break;

	case Op_K_case:
		print_func(fp, "[target_jmp = " PTRFMT "] [match_exp = %s]",
						pc->target_jmp,	(pc + 1)->match_exp ? "true" : "false");
		if (pc->comment) {
			print_func(fp, " [comment = " PTRFMT "]\n", pc->comment);
			print_instruction(pc->comment, print_func, fp, in_dump);
		} else
			print_func(fp, "\n");
		break;

	case Op_K_namespace:
		print_func(fp, "[namespace = %s]", pc->ns_name);
		if (pc->nexti)
			print_func(fp, "[nexti = " PTRFMT "]", pc->nexti);
		if (pc->comment)
			print_func(fp, "[comment = " PTRFMT "]", pc->comment);
		print_func(fp, "\n");
		break;

	case Op_arrayfor_incr:
		print_func(fp, "[array_var = %s] [target_jmp = " PTRFMT "]\n",
		                pc->array_var->type == Node_param_list ?
		                   func->fparms[pc->array_var->param_cnt].param : pc->array_var->vname,
		                pc->target_jmp);
		break;

	case Op_line_range:
		print_func(fp, "[triggered = %ld] [target_jmp = " PTRFMT "]\n",
		                pc->triggered, pc->target_jmp);
		break;

	case Op_cond_pair:
		print_func(fp, "[line_range = " PTRFMT "] [target_jmp = " PTRFMT "]\n",
		                pc->line_range, pc->target_jmp);
		break;

	case Op_sub_builtin:
	{
		const char *fname = "sub";
		static const struct flagtab values[] = {
			{ GSUB, "GSUB" },
			{ GENSUB, "GENSUB" },
			{ LITERAL, "LITERAL" },
			{ 0, NULL }
		};

		if ((pc->sub_flags & GSUB) != 0)
			fname = "gsub";
		else if ((pc->sub_flags & GENSUB) != 0)
			fname = "gensub";
		print_func(fp, "%s [arg_count = %ld] [sub_flags = %s]\n",
				fname, pc->expr_count,
				genflags2str(pc->sub_flags, values));
	}
		break;

	case Op_builtin:
		print_func(fp, "%s [arg_count = %ld]\n", getfname(pc->builtin, false),
						pc->expr_count);
		break;

	case Op_ext_builtin:
		print_func(fp, "%s [arg_count = %ld]\n", (pc + 1)->func_name,
						pc->expr_count);
		break;

	case Op_subscript:
	case Op_sub_array:
		print_func(fp, "[sub_count = %ld]\n", pc->sub_count);
		break;

	case Op_store_sub:
		print_memory(pc->memory, func, print_func, fp);
		print_func(fp, " [sub_count = %ld]\n", pc->expr_count);
		break;

	case Op_subscript_lhs:
		print_func(fp, "[sub_count = %ld] [do_reference = %s]\n",
		                pc->sub_count,
		                pc->do_reference ? "true" : "false");
		break;

	case Op_K_delete:
	case Op_in_array:
		print_func(fp, "[expr_count = %ld]\n", pc->expr_count);
		break;

	case Op_concat:
		/* NB: concat_flag CSVAR only used in grammar, don't display it */
		print_func(fp, "[expr_count = %ld] [concat_flag = %s]\n",
						pc->expr_count,
						(pc->concat_flag & CSUBSEP) != 0 ? "CSUBSEP" : "0");
		break;

	case Op_rule:
		print_func(fp, "[in_rule = %s] [source_file = %s]",
		                ruletab[pc->in_rule],
		                pc->source_file ? pc->source_file : "cmd. line");
		if (pc[3].nexti != NULL) {
			print_func(fp, "[ns_list = " PTRFMT "]\n", pc[3].nexti);
			print_ns_list(pc[3].nexti, print_func, fp, in_dump);
		} else
			print_func(fp, "\n");
		break;

	case Op_lint:
	{
		static const char *const linttypetab[] = {
			"LINT_illegal",
			"LINT_assign_in_cond",
			"LINT_no_effect"
		};
		print_func(fp, "[lint_type = %s]\n", linttypetab[pc->lint_type]);
	}
		break;

	case Op_exec_count:
		print_func(fp, "[exec_count = " EXEC_COUNT_FMT "]\n", pc->exec_count);
		break;

 	case Op_store_var:
		print_memory(pc->memory, func, print_func, fp);
		if (pc->initval != NULL) {
			print_func(fp, " = ");
			print_memory(pc->initval, func, print_func, fp);
		}
		print_func(fp, "\n");
		break;

	case Op_push_lhs:
		print_memory(pc->memory, func, print_func, fp);
		print_func(fp, " [do_reference = %s]\n",
		                pc->do_reference ? "true" : "false");
		break;

	case Op_comment:
		print_memory(pc->memory, func, print_func, fp);
		print_func(fp, " [comment_type = %s]",
			pc->memory->comment_type == EOL_COMMENT ?
						"EOL" : "BLOCK");
		if (pc->comment) {
			print_func(fp, " [comment = " PTRFMT "]\n", pc->comment);
			print_instruction(pc->comment, print_func, fp, in_dump);
		} else
			print_func(fp, "\n");
		break;

	case Op_push_i:
	case Op_push:
	case Op_push_arg:
	case Op_push_arg_untyped:
	case Op_push_param:
	case Op_push_array:
	case Op_push_re:
	case Op_match_rec:
	case Op_match:
	case Op_nomatch:
	case Op_plus_i:
	case Op_minus_i:
	case Op_times_i:
	case Op_exp_i:
	case Op_quotient_i:
	case Op_mod_i:
	case Op_assign_concat:
		print_memory(pc->memory, func, print_func, fp);
		/* fall through */
	default:
		print_func(fp, "\n");
		break;
	}
}

/* do_trace_instruction --- trace command */

int
do_trace_instruction(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	if (arg != NULL && arg->type == D_argument
			&& arg->a_argument == A_TRACE_ON)
		do_trace = true;
	else
		do_trace = false;
	return false;
}

/* print_code --- print a list of instructions */

static int
print_code(INSTRUCTION *pc, void *x)
{
	struct pf_data *data = (struct pf_data *) x;
	for (; pc != NULL; pc = pc->nexti)
		print_instruction(pc, data->print_func, data->fp, data->defn /* in_dump */);
	return 0;
}

/* print_ns_list --- print the list of namespaces */

static void
print_ns_list(INSTRUCTION *pc, Func_print print_func, FILE *fp, int in_dump)
{
	for (; pc != NULL; pc = pc->nexti) {
		print_instruction(pc, print_func, fp, in_dump);
		if (pc->comment != NULL)
			print_instruction(pc->comment, print_func, fp, in_dump);
	}
}

/* do_dump_instructions --- dump command */

int
do_dump_instructions(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	FILE *fp;
  	NODE **funcs;

	if (arg != NULL && arg->type == D_string) {
		/* dump to a file */
		if ((fp = fopen(arg->a_string, "w")) == NULL) {
			d_error(_("could not open `%s' for writing: %s"),
					arg->a_string, strerror(errno));
			return false;
		}
		pf_data.print_func = fprintf;
		pf_data.fp = fp;
		pf_data.defn = true;	/* in_dump = true */
		(void) print_code(code_block, &pf_data);
		funcs = function_list(true);
		(void) foreach_func(funcs,
		                     (int (*)(INSTRUCTION *, void *)) print_code,
		                     &pf_data);
		efree(funcs);
		fclose(fp);
		return false;
	}

	funcs = function_list(true);
	initialize_pager(out_fp);
	if (setjmp(pager_quit_tag) == 0) {
		pf_data.print_func = gprintf;
		pf_data.fp = out_fp;
		pf_data.defn = true;	/* in_dump = true */
		(void) print_code(code_block, &pf_data);
		(void) foreach_func(funcs,
		                    (int (*)(INSTRUCTION *, void *)) print_code,
		                     &pf_data);
	}
	efree(funcs);
	return false;
}

/* do_save --- save command */

int
do_save(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
#if defined(HAVE_LIBREADLINE) && defined(HAVE_HISTORY_LIST)
	FILE *fp;
	HIST_ENTRY **hist_list;
	int i;

	if ((fp = fopen(arg->a_string, "w")) == NULL) {
		d_error(_("could not open `%s' for writing: %s"),
				arg->a_string, strerror(errno));
		return false;
	}

	hist_list = history_list();
	if (hist_list && history_length > sess_history_base) {
		for (i = sess_history_base; hist_list[i] != NULL; i++) {
			char *line;
			line = hist_list[i]->line;

			/* exclude save commands;
			 * N.B.: this test may fail if there is another
			 * command with the same first 2 letters.
			 */

			if (strlen(line) > 1
			    && strncmp(line, "sa", 2) == 0)
				continue;

			fprintf(fp, "%s\n", line);
		}
	}
	fclose(fp);
#endif
	return false;
}

/* do_option --- option command */

int
do_option(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	const struct dbg_option *opt;
	char *name, *value;

	if (arg == NULL) {	/* display all available options and corresponding values */
		for (opt = option_list; opt->name; opt++) {
			if (opt->str_val != NULL)
				fprintf(out_fp, "%s = \"%s\"\n", opt->name, *(opt->str_val));
			else
				fprintf(out_fp, "%s = %d\n", opt->name, *(opt->num_val));
		}
		return false;
	}

	name = arg->a_string;
	arg = arg->next;
	value = arg ? arg->a_string : NULL;

	for (opt = option_list; opt->name; opt++) {	/* linear search */
		if (strcmp(name, opt->name) == 0)
			break;
	}
	if (! opt->name)
		return false;

	if (value == NULL) {	/* display current setting */
		if (opt->str_val != NULL)
			fprintf(out_fp, "%s = \"%s\"\n", opt->name, *(opt->str_val));
		else
			fprintf(out_fp, "%s = %d\n", opt->name, *(opt->num_val));
	} else
		(*(opt->assign))(value);
	return false;
}


#ifdef HAVE_LIBREADLINE

/* initialize_pager --- initialize our idea of the terminal size */

void
initialize_pager(FILE *fp)
{
	if (! os_isatty(fileno(fp)) || ! input_from_tty || input_fd != 0) {
		screen_width = INT_MAX;
		screen_height = INT_MAX;
	} else {
		/* get the terminal size from readline. */

		rl_reset_terminal(NULL); /* N.B.: NULL argument means
		                          * "use TERM env variable for terminal name".
		                          */
		rl_get_screen_size(&screen_height, &screen_width);
		if (screen_height <= 1)
			screen_height = INT_MAX;
		if (screen_width <= 1)
			screen_width = INT_MAX;
	}
	pager_lines_printed = 0;
}
#endif

static void
prompt_continue(FILE *fp)
{
	bool quit_pager = false;

	if (os_isatty(fileno(fp)) && input_fd == 0)
		quit_pager = prompt_yes_no(
			// TRANSLATORS: don't translate the 'q' inside the brackets.
	                _("\t------[Enter] to continue or [q] + [Enter] to quit------"),
	                'q', false, fp);
	if (quit_pager)
		longjmp(pager_quit_tag, 1);
	pager_lines_printed = 0;
}

/* gprintf --- like fprintf but allows paging */

int
gprintf(FILE *fp, const char *format, ...)
{
	va_list args;
	static char *buf = NULL;
	static size_t buflen = 0;
	static int bl = 0;
	char *p, *q;
	int nchar;

#define GPRINTF_BUFSIZ 512
	if (buf == NULL) {
		buflen = GPRINTF_BUFSIZ;
		emalloc(buf, char *, buflen * sizeof(char), "gprintf");
	} else if (buflen - bl < GPRINTF_BUFSIZ/2) {
		buflen += GPRINTF_BUFSIZ;
		erealloc(buf, char *, buflen * sizeof(char), "gprintf");
	}
#undef GPRINTF_BUFSIZ

	while (true) {
		va_start(args, format);
		nchar = vsnprintf(buf + bl, buflen - bl, format, args);
		va_end(args);
		if (nchar == 0)
			return 0;
		if (nchar > 0 && nchar < buflen - bl) {
			bl += nchar;
			if (buf[bl-1] != '\n') /* buffer output until see a newline at end */
				return nchar;
			break;
		}

		/* enlarge buffer, and try again */
		buflen *= 2;
		erealloc(buf, char *, buflen * sizeof(char), "gprintf");
	}

	bl = 0;
	for (p = buf; (q = strchr(p, '\n')) != NULL; p = q + 1) {
		int sz = (int) (q - p);

		while (sz > 0) {
			int cnt;
			cnt = sz > screen_width ? screen_width : sz;

			/* do not print partial line before scrolling */
			if (cnt < sz && (pager_lines_printed == (screen_height - 2)))
				prompt_continue(fp);

			if (fwrite(p, sizeof(char), cnt, fp) != cnt)
				return -1;
			if (cnt == sz)
				break;
			else {
				if (++pager_lines_printed == (screen_height - 1))
					prompt_continue(fp);
				sz -= screen_width;
				assert(sz > 0);
				p += cnt;
			}
		}

		fprintf(fp, "\n");
		if (++pager_lines_printed == (screen_height - 1))
			prompt_continue(fp);
		p++;
	}
	return nchar;
}


static int
serialize_subscript(char *buf, int buflen, struct list_item *item)
{
	int bl, nchar, i;
	NODE *sub;

	nchar = snprintf(buf, buflen, "%d%c%d%c%s%c%d%c",
				item->number, FSEP, D_subscript, FSEP, item->sname, FSEP,
				item->num_subs, FSEP);
	if (nchar <= 0)
		return 0;
	else if (nchar >= buflen)	/* need larger buffer */
		return nchar;
 	bl = nchar;
	for (i = 0; i < item->num_subs; i++) {
		sub = item->subs[i];
		nchar = snprintf(buf + bl, buflen - bl, "%lu%c%.*s%c",
					(unsigned long) sub->stlen, FSEP,
					(int) sub->stlen, sub->stptr, FSEP);
		if (nchar <= 0)
			return 0;
		bl += nchar;
		if (bl >= buflen)	/* need larger buffer */
			return bl;
	}
	return bl;
}


/*
 * serialize_list--- convert a list structure to a byte stream and
 *               save in environment.
 */

static void
serialize_list(int type)
{
	static char *buf = NULL;
	static int buflen = 0;
	int bl;
	BREAKPOINT *b = NULL;
	struct list_item *wd = NULL;
	HIST_ENTRY **hist_list = NULL;
	int hist_index = 0;
	struct dbg_option *opt = NULL;
	struct commands_item *commands = NULL, *c;
	int cnum = 0;
	struct condition *cndn = NULL;
	void *ptr, *end_ptr;
#ifdef HAVE_LIBREADLINE
	HIST_ENTRY *h = NULL;
#endif

	switch (type) {
	case BREAK:
		end_ptr = (void *) &breakpoints;
		ptr = (void *) breakpoints.prev;
		break;
	case WATCH:
		end_ptr = (void *) &watch_list;
		ptr = (void *) watch_list.prev;
		break;
	case DISPLAY:
		end_ptr = (void *) &display_list;
		ptr = (void *) display_list.prev;
		break;
	case HISTORY:
		hist_list = history_list();
		if (hist_list == NULL) /* empty history list */
			return;
		end_ptr = NULL;
		ptr = (void *) hist_list[0];
		break;
	case OPTION:
	{
		int n;
		n = sizeof(option_list)/sizeof(option_list[0]);
		end_ptr = (void *) &option_list[n - 1];
		ptr = (void *) option_list;
	}
		break;

	default:
		return;
	}

	if (type != HISTORY && ptr == end_ptr)		/* empty list */
		return;

#define SERIALIZE_BUFSIZ 512

	if (buf == NULL) {	/* first time */
		buflen = SERIALIZE_BUFSIZ;
		emalloc(buf, char *, buflen + 1, "serialize");
	}
	bl = 0;

	while (ptr != end_ptr) {
		int nchar = 0;
		if (buflen - bl < SERIALIZE_BUFSIZ/2) {
enlarge_buffer:
			buflen *= 2;
			erealloc(buf, char *, buflen + 1, "serialize");
		}

#undef SERIALIZE_BUFSIZ

		/* field seperator is FSEP ('\037'), and the record separator is RSEP ('\036') */

		switch (type) {
		case BREAK:
			b = (BREAKPOINT *) ptr;

			/* src source_line flags ignore_count hit_count number;
			 * commands and condition processed later in the end switch
			 */

			nchar = snprintf(buf + bl, buflen - bl,
			                 "%s%c%d%c%d%c%d%c%d%c%d%c",
			                 b->src, FSEP, b->bpi->source_line, FSEP, b->flags, FSEP,
			                 (int) b->ignore_count, FSEP,
			                 (int) b->hit_count, FSEP, b->number, FSEP);
			cnum = b->number;
			commands = &b->commands;
			cndn = &b->cndn;
			break;
		case DISPLAY:
		case WATCH:
			wd = (struct list_item *) ptr;

			/* subscript    -- number type sname num_subs subs(stlen + stptr) [commands [condition]]
			 * variable     -- number type sname [commands [condition]]
			 * field        -- number type symbol(numbr) [commands [condition]]
			 */

			if (IS_PARAM(wd))	/* exclude parameters */
				nchar = 0;
			else if (IS_SUBSCRIPT(wd))
				nchar = serialize_subscript(buf + bl, buflen - bl, wd);
			else if (IS_FIELD(wd))
				nchar = snprintf(buf + bl, buflen - bl, "%d%c%d%c%d%c",
				            wd->number, FSEP, D_field, FSEP, (int) get_number_si(wd->symbol), FSEP);
			else
				nchar = snprintf(buf + bl, buflen - bl, "%d%c%d%c%s%c",
				            wd->number, FSEP, D_variable, FSEP, wd->sname, FSEP);
			cnum = wd->number;
			commands = &wd->commands;
			cndn = &wd->cndn;
			break;
		case HISTORY:
#if defined(HAVE_LIBREADLINE) && defined(HAVE_HISTORY_LIST)
			h = (HIST_ENTRY *) ptr;
			nchar = strlen(h->line);
			if (nchar >= buflen - bl)
				goto enlarge_buffer;
			strcpy(buf + bl, h->line);
#endif
			break;
		case OPTION:
			opt = (struct dbg_option *) ptr;
			if (opt->num_val != NULL)
				nchar = snprintf(buf + bl, buflen - bl,
								"%s%c%d%c", opt->name, FSEP, *(opt->num_val), FSEP);
			else
				nchar = snprintf(buf + bl, buflen - bl,
								"%s%c%s%c", opt->name, FSEP, *(opt->str_val), FSEP);
			break;
		default:
			break;
		}

		if (nchar == 0)	/* skip empty history lines etc.*/
			;
		else if (nchar > 0 && nchar  < buflen - bl) {
			bl += nchar;
			buf[bl] = RSEP;	/* record */
			buf[++bl] = '\0';
		} else
			goto enlarge_buffer;

		switch (type) {
		case BREAK:
		case WATCH:
			/* recreate the `commands' command strings including the `commands'
			 * and `end' commands; command seperator is '\034'.
			 * re-parsed in unserialize_list to recover the commands list.
			 * Alternatively, one could encode(serialize) each command and it's arguments.
			 */

			bl--;	/* undo RSEP from above */

			/* compute required room in buffer */
			nchar = 0;
			for (c = commands->next; c != commands; c = c->next) {
				nchar += (strlen(c->cmd_string) + 1);
				if (c->cmd == D_eval) {
					CMDARG *a = c->arg;
					nchar += (strlen(a->a_string) + 1);	/* awk statements */
					nchar += (strlen("end") + 1);
				}
			}

			if (nchar > 0) {	/* non-empty commands list */
				nchar += (strlen("commands ") + 20 /*cnum*/ + 1 /*CSEP*/ + strlen("end") + 1 /*FSEP*/);
				if (nchar >= buflen - bl) {
					buflen = bl + nchar + 1 /*RSEP*/;
					erealloc(buf, char *, buflen + 1, "serialize_list");
				}
				nchar = sprintf(buf + bl, "commands %d", cnum);
				bl += nchar;
				buf[bl++] = CSEP;
				for (c = commands->next; c != commands; c = c->next) {
					nchar = strlen(c->cmd_string);
					memcpy(buf + bl, c->cmd_string, nchar);
					bl += nchar;
					buf[bl++] = CSEP;

					if (c->cmd == D_eval) {
						CMDARG *a = c->arg;
						nchar = strlen(a->a_string);	/* statements */
						memcpy(buf + bl, a->a_string, nchar);
						bl += nchar;
						buf[bl++] = CSEP;
						nchar = strlen("end");	/* end of 'eval' */
						memcpy(buf + bl, "end", nchar);
						bl += nchar;
						buf[bl++] = CSEP;
					}
				}
				nchar = strlen("end");	/* end of 'commands' */
				memcpy(buf + bl, "end", nchar);
				bl += nchar;
				buf[bl++] = FSEP;		/* field */
			}
			buf[bl++] = RSEP;		/* record */
			buf[bl] = '\0';

			/* condition expression */
			if (cndn->expr) {
				bl--;	/* undo RSEP from above */
				nchar = strlen(cndn->expr);
				if (nchar + 1 /*FSEP*/ >= buflen - bl) {
					buflen = bl + nchar + 1 /*FSEP*/ + 1 /*RSEP*/;
					erealloc(buf, char *, buflen + 1, "serialize_list");
				}
				memcpy(buf + bl, cndn->expr, nchar);
				bl += nchar;
				buf[bl++] = FSEP;		/* field */
				buf[bl++] = RSEP;		/* record */
				buf[bl] = '\0';
			}

			ptr = (type == BREAK) ? (void *) b->prev : (void *) wd->prev;
			break;
		case DISPLAY:
			ptr = (void *) wd->prev;
			break;
		case HISTORY:
			ptr = (void *) hist_list[++hist_index];
			break;
		case OPTION:
			ptr = (void *) (++opt);
			break;
		default:
			break;
		}
	}

	if (bl > 0)	/* non-empty list */
		setenv(env_variable[type], buf, 1);
}


static void
unserialize_commands(char *str, int str_len)
{
	if (str_len <= 0 || str == NULL)
		return;
	commands_string = str;
	commands_string_len = str_len;
	push_cmd_src(INVALID_HANDLE, false, read_commands_string, 0, 0, EXIT_FATAL);
	line_sep = CSEP;
	read_command();		/* forced to return in do_commands */
	pop_cmd_src();
}


/* unserialize_list_item --- create a list_item structure from unserialized data */

static struct list_item *
unserialize_list_item(struct list_item *list, char **pstr, int *pstr_len, int field_cnt)
{
	int num, type, i;
	struct list_item *l;
	NODE *symbol = NULL;
	int sub_cnt = 0, cnt;
	NODE **subs = NULL;

	/* subscript	-- number type sname num_subs subs [commands [condition]]
	 * variable		-- number type sname [commands [condition]]
	 * field		-- number type symbol(numbr) commands [commands [condition]]
	 */

	num = strtol(pstr[0], NULL, 0);
	type = strtol(pstr[1], NULL, 0);

	if (type == D_field) {
		int field_num;
		field_num = strtol(pstr[2], NULL, 0);
		symbol = make_number((AWKNUM) field_num);
		cnt = 3;
	} else {
		char *name;
		name = estrdup(pstr[2], pstr_len[2]);
		symbol = find_symbol(name, NULL);
		efree(name);
		if (symbol == NULL)
			return NULL;
		cnt = 3;
		if (type == D_subscript) {
			int sub_len;
			sub_cnt = strtol(pstr[3], NULL, 0);
			emalloc(subs, NODE **, sub_cnt * sizeof(NODE *), "unserialize_list_item");
			cnt++;
			for (i = 0; i < sub_cnt; i++) {
				sub_len = strtol(pstr[cnt], NULL, 0);
				subs[i] = make_string(pstr[cnt + 1], sub_len);
				cnt += 2;
			}
		}
	}

	l = add_item(list, type, symbol, NULL);
	if (type == D_subscript) {
		l->num_subs = sub_cnt;
		l->subs = subs;
	}
	l->number = num;	/* keep same item number across executions */

	if (list == &watch_list) {
		initialize_watch_item(l);
		/* unserialize watchpoint `commands' */
		unserialize_commands(pstr[cnt], pstr_len[cnt]);
		cnt++;
		if (field_cnt > cnt) {
			char *expr;
			expr = estrdup(pstr[cnt], pstr_len[cnt]);
			if (parse_condition(D_watch, l->number, expr) != 0)
				efree(expr);
		}
		if (num > list->number)   /* update list number counter */
			list->number = num;
	} else
		list->number = num;

	return l;
}

/* unserialize_breakpoint --- create a breakpoint structure from unserialized data */

static BREAKPOINT *
unserialize_breakpoint(char **pstr, int *pstr_len, int field_cnt)
{
	char *src;
	int lineno;
	BREAKPOINT *b = NULL;
	INSTRUCTION *rp;
	SRCFILE *s;

	/* src source_line flags ignore_count hit_count number commands [condition] */

	src = estrdup(pstr[0], pstr_len[0]);
	s = source_find(src);
	efree(src);
	if (s == NULL)
		return NULL;
	src = s->src;
	lineno = strtol(pstr[1], NULL, 0);
	if (lineno <= 0 || lineno > s->srclines)
		return NULL;
	rp = find_rule(src, lineno);
	if (rp == NULL
			||  (b = set_breakpoint_at(rp, lineno, true)) == NULL
	)
		return NULL;

	b->flags =  strtol(pstr[2], NULL, 0);
	b->ignore_count = strtol(pstr[3], NULL, 0);
	b->hit_count = strtol(pstr[4], NULL, 0);
	b->number =  strtol(pstr[5], NULL, 0);	/* same number as previous run */

	if (field_cnt > 6)	/* unserialize breakpoint `commands' */
		unserialize_commands(pstr[6], pstr_len[6]);

	if (field_cnt > 7) {	/* condition expression */
		char *expr;
		expr = estrdup(pstr[7], pstr_len[7]);
		if (parse_condition(D_break, b->number, expr) != 0)
			efree(expr);
	}

	if (b->number > watch_list.number)  /* watch and break has same number counter */
		watch_list.number = b->number;  /* update counter */
	return b;
}

/* unserialize_option --- set a debugger option from unserialized data. */

static struct dbg_option *
unserialize_option(char **pstr, int *pstr_len, int field_cnt ATTRIBUTE_UNUSED)
{
	const struct dbg_option *opt;

	for (opt = option_list; opt->name; opt++) {
		if (strncmp(pstr[0], opt->name, pstr_len[0]) == 0) {
			char *value;

			value = estrdup(pstr[1], pstr_len[1]);
			(*(opt->assign))(value);
			efree(value);
			return ((struct dbg_option *) opt);
		}
	}
	return NULL;
}

/* unserialize_list -- reconstruct list from serialized data stored in
 *                environment variable.
 */

static void
unserialize_list(int type)
{
	char *val;
	char *p, *q, *r, *s;
#define MAX_FIELD 30
	static char *pstr[MAX_FIELD];
	static int pstr_len[MAX_FIELD];

	val = getenv(env_variable[type]);
	if (val == NULL)
		return;

	for (p = val; (q = strchr(p, RSEP)) != NULL; p = q + 1) {
		int field_cnt = 0;
		if (type == HISTORY) {
			*q = '\0';
			add_history(p);
			*q = RSEP;
			continue;
		}

		r = p;
		while ((s = strchr(r, FSEP)) != NULL && s < q) {
			pstr[field_cnt] = r;
			pstr_len[field_cnt] = (int) (s - r);
			r = s + 1;
			field_cnt++;
			if (field_cnt == MAX_FIELD)
#ifdef GAWKDEBUG
				fatal("Increase MAX_FIELD and recompile");
#else
				return;
#endif
		}

		switch (type) {
		case BREAK:
			(void) unserialize_breakpoint(pstr, pstr_len, field_cnt);
			break;
		case DISPLAY:
			(void) unserialize_list_item(&display_list, pstr, pstr_len, field_cnt);
			break;
		case WATCH:
			(void) unserialize_list_item(&watch_list, pstr, pstr_len, field_cnt);
			break;
		case OPTION:
			(void) unserialize_option(pstr, pstr_len, field_cnt);
			break;
		case HISTORY:
			/* processed at the beginning of for loop */
			break;
		default:
			break;
		}
	}

#ifdef HAVE_LIBREADLINE
	if (type == HISTORY)
		sess_history_base = history_length;
#endif

	unsetenv(env_variable[type]);
#undef MAX_FIELD
}

static int
prompt_yes_no(const char *mesg, char res_true, int res_default, FILE *fp)
{
	char *in_str;
	int ret = res_default; /* default */

	if (input_from_tty) {
		fprintf(fp, "%s", _(mesg));
		in_str = read_a_line(NULL);
		if (in_str == NULL)	/* EOF */
			exit(EXIT_FAILURE);
		ret = (*in_str == res_true);
		efree(in_str);
	}
	return ret;
}

/* has_break_or_watch_point --- check if given breakpoint or watchpoint
 *                              number exists. When flag any is true,
 *                              check if any breakpoint/watchpoint
 *                              has been set (ignores num). Returns
 *                              type (breakpoint or watchpoint) or 0.
 */

int
has_break_or_watch_point(int *pnum, bool any)
{
	BREAKPOINT *b = NULL;
	struct list_item *w = NULL;

	if (any) {
		if (breakpoints.next != &breakpoints)
			b = breakpoints.next;
		if (watch_list.next != &watch_list)
			w = watch_list.next;

		if (! b && ! w)
			return 0;
		if (b && ! w) {
			*pnum = b->number;
			return D_break;
		}
		if (w && ! b) {
			*pnum = w->number;
			return D_watch;
		}
		if (w->number > b->number) {
			*pnum = w->number;
			return D_watch;
		}
		*pnum = b->number;
		return D_break;
	}

	/* N.B: breakpoints and watchpoints get numbers from a single
	 * counter/sequencer watch_list.number.
	 */

	for (b = breakpoints.next; b != &breakpoints; b = b->next) {
		if (b->number == *pnum)
			return D_break;
	}
	for (w = watch_list.next; w != &watch_list; w = w->next) {
		if (w->number == *pnum)
			return D_watch;
	}

	return 0;
}

/* delete_commands_item --- delete item(command) from `commands' list. */

static void
delete_commands_item(struct commands_item *c)
{
	efree(c->cmd_string);
	free_cmdarg(c->arg);
	c->next->prev = c->prev;
	c->prev->next = c->next;
	efree(c);
}

/* do_commands --- commands command */

int
do_commands(CMDARG *arg, int cmd)
{
	static BREAKPOINT *b;
	static struct list_item *w;
	static struct commands_item *commands;
	struct commands_item *c;

	if (cmd == D_commands) {
		int num = -1, type;
		if (arg == NULL)
			type = has_break_or_watch_point(&num, true);
		else {
			num = arg->a_int;
			type = has_break_or_watch_point(&num, false);
		}
		b = NULL;
		w = NULL;
		if (type == D_break)
			b = find_breakpoint(num);
		else if (type == D_watch)
			w = find_item(&watch_list, num);
		assert((b != NULL) || (w != NULL));
		commands = (b != NULL) ? &b->commands : &w->commands;

		/* delete current commands */
		for (c = commands->next; c != commands; c = c->next) {
			c = c->prev;
			delete_commands_item(c->next);
		}
		return false;

	} else if (cmd == D_end) {
		commands = NULL;
		if (read_a_line == read_commands_string) /* unserializig commands */
			return true;	/* done unserializing, terminate zzparse() */
		return false;

	} else if (cmd == D_silent) {
		if (b != NULL)
			b->silent = true;
		else if (w != NULL)
			w->silent = true;
		/* we also append silent command to the list for use
		 * in `info break(watch)', and to simplify
		 * serialization/unserialization of commands.
		 */
	}

	assert(commands != NULL);

	emalloc(c, struct commands_item *, sizeof(struct commands_item), "do_commands");
	c->next = NULL;
	c->cmd = cmd;

	/* N.B.: first arg is the command string, see command.y */
	c->cmd_string = arg->a_string;
	c->arg = arg->next; /* actual arguments to the command */
	efree(arg);

	/* append to the list */
	c->prev = commands->prev;
	c->next = commands;
	commands->prev = c;
	c->prev->next = c;
	return false;
}

/* execute_commands --- execute breakpoint/watchpoint commands, the first
 *                      command that resumes execution terminates
 *                      commands processing.
 */

static int
execute_commands(struct commands_item *commands)
{
	struct commands_item *c;
	Func_cmd cmd_ptr;
	bool ret = false;

	for (c = commands->next; c != commands; c = c->next) {
		if (c->cmd == D_silent)
			continue;
		cmd_ptr = get_command(c->cmd);		/* command handler */
		ret = (*cmd_ptr)(c->arg, c->cmd);
		if (ret)	/* resume execution (continue, next etc.) */
			break;
	}
	return ret;
}

/* do_print_f --- printf command */

int
do_print_f(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	int count = 0;
	int i;
	CMDARG *a;
	NODE **tmp;
	char *name;
	NODE *r;
	volatile jmp_buf fatal_tag_stack;

	/* count maximum required size for tmp */
	for (a = arg; a != NULL ; a = a->next)
		count++;
	emalloc(tmp, NODE **, count * sizeof(NODE *), "do_print_f");

	for (i = 0, a = arg; a != NULL ; i++, a = a->next) {
		switch (a->type) {
		case D_variable:
			name = a->a_string;
			r = find_symbol(name, NULL);
			if (r == NULL)
				goto done;
			if (r->type == Node_var_new || r->type == Node_elem_new)
				tmp[i] = Nnull_string;
			else if (r->type != Node_var) {
				d_error(_("`%s' is not a scalar variable"), name);
				goto done;
			} else
				tmp[i] = r->var_value;
			break;
		case D_field:
		{
			long field_num;
			r = a->a_node;
			field_num = get_number_si(r);
			tmp[i] = *get_field(field_num, NULL);
		}
			break;
		case D_subscript:
		{
			int cnt = a->a_count;
			name = a->a_string;
			r = find_array(name);
			if (r == NULL)
				goto done;

			for (; cnt > 0; cnt--) {
				NODE *value, *subs;
				a = a->next;
				subs = a->a_node;
				value = in_array(r, subs);
				if (cnt == 1) {
					if (value == NULL)
						tmp[i] = Nnull_string;		/* FIXME: goto done ? */
					else if (value->type == Node_var_array) {
						d_error(_("attempt to use array `%s[\"%.*s\"]' in a scalar context"),
									name, (int) subs->stlen, subs->stptr);
						goto done;
					} else
						tmp[i] = value;
				} else {
					if (value == NULL) {
						d_error(_("[\"%.*s\"] not in array `%s'"),
									(int) subs->stlen, subs->stptr, name);
						goto done;
					} else if (value->type != Node_var_array) {
						d_error(_("attempt to use scalar `%s[\"%.*s\"]' as array"),
									name, (int) subs->stlen, subs->stptr);
						goto done;
					} else {
						r = value;
						name = r->vname;
					}
				}
			}
		}
			break;
		case D_node:
			tmp[i] = a->a_node;
			break;
		default:
			break;
		}
	}

	tmp[0] = force_string(tmp[0]);

	PUSH_BINDING(fatal_tag_stack, fatal_tag, fatal_tag_valid);
	if (setjmp(fatal_tag) == 0)
		r = format_tree(tmp[0]->stptr, tmp[0]->stlen, tmp, i);
	else {
		/* fatal error, restore exit_val of program */
		exit_val = EXIT_SUCCESS;
		r = NULL;
	}
	POP_BINDING(fatal_tag_stack, fatal_tag, fatal_tag_valid);

	if (r != NULL) {
		(void) fwrite(r->stptr, sizeof(char), r->stlen, out_fp);
		unref(r);
	}
done:
	efree(tmp);
	return false;
}

/* do_source --- source command */

int
do_source(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	int fd;
	char *file = arg->a_string;

	fd = open_readfd(file);
	if (fd <= INVALID_HANDLE) {
		d_error(_("cannot open source file `%s' for reading: %s"),
					file, strerror(errno));
		return false;
	}

	push_cmd_src(fd, false, g_readline, close, D_source, EXIT_SUCCESS);
	cmd_src->str = estrdup(file, strlen(file));
	return false;
}

/* open_readfd --- open a file for reading */

static int
open_readfd(const char *file)
{
	int fd;

	fd = open(file, O_RDONLY);
	if (fd <= INVALID_HANDLE)
		return INVALID_HANDLE;
	else if (os_isdir(fd)) {
		(void) close(fd);
		errno = EISDIR;
		return INVALID_HANDLE;
	}
	return fd;
}

/* find_option --- check if option name is valid */

int
find_option(char *name)
{
	const char *p;
	int idx;

	for (idx = 0; (p = option_list[idx].name); idx++) {
		if (strcmp(p, name) == 0)
			return idx;
	}
	return -1;
}

/* option_help --- display help text for debugger options */

void
option_help()
{
	const struct dbg_option *opt;

	for (opt = option_list; opt->name; opt++)
		fprintf(out_fp, "\t%-15.15s - %s\n", opt->name, _(opt->help_txt));
}

#ifdef HAVE_LIBREADLINE

/* option_generator --- generator function for option name completion */

char *
option_generator(const char *text, int state)
{
	static size_t textlen;
	static int idx;
	const char *name;

	if (! state) {	/* first time */
		textlen = strlen(text);
		idx = 0;
	}

	while ((name = option_list[idx++].name)) {
		if (strncmp(name, text, textlen) == 0)
			return estrdup(name, strlen(name));
	}
	return NULL;
}

#endif

/* set_gawk_output --- redirect gawk (normal) output */

static void
set_gawk_output(const char *file)
{
	int fd = INVALID_HANDLE;
	FILE *fp = NULL;

	if (output_fp != stdout) {
		if (output_fp != stderr) {
			fclose(output_fp);
			efree((void*) output_file);
		}
		output_fp = stdout;
		output_is_tty = os_isatty(fileno(stdout));
		output_file = "/dev/stdout";
	}

	if (file == NULL || file[0] == '\0')
		return;

	errno = 0;
	if ((fd = os_devopen(file, O_WRONLY)) != INVALID_HANDLE) {
		fp = fdopen(fd, "w");
		if (fp == NULL)
			close(fd);

	} else if (strncmp(file, "/dev/", 5) == 0) {
		char *cp = (char *) file + 5;

		if (strcmp(cp, "stdout") == 0)
			return;
		if (strcmp(cp, "stderr") == 0) {
			output_fp = stderr;
			output_file = "/dev/stderr";
			output_is_tty = os_isatty(fileno(stderr));
			return;
		}

		if (strncmp(cp, "fd/", 3) == 0) {
			cp += 3;
			fd = (int) strtoul(cp, NULL, 10);
			if (errno == 0 && fd > INVALID_HANDLE) {
				fp = fdopen(fd, "w");
				if (fp == NULL)
					fd = INVALID_HANDLE;
			} else
				fd = INVALID_HANDLE;
		} else {
			/* /dev/ttyN, /dev/pts/N, /dev/null etc. */
			fd = open(file, O_WRONLY);
		}

		if (fd > INVALID_HANDLE && fp == NULL) {
			fp = fdopen(fd, "w");
			if (fp == NULL)
				close(fd);
		}

	} else {
		/* regular file */
		fp = fopen(file, "w");
	}

	if (fp != NULL) {
		output_fp = fp;
		output_file = estrdup(file, strlen(file));
		setbuf(fp, (char *) NULL);
		output_is_tty = os_isatty(fileno(fp));
	} else {
		d_error(_("could not open `%s' for writing: %s"),
					file,
					errno != 0 ? strerror(errno) : _("reason unknown"));
		fprintf(out_fp, _("sending output to stdout\n"));
	}
}

/* set_prompt --- set debugger prompt */

static void
set_prompt(const char *value)
{
	efree((void *) dgawk_prompt);
	dgawk_prompt = estrdup(value, strlen(value));
	dbg_prompt = dgawk_prompt;
}

/* set_option_flag --- convert option string to flag value */

static int
set_option_flag(const char *value)
{
	long n;
	if (strcmp(value, "on") == 0)
		return true;
	if (strcmp(value, "off") == 0)
		return false;
	errno = 0;
	n = strtol(value, NULL, 0);
	return (errno == 0 && n != 0);
}

/* set_option_num --- set integer option value from string */

static void
set_option_num(int *pnum, const char *value)
{
	long n;
	errno = 0;
	n = strtol(value, NULL, 0);
	if (errno == 0 && n > 0)
		*pnum = n;
	else
		d_error(_("invalid number"));
}

/* set_listsize --- set list output window size */

static void
set_listsize(const char *value)
{
	set_option_num(&list_size, value);
}

/* set_trace --- set instruction tracing on or off */

static void
set_trace(const char *value)
{
	do_trace = set_option_flag(value);
}

/* set_save_history --- save history on exit */

static void
set_save_history(const char *value)
{
	do_save_history = set_option_flag(value);
}

/* set_save_options --- save options on exit */

static void
set_save_options(const char *value)
{
	do_save_options = set_option_flag(value);
}

/* set_history_size --- maximum entries in history file */

static void
set_history_size(const char *value)
{
	set_option_num(&history_size, value);
}


/* read_commands_string --- one of the many ways zzlex fetches a line to parse;
 *                          this one is used to parse `commands' string during
 *                          unserialization.
 */

char *
read_commands_string(const char *prompt ATTRIBUTE_UNUSED)
{
	char *p, *end, *line;

	if (commands_string == NULL)
		return NULL;

	p = (char *) commands_string;
	end = (char *) commands_string + commands_string_len;
	for (; p < end; p++) {
		if (*p == line_sep) {
			line = estrdup(commands_string, p - commands_string);
			commands_string = p + 1;
			commands_string_len = end - commands_string;
			return line;
		}
	}

	line = estrdup(commands_string, commands_string_len);
	commands_string = NULL;
	commands_string_len = 0;
	return line;
}

/* save_options --- save current options to file */

static void
save_options(const char *file)
{
	FILE *fp;
	const struct dbg_option *opt;

	fp = fopen(file, "w");
	if (fp == NULL)
		return;

	for (opt = option_list; opt->name; opt++) {
		if (opt->str_val != NULL)
			fprintf(fp, "option %s = \"%s\"\n", opt->name, *(opt->str_val));
		else
			fprintf(fp, "option %s = %d\n", opt->name, *(opt->num_val));
	}
	fclose(fp);
	chmod(file, 0600);
}

/* close_all --- close all open files */

static void
close_all()
{
	bool stdio_problem, got_EPIPE;
	struct command_source *cs;

	(void) nextfile(& curfile, true);	/* close input data file */
	(void) close_io(& stdio_problem, & got_EPIPE);
	if (cur_srcfile->fd != INVALID_HANDLE) {
		close(cur_srcfile->fd);
		cur_srcfile->fd = INVALID_HANDLE;
	}
	for (cs = cmd_src; cs != NULL; cs = cs->next) {
		if (cs->close_func && cs->fd != INVALID_HANDLE) {
			cs->close_func(cs->fd);
			cs->fd = INVALID_HANDLE;
		}
	}

	close_extensions();

	set_gawk_output(NULL);	/* closes output_fp if not stdout */
}

/* pre_execute_code --- pre_hook for execute_code, called by pre_execute */

static int
pre_execute_code(INSTRUCTION **pi)
{
	INSTRUCTION *ei = *pi;

	switch (ei->opcode) {
	case Op_K_exit:
	case Op_K_next:
	case Op_K_nextfile:
	case Op_K_getline:	/* getline without redirection */
		d_error(_("`%s' not allowed in current context;"
				" statement ignored"),
				op2str(ei->opcode));
		*pi = ei->nexti;
		break;
	case Op_K_return_from_eval:
		if (ei->nexti != NULL) {	/* not an implicit return */
			NODE *r;
			d_error(_("`return' not allowed in current context;"
					" statement ignored"));
			/* throw away return value already pushed onto stack */
			r = POP_SCALAR();
			DEREF(r);
			*pi = ei->nexti;
		}
		break;
	default:
		break;
	}
	return (ei == *pi);
}

extern INSTRUCTION *unwind_stack(long n);

static NODE *
execute_code(volatile INSTRUCTION *code)
{
	volatile NODE *r = NULL;
	volatile jmp_buf fatal_tag_stack;
	// long save_stack_size;	// see comment below
	int save_flags = do_flags;

	/* We use one global stack for all contexts.
	 * Save # of items in stack; in case of
	 * a fatal error, pop stack until it has that many items.
	 */

	// save_stack_size = (stack_ptr  - stack_bottom) + 1;	// see comment below
	do_flags = false;

	PUSH_BINDING(fatal_tag_stack, fatal_tag, fatal_tag_valid);
	if (setjmp(fatal_tag) == 0) {
		(void) interpret((INSTRUCTION *) code);
		r = POP_SCALAR();
	} else {	/* fatal error */
		/*
		 * 9/2022:
		 * Initially, the code did this:
		 *
		 * (void) unwind_stack(save_stack_size);
		 *
		 * to attempt to recover and keep going. But a fatal error
		 * can corrupt memory. Instead of trying to recover, just
		 * start over.
		 */
		// Let the user know, but DON'T use the fatal() function!
		fprintf(stderr, _("fatal error during eval, need to restart.\n"));
		// Go back to debugger
		restart(false);		// does not return
	}

	POP_BINDING(fatal_tag_stack, fatal_tag, fatal_tag_valid);
	do_flags = save_flags;
	if (exit_val != EXIT_SUCCESS) {	/* must be EXIT_FATAL? */
		exit_val = EXIT_SUCCESS;
		return NULL;
	}
	return (NODE *) r;
}

/* do_eval --- eval command */

int
do_eval(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	NODE *r, *ret_val;
	NODE *f = NULL;
	NODE *this_frame = NULL, *this_func = NULL;
	NODE **sp;
	INSTRUCTION *eval, *code = NULL;
	AWK_CONTEXT *ctxt;
	int ecount = 0, pcount = 0;
	int ret;
	int save_flags = do_flags;
	SRCFILE *the_source;

	if (prog_running) {
		this_frame = find_frame(0);
		this_func = this_frame->func_node;
	}

	install_params(this_func);	/* expose current function parameters to eval */
	ctxt = new_context();
	ctxt->install_func = append_symbol;	/* keep track of newly installed globals */
	push_context(ctxt);
	the_source = add_srcfile(SRC_CMDLINE, arg->a_string, srcfiles, NULL, NULL);
	do_flags &= DO_MPFR;	// preserve this flag only
	ret = parse_program(&code, true);
	do_flags = save_flags;
	remove_params(this_func);
	if (ret != 0) {
		pop_context();	/* switch to prev context */
		free_context(ctxt, false /* keep_globals */);

		/* Remove @eval from FUNCTAB. */
		NODE *s = make_string("@eval", 5);
		(void) assoc_remove(func_table, s);
		unref(s);

		return false;
	}

	f = lookup("@eval");
	assert(f != NULL);
	if (this_func == NULL) {	/* in main */
		/* do a function call */
		eval = bcalloc(Op_func_call, 2, 0);
		eval->source_file = cur_srcfile->src;
		eval->func_body = f;
		eval->func_name = NULL;	/* not needed, func_body already assigned */
		(eval + 1)->expr_count = 0;
		eval->nexti = bcalloc(Op_stop, 1, 0);

	} else {
		/* execute as a part of the current function */
		int i;
		INSTRUCTION *t;

		eval = f->code_ptr;	/* Op_func */
		eval->source_file = cur_srcfile->src;
		/* turn implicit Op_K_return into Op_stop */
		t = (eval + 1)->lasti;	/* Op_K_return */
		t->opcode = Op_stop;

		/* add or append eval locals to the current frame stack */
		ecount = f->param_cnt;	/* eval local count */
		pcount = this_func->param_cnt;

		if (ecount > 0) {
			if (pcount == 0)
				emalloc(this_frame->stack, NODE **, ecount * sizeof(NODE *), "do_eval");
			else
				erealloc(this_frame->stack, NODE **, (pcount + ecount) * sizeof(NODE *), "do_eval");

			sp = this_frame->stack + pcount;
			for (i = 0; i < ecount; i++) {
				NODE *np;

				np = f->fparms + i;
				np->param_cnt += pcount;	/* appending eval locals: fixup param_cnt */

				getnode(r);
				memset(r, 0, sizeof(NODE));
				*sp++ = r;
				/* local variable */
				r->type = Node_var_new;
				r->vname = np->param;
			}

			this_func->param_cnt += ecount;
		}
	}

#if 0
	pf_data.print_func = fprintf;
	pf_data.fp = out_fp;
	pf_data.defn = false;	/* in_dump = false */
	(void) print_code(f->code_ptr, &pf_data);
#endif

	ret_val = execute_code((volatile INSTRUCTION *) eval);

	if (ret_val != NULL)
		DEREF(ret_val);	/* throw away return value */
	/* else
		fatal error */

	if (this_func != NULL && ecount > 0) {
		int i;

		/* undo frame manipulation from above */

		/* free eval locals */
		sp = this_frame->stack + pcount;
		for (i = ecount; i > 0; i--) {
			r = *sp;
			if (r->type == Node_var)     /* eval local variable */
				DEREF(r->var_value);
			else if (r->type == Node_var_array)     /* eval local array */
				assoc_clear(r);
			freenode(r);
			*sp++ = (NODE *) 0;
		}
		if (pcount == 0) {
			efree(this_frame->stack);
			this_frame->stack = NULL;
		} /* else
				restore_frame() will free it */

		this_func->param_cnt -= ecount;
	}

	/*
	 * Always destroy symbol "@eval", however destroy all newly installed
	 * globals only if fatal error (execute_code() returing NULL).
	 */

	pop_context();	/* switch to prev context */
	free_context(ctxt, (ret_val != NULL));   /* free all instructions and optionally symbols */

	if (ret_val != NULL) {
		/*
		 * Remove @eval from FUNCTAB, so that above code
		 * will work the next time around.
		 */
		NODE *s = make_string("@eval", 5);

		(void) assoc_remove(func_table, s);
		unref(s);
	}

	free(f->vname);
	freenode(f);

	free_srcfile(the_source);

	return false;
}

/*
GDB Documentation:
	... When you use condition, GDB checks expression
immediately for syntactic correctness, and to determine whether symbols
in it have referents in the context of your breakpoint. If expression
uses symbols not referenced in the context of the breakpoint, GDB prints
an error message:

    No symbol "foo" in current context.
*/

static int invalid_symbol = 0;

static void
check_symbol(NODE *r)
{
	invalid_symbol++;
	d_error(_("no symbol `%s' in current context"), r->vname);
	/* install anyway, but keep track of it */
	append_symbol(r);
}

/* parse_condition --- compile a condition expression */

static int
parse_condition(int type, int num, char *expr)
{
	INSTRUCTION *code = NULL;
	AWK_CONTEXT *ctxt = NULL;
	int ret;
	BREAKPOINT *b;
	struct list_item *w;
	NODE *this_func = NULL;
	INSTRUCTION *it, *stop, *rule;
	struct condition *cndn = NULL;
	int save_flags = do_flags;

	if (type == D_break && (b = find_breakpoint(num)) != NULL) {
		INSTRUCTION *rp;
		cndn = &b->cndn;
		rp = find_rule(b->src, b->bpi->source_line);
		if (rp != NULL && rp->opcode == Op_func)
			this_func = rp->func_body;
	} else if (type == D_watch && (w = find_item(&watch_list, num)) != NULL) {
		cndn = &w->cndn;
		this_func = find_frame(cur_frame)->func_node;
	}

	if (cndn == NULL)
		return -1;
	if (expr == NULL)
		goto out;	/* delete condition */

	install_params(this_func);
	ctxt = new_context();
	invalid_symbol = 0;
	ctxt->install_func = check_symbol;
	push_context(ctxt);
	(void) add_srcfile(SRC_CMDLINE, expr, srcfiles, NULL, NULL);
	do_flags = false;
	ret = parse_program(&code, true);
	do_flags = save_flags;
	remove_params(this_func);
	pop_context();

	if (ret != 0 || invalid_symbol) {
		free_context(ctxt, false /* keep_globals */);
		return -1;
	}

	/* condition expression is parsed as awk pattern without
	 * any action. The code is then modified to end up with
	 * a `1.0' on stack when the expression is true, `0.0' otherwise.
	 */

	assert(code != NULL);
	rule = ctxt->rule_list.nexti;
	stop = bcalloc(Op_stop, 1, 0);

	it = rule->firsti;	/* Op_K_print_rec */
	assert(it->opcode == Op_K_print_rec);
	it->opcode = Op_push_i;
	it->memory = make_number(1.0);
	it->nexti = bcalloc(Op_jmp, 1, 0);
	it->nexti->target_jmp = stop;
	it->nexti->nexti = rule->lasti;

	it = rule->lasti;		/* Op_no_op, target for Op_jmp_false */
	assert(it->opcode == Op_no_op);
	it->opcode = Op_push_i;
	it->memory = make_number(0.0);
	it->nexti = stop;

out:
	if (cndn->expr != NULL)
		efree(cndn->expr);
	free_context(cndn->ctxt, false);
	cndn->code = code;
	cndn->expr = expr;
	cndn->ctxt = ctxt;

	return 0;
}

/* do_condition --- condition command */

int
do_condition(CMDARG *arg, int cmd ATTRIBUTE_UNUSED)
{
	int type, num;
	char *expr = NULL;

	num = arg->a_int;
	type = has_break_or_watch_point(&num, false);
	if (! type)
		return false;
	arg = arg->next;	/* condition expression */
	if (arg != NULL)
		expr = arg->a_string;
	if (parse_condition(type, num, expr) == 0 && arg != NULL)
		arg->a_string = NULL;	/* don't let free_cmdarg free it */
	return false;
}

/* in_cmd_src --- check if filename already in cmd_src */

int
in_cmd_src(const char *filename)
{
	struct command_source *cs;
	for (cs = cmd_src; cs != NULL; cs = cs->next) {
		if (cs->str != NULL && strcmp(cs->str, filename) == 0)
			return true;
	}
	return false;
}

int
get_eof_status()
{
	if (cmd_src == NULL)
		return EXIT_FATAL;
	return cmd_src->eof_status;
}

void
push_cmd_src(
	int fd,
	bool istty,
	char * (*readfunc)(const char *),
	int (*closefunc)(int),
	int ctype,
	int eofstatus)
{
	struct command_source *cs;
	emalloc(cs, struct command_source *, sizeof(struct command_source), "push_cmd_src");
	cs->fd = fd;
	cs->is_tty = istty;
	cs->read_func = readfunc;
	cs->close_func = closefunc;
	cs->cmd = ctype;

	/* eof_status = EXIT_FATAL     - exit with status EXIT_FATAL on EOF or error.
	 *            = EXIT_FAILURE   - exit status EXIT_FAILURE on error.
	 *            = EXIT_SUCCESS   - don't exit on EOF or error.
	 */
	cs->eof_status = eofstatus;
	cs->str = NULL;
	cs->next = cmd_src;
	cmd_src = cs;

	input_fd = fd;
	input_from_tty = istty;
	read_a_line = readfunc;
}

int
pop_cmd_src()
{
	struct command_source *cs;

	if (cmd_src->next == NULL)
		return -1;

	cs = cmd_src;
	cmd_src = cs->next;
	if (cs->close_func && cs->fd != INVALID_HANDLE)
		cs->close_func(cs->fd);
	if (cs->str != NULL)
		efree(cs->str);
	efree(cs);

	input_fd = cmd_src->fd;
	input_from_tty = cmd_src->is_tty;
	read_a_line = cmd_src->read_func;
	return 0;
}
