/*
	debug.c

	Debugging support

	Copyright (C) 2020 Bill Currie <bill@taniwha.org>

	Author: Bill Currie <bill@taniwha.org>
	Date: 2020/03/24

	This program 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 2
	of the License, or (at your option) any later version.

	This program 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:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA

*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <ctype.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "QF/dstring.h"
#include "QF/hash.h"
#include "QF/keys.h"
#include "QF/quakefs.h"
#include "QF/sys.h"

#include "ruamoko/qwaq/qwaq.h"
#include "ruamoko/qwaq/ui/event.h"
#include "ruamoko/qwaq/ui/curses.h"
#include "ruamoko/qwaq/debugger/debug.h"

typedef struct qwaq_target_s {
	progs_t    *pr;
	struct qwaq_debug_s *debugger;
	int         handle;
	prdebug_t   event;
	void       *param;
	rwcond_t    run_cond;
	int         run_command;
} qwaq_target_t;

typedef struct qwaq_debug_s {
	progs_t    *pr;
	qwaq_input_resources_t *input;	// to communicate with the debugger thread
	PR_RESMAP (qwaq_target_t) targets;
} qwaq_debug_t;

#define always_inline inline __attribute__((__always_inline__))

static qwaq_target_t *
target_new (qwaq_debug_t *debug)
{
	return PR_RESNEW (debug->targets);
}

static void
target_free (qwaq_debug_t *debug, qwaq_target_t *target)
{
	PR_RESFREE (debug->targets, target);
}

static void
target_reset (qwaq_debug_t *debug)
{
	PR_RESRESET (debug->targets);
}

static inline qwaq_target_t *
target_get (qwaq_debug_t *debug, unsigned index)
{
	return PR_RESGET (debug->targets, index);
}

static inline int __attribute__((pure))
target_index (qwaq_debug_t *debug, qwaq_target_t *target)
{
	return PR_RESINDEX (debug->targets, target);
}

static always_inline qwaq_target_t * __attribute__((pure))
get_target (qwaq_debug_t *debug, const char *name, int handle)
{
	qwaq_target_t *target = target_get (debug, handle);

	if (!target || !target->debugger) {
		PR_RunError (debug->pr, "invalid target passed to %s", name + 4);
	}
	return target;
}

static void
qwaq_debug_handler (prdebug_t debug_event, void *param, void *data)
{
	__auto_type target = (qwaq_target_t *) data;
	qwaq_debug_t *debug = target->debugger;
	qwaq_event_t event = {};
	int         ret;

	target->event = debug_event;
	target->param = param;
	event.what = qe_debug_event;
	event.message.pointer_val = target->handle;

	while ((ret = qwaq_add_event (debug->input, &event)) == ETIMEDOUT) {
		// spin
	}
	if (ret == EINVAL) {
		Sys_Error ("event queue broke");
	}
	pthread_mutex_lock (&target->run_cond.mut);
	while (!target->run_command) {
		pthread_cond_wait (&target->run_cond.rcond, &target->run_cond.mut);
	}
	target->run_command = 0;
	pthread_mutex_unlock (&target->run_cond.mut);
	if (debug_event == prd_runerror || debug_event == prd_error) {
		pthread_exit (param);
	}
}

//FIXME need a better way to get this from one thread to the others
pthread_cond_t debug_data_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t debug_data_mutex = PTHREAD_MUTEX_INITIALIZER;
static qwaq_debug_t *qwaq_debug_data;

static int
qwaq_debug_load (progs_t *pr)
{
	__auto_type debug = (qwaq_debug_t *) PR_Resources_Find (pr, "qwaq-debug");

	pthread_mutex_lock (&debug_data_mutex);
	qwaq_debug_data = debug;	// FIXME ? see decl
	pthread_cond_broadcast (&debug_data_cond);
	pthread_mutex_unlock (&debug_data_mutex);

	return 1;
}

static void
qwaq_debug_clear (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	target_reset (debug);
}

static void
qwaq_debug_destroy (progs_t *pr, void *_res)
{
}

static void
qwaq_target_clear (progs_t *pr, void *_res)
{
	qwaq_target_t *target = pr->debug_data;
	if (target) {
		target_free (target->debugger, target);
	}
}

static void
qwaq_target_destroy (progs_t *pr, void *_res)
{
}

static int
qwaq_target_load (progs_t *pr)
{
	pthread_mutex_lock (&debug_data_mutex);
	while (!qwaq_debug_data) {
		pthread_cond_wait (&debug_data_cond, &debug_data_mutex);
	}
	pthread_mutex_unlock (&debug_data_mutex);

	qwaq_target_t *target = target_new (qwaq_debug_data);
	target->pr = pr;
	target->debugger = qwaq_debug_data;
	target->handle = target_index (qwaq_debug_data, target);
	qwaq_init_cond (&target->run_cond);
	target->run_command = 0;

	pr->debug_handler = qwaq_debug_handler;
	pr->debug_data = target;

	// start tracing immediately so the debugger has a chance to start up
	// before the target progs begin running
	pr->pr_trace = 1;
	pr->pr_trace_depth = -1;

	return 1;
}

static void
qdb_set_trace (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	int         state = P_INT (pr, 1);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;

	tpr->pr_trace = state;
}

static void
qdb_set_breakpoint (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	unsigned    staddr = P_INT (pr, 1);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;

	if (staddr >= tpr->progs->statements.count) {
		R_INT (pr) = -1;
		return;
	}
	int         set = (tpr->pr_statements[staddr].op & OP_BREAK) != 0;
	tpr->pr_statements[staddr].op |= OP_BREAK;
	R_INT (pr) = set;
}

static void
qdb_clear_breakpoint (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	unsigned    staddr = P_UINT (pr, 1);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;

	if (staddr >= tpr->progs->statements.count) {
		R_INT (pr) = -1;
		return;
	}
	tpr->pr_statements[staddr].op &= ~OP_BREAK;
	R_INT (pr) = 0;
}

static void
qdb_set_watchpoint (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	pr_ptr_t    offset = P_UINT (pr, 1);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;

	if (offset >= tpr->globals_size) {
		R_INT (pr) = -1;
		return;
	}
	tpr->watch = &tpr->pr_globals[offset];
	R_INT (pr) = 0;
}

static void
qdb_clear_watchpoint (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;

	tpr->watch = 0;
	R_INT (pr) = 0;
}

static void
qdb_continue (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);

	pthread_mutex_lock (&target->run_cond.mut);
	target->run_command = 1;
	pthread_cond_signal (&target->run_cond.rcond);
	pthread_mutex_unlock (&target->run_cond.mut);
}

static void
qdb_get_state (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;
	pr_lineno_t *lineno;
	pr_auxfunction_t *f;
	pr_string_t file = 0;
	unsigned    line = 0;
	unsigned    staddr = tpr->pr_xstatement;
	pr_func_t   func = 0;

	if (tpr->pr_xfunction) {
		func = tpr->pr_xfunction - tpr->function_table;
	}

	lineno = PR_Find_Lineno (tpr, staddr);
	if (lineno) {
		f = PR_Get_Lineno_Func (tpr, lineno);
		//FIXME file is a permanent string. dynamic would be better
		//but they're not merged (and would need refcounting)
		file = PR_SetString (pr, PR_Get_Source_File (tpr, lineno));
		func = f->function;
		line = PR_Get_Lineno_Line (tpr, lineno);
		line += f->source_line;
	}

	qdb_state_t state = {};
	state.staddr = staddr;
	state.func = func;
	state.file = file;
	state.line = line;

	R_PACKED (pr, qdb_state_t) = state;
}

static void
qdb_get_stack_depth (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;

	R_INT (pr) = tpr->pr_depth;
}

static void
qdb_get_stack (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;
	int         count = tpr->pr_depth;

	R_POINTER (pr) = 0;
	if (count > 0) {
		size_t      size = count * sizeof (qdb_stack_t);
		pr_string_t stack_block = PR_AllocTempBlock (pr, size);
		__auto_type stack = (qdb_stack_t *) PR_GetString (pr, stack_block);

		for (int i = 0; i < count; i++) {
			stack[i].staddr = tpr->pr_stack[i].staddr;
			stack[i].func = tpr->pr_stack[i].func - tpr->pr_xfunction;
			//XXX temp strings (need access somehow)
		}
		R_POINTER (pr) = PR_SetPointer (pr, stack);
	}
}

static void
qdb_get_event (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	__auto_type event = &P_STRUCT (pr, qdb_event_t, 1);

	memset (event, 0, sizeof (*event));
	event->what = target->event;
	switch (event->what) {
		case prd_subenter:
			event->function = *(pr_func_t *) target->param;
			break;
		case prd_runerror:
		case prd_error:
			event->message = PR_SetReturnString (pr, (char *) target->param);
			break;
		case prd_begin:
			event->function = *(pr_func_t *) target->param;
			break;
		case prd_terminate:
			event->exit_code = *(int *) target->param;
			break;
		case prd_trace:
		case prd_breakpoint:
		case prd_watchpoint:
		case prd_subexit:
		case prd_none:
			break;
	}
	R_INT (pr) = target->event != prd_none;
}

static void
qdb_get_data (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;
	pr_ptr_t    srcoff = P_POINTER (pr, 1);
	unsigned    length = P_UINT (pr, 2);
	pr_ptr_t    dstoff = P_POINTER(pr, 3);

	pr_type_t  *src = PR_GetPointer(tpr, srcoff);
	pr_type_t  *dst = PR_GetPointer(pr, dstoff);

	if (srcoff >= tpr->globals_size || srcoff + length >= tpr->globals_size) {
		R_INT (pr) = -1;
		return;
	}
	if (dstoff >= pr->globals_size || dstoff + length >= pr->globals_size) {
		R_INT (pr) = -1;
		return;
	}
	memcpy (dst, src, length * sizeof (pr_type_t));
	R_INT (pr) = 0;
}

static void
qdb_get_string (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;
	pr_string_t string = P_STRING (pr, 1);

	R_STRING (pr) = 0;
	if (PR_StringValid (tpr, string)) {
		RETURN_STRING (pr, PR_GetString (tpr, string));
	}
}

static void
qdb_get_file_path (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;
	const char *file = P_GSTRING (pr, 1);
	const char *basedir = PR_Debug_GetBaseDirectory (tpr, file);

	if (basedir) {
		size_t      baselen = strlen (basedir);
		size_t      filelen = strlen (file);
		char       *path = alloca (baselen + filelen + 2);
		strcpy (path, basedir);
		path[baselen] = '/';
		strcpy (path + baselen + 1, file);
		path = QFS_CompressPath (path);
		RETURN_STRING (pr, path);
		free (path);
	} else {
		R_STRING (pr) = P_STRING (pr, 1);
	}
}

static void
qdb_find_string (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;
	const char *str = P_GSTRING (pr, 1);

	R_INT (pr) = PR_FindString (tpr, str);
}

static void
qdb_find_global (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;
	const char *name = P_GSTRING (pr, 1);
	pr_def_t   *def = PR_FindGlobal (tpr, name);

	if (def) {
		R_PACKED (pr, qdb_def_t).type_size = (def->size << 16) | def->type;
		R_PACKED (pr, qdb_def_t).offset = def->ofs;
		R_PACKED (pr, qdb_def_t).name = def->name;
		R_PACKED (pr, qdb_def_t).type_encoding = def->type_encoding;
	} else {
		memset (&R_PACKED (pr, qdb_def_t), 0, sizeof (qdb_def_t));
	}
}

static void
qdb_find_field (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;
	const char *name = P_GSTRING (pr, 1);
	pr_def_t   *def = PR_FindField (tpr, name);

	if (def) {
		R_PACKED (pr, qdb_def_t).type_size = (def->size << 16) | def->type;
		R_PACKED (pr, qdb_def_t).offset = def->ofs;
		R_PACKED (pr, qdb_def_t).name = def->name;
		R_PACKED (pr, qdb_def_t).type_encoding = def->type_encoding;
	} else {
		memset (&R_PACKED (pr, qdb_def_t), 0, sizeof (qdb_def_t));
	}
}

static void
return_function (progs_t *pr, dfunction_t *func)
{
	R_POINTER (pr) = 0;
	if (func) {
		__auto_type f
			= (qdb_function_t *) PR_Zone_Malloc (pr, sizeof (qdb_function_t));
		f->staddr = func->first_statement;
		f->local_data = func->params_start;
		f->local_size = func->locals;
		f->profile = func->profile;
		f->name = func->name;
		f->file = func->file;
		f->num_params = func->numparams;
		R_POINTER (pr) = PR_SetPointer (pr, f);
	}
}

static void
qdb_find_function (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;
	const char *name = P_GSTRING (pr, 1);
	dfunction_t *func = PR_FindFunction (tpr, name);

	return_function (pr, func);
}

static void
qdb_get_function (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;
	pr_uint_t   fnum = P_INT (pr, 1);
	dfunction_t *func = tpr->pr_functions + fnum;

	if (fnum >= tpr->progs->functions.count) {
		func = 0;
	}
	return_function (pr, func);
}

static void
return_auxfunction (progs_t *pr, pr_auxfunction_t *auxfunc)
{
	R_POINTER (pr) = 0;
	if (auxfunc) {
		__auto_type f
			= (qdb_auxfunction_t *) PR_Zone_Malloc (pr,
												sizeof (qdb_auxfunction_t));
		f->function = auxfunc->function;
		f->source_line = auxfunc->source_line;
		f->line_info = auxfunc->line_info;
		f->local_defs = auxfunc->local_defs;
		f->num_locals = auxfunc->num_locals;
		f->return_type = auxfunc->return_type;
		R_POINTER (pr) = PR_SetPointer (pr, f);
	}
}

static void
qdb_find_auxfunction (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;
	const char *name = P_GSTRING (pr, 1);
	dfunction_t *func = PR_FindFunction (tpr, name);
	pr_uint_t   fnum = func - tpr->pr_functions;
	pr_auxfunction_t *auxfunc = PR_Debug_MappedAuxFunction (tpr, fnum);

	if (fnum >= tpr->progs->functions.count) {
		func = 0;
	}
	return_auxfunction (pr, auxfunc);
}

static void
qdb_get_auxfunction (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;
	pr_uint_t   fnum = P_UINT (pr, 1);
	pr_auxfunction_t *auxfunc = PR_Debug_MappedAuxFunction (tpr, fnum);

	return_auxfunction (pr, auxfunc);
}

static void
qdb_get_local_defs (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;
	pr_uint_t   fnum = P_UINT (pr, 1);
	pr_auxfunction_t *auxfunc = PR_Debug_MappedAuxFunction (tpr, fnum);

	R_POINTER (pr) = 0;
	if (auxfunc && auxfunc->num_locals) {
		pr_def_t   *defs = PR_Debug_LocalDefs (tpr, auxfunc);
		__auto_type qdefs
			= (qdb_def_t *) PR_Zone_Malloc (pr,
								auxfunc->num_locals * sizeof (qdb_def_t));
		for (unsigned i = 0; i < auxfunc->num_locals; i++) {
			qdefs[i].type_size = (defs[i].size << 16) | defs[i].type;
			qdefs[i].offset = defs[i].ofs;
			qdefs[i].name = defs[i].name;
			qdefs[i].type_encoding = defs[i].type_encoding;
		}
		R_POINTER (pr) = PR_SetPointer (pr, qdefs);
	}
}

static void
qdb_get_source_line_addr (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;
	const char *file = P_GSTRING (pr, 1);
	pr_uint_t   line = P_UINT (pr, 2);

	R_UINT (pr) = PR_FindSourceLineAddr (tpr, file, line);
}

static void
qdb_has_data_stack (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;

	R_INT (pr) = tpr->progs->version == PROG_VERSION;
}

static void
qdb_get_frame_addr (progs_t *pr, void *_res)
{
	__auto_type debug = (qwaq_debug_t *) _res;
	pr_ptr_t    handle = P_INT (pr, 0);
	qwaq_target_t *target = get_target (debug, __FUNCTION__, handle);
	progs_t    *tpr = target->pr;

	R_UINT (pr) = 0;

	if (tpr->progs->version == PROG_VERSION) {
		if (tpr->pr_depth) {
			prstack_t  *frame = tpr->pr_stack + tpr->pr_depth - 1;
			R_UINT (pr) = frame->stack_ptr;
		}
	}
}

#define bi(x,np,params...) {#x, x, -1, np, {params}}
#define p(type) PR_PARAM(type)
static builtin_t builtins[] = {
	bi(qdb_set_trace,            2, p(int), p(int)),
	bi(qdb_set_breakpoint,       2, p(int), p(uint)),
	bi(qdb_clear_breakpoint,     2, p(int), p(uint)),
	bi(qdb_set_watchpoint,       2, p(int), p(uint)),
	bi(qdb_clear_watchpoint,     1, p(int)),
	bi(qdb_continue,             1, p(int)),
	bi(qdb_get_state,            1, p(int)),
	bi(qdb_get_stack_depth,      1, p(int)),
	bi(qdb_get_stack,            1, p(int)),
	bi(qdb_get_event,            2, p(int), p(ptr)),
	bi(qdb_get_data,             4, p(int), p(uint), p(uint), p(ptr)),
	{"qdb_get_string|{tag qdb_target_s=}I",	qdb_get_string,	-1, 1, {p(uint)}},
	{"qdb_get_string|{tag qdb_target_s=}*",	qdb_get_string,	-1, 1, {p(string)}},
	bi(qdb_get_file_path,        2, p(int), p(string)),
	bi(qdb_find_string,          2, p(int), p(string)),
	bi(qdb_find_global,          2, p(int), p(string)),
	bi(qdb_find_field,           2, p(int), p(string)),
	bi(qdb_find_function,        2, p(int), p(string)),
	bi(qdb_get_function,         2, p(int), p(uint)),
	bi(qdb_find_auxfunction,     2, p(int), p(string)),
	bi(qdb_get_auxfunction,      2, p(int), p(uint)),
	bi(qdb_get_local_defs,       2, p(int), p(uint)),
	bi(qdb_get_source_line_addr, 3, p(int), p(string), p(uint)),
	bi(qdb_has_data_stack,       1, p(int)),
	bi(qdb_get_frame_addr,       1, p(int)),
	{}
};

void
QWAQ_Debug_Init (progs_t *pr)
{
	qwaq_debug_t *debug = calloc (sizeof (*debug), 1);

	debug->pr = pr;
	debug->input = PR_Resources_Find (pr, "input");

	PR_AddLoadFunc (pr, qwaq_debug_load);
	PR_Resources_Register (pr, "qwaq-debug", debug, qwaq_debug_clear,
						   qwaq_debug_destroy);
	PR_RegisterBuiltins (pr, builtins, debug);
}

void
QWAQ_DebugTarget_Init (progs_t *pr)
{
	PR_AddLoadFunc (pr, qwaq_target_load);
	PR_Resources_Register (pr, "qwaq-target", 0, qwaq_target_clear,
						   qwaq_target_destroy);
}
