/**************************************************************
 *
 *	CRISP - Custom Reduced Instruction Set Programmers Editor
 *
 *	(C) Paul Fox, 1989
 *
 *    Please See COPYRIGHT notice.
 *
 **************************************************************/
# include	"list.h" 
# include	"debug.h"

# define	ARGS	_a,_b,_c,_d,_e,_f,_g,_h,_i,_j

/**********************************************************************/
/*   CRISP  log  file  defaults to /tmp, but can be overridden by an  */
/*   environment variable.					      */
/**********************************************************************/
# define	CRISP_LOG	"/tmp/crisp.log"
char	*crisp_log = CRISP_LOG;
int	dflag = 0;
extern	int	pflag;
static FILE	*debug_fp = NULL;
extern	BUILTIN builtin[];

void	trace_log();
void	trace_ilog();
static void trace_sym1 PROTO((SYMBOL *));
SPTREE	*get_sym_level PROTO((void));
void	dbg_stack_trace PROTO((void));
void	dbg_nest_level PROTO((void));
void	dbg_inq_vars PROTO((void));
void	dbg_inq_var_info PROTO((void));

void
do_debug()
{	

	if (argv[1].l_flags != F_NULL)
		dflag = argv[1].l_int & 0xffff;
	else
		dflag = !dflag;
	ewprintf("*** DEBUG %s (0x%04x)%s%s ***", dflag ? "ON" : "OFF", dflag,
		dflag & DB_REGEXP ? " REGEXP" : "",
		dflag & DB_UNDO ? " UNDO" : "",
		dflag & DB_PROMPT ? " PROMPT" : ""
		);
	trace_log("\n");
	if (!dflag) {
		fflush(debug_fp);
		}
}

/**********************************************************************/
/*   Function  which  implements  the  debug_support()  macro.  This  */
/*   function  is  a  switcher  to  the  actual  subfunctions. These  */
/*   functions are used to implement the CRUNCH/CRISP debugger.	      */
/**********************************************************************/
void
debug_support()
{

	switch ((int) argv[1].l_int) {
	  case DBG_STACK_TRACE:
	  	dbg_stack_trace();
	  	break;
	  case DBG_NEST_LEVEL:
	  	dbg_nest_level();
	  	break;
	  case DBG_INQ_VARS:
	  	dbg_inq_vars();
	  	break;
	  case DBG_INQ_VAR_INFO:
	  	dbg_inq_var_info();
		break;
	  default:
	  	acc_assign_int(-1L);
		break;
	  }
}

/**********************************************************************/
/*   Function   to  return  a  list  containing  a  stack  trace  of  */
/*   execution.							      */
/**********************************************************************/
void
dbg_stack_trace()
{
	extern int ms_cnt;
	int	len = ms_cnt * sizeof_atoms[F_RSTR] + sizeof_atoms[F_HALT];
	LIST	*stk = (LIST *) chk_alloc(len);
	register LIST *stkp = stk;
	register int	i;
	char	*start_fn = get_str(3);
	int	skipping = TRUE;

	if (start_fn == NULL || *start_fn == NULL)
		skipping = FALSE;
		
	for (i = ms_cnt-1; i >= 0; i--) {
		if (skipping) {
			if (strcmp(start_fn, mac_stack[i].name) != 0)
				continue;
			skipping = FALSE;
			continue;
			}
		*stkp = F_RSTR;
		LPUT32(stkp, (long) r_init(F_STR, 
				mac_stack[i].name,
				strlen(mac_stack[i].name) + 1));
		stkp += sizeof_atoms[F_RSTR];
		}
	*stkp = F_HALT;
	acc_donate_list(stk, len);
}

/**********************************************************************/
/*   The   following   function   is  used  to  return  the  current  */
/*   execution nesting level.					      */
/**********************************************************************/
void
dbg_nest_level()
{
	acc_assign_int((long) ms_cnt);
}

/**********************************************************************/
/*   Following   function   returns  a  list  of  variables  at  the  */
/*   specified execution level.					      */
/**********************************************************************/
void
dbg_inq_vars()
{	SPTREE	*treep;
	int	len;
	LIST	*new_list;
	register LIST	*lp;
	SYMBOL	*sp;
	int	i;
	SPBLK	**array;
	int	num_syms;

	if ((treep = get_sym_level()) == NULL)
		return;
	/***********************************************/
	/*   Now  work  out  how  much memory we need  */
	/*   to create the list to store the result.   */
	/***********************************************/
	array = sp_flatten(treep);
	num_syms = sp_size(treep);
	len = sizeof_atoms[F_STR] * num_syms + 2;
	lp = new_list = (LIST *) chk_alloc(len);
	for (i = 0; array[i]; i++) {
		sp = (SYMBOL *) array[i]->data;
		*lp = F_STR;
		LPUT32(lp, (long) strdup(sp->s_name));
		lp += sizeof_atoms[F_STR];
		}
	*lp = F_HALT;
	acc_donate_list(new_list, len);
	/***********************************************/
	/*   Free things we no longer need.	       */
	/***********************************************/
	chk_free((void *) array);
}
/**********************************************************************/
/*   Function   to  return  type  and  value  of  a  variable  at  a  */
/*   specified nesting level.					      */
/**********************************************************************/
void
dbg_inq_var_info()
{	SPTREE	*treep;
	LIST	*new_list;
	int	len;
	LIST	*lp;
	SPBLK	*spb;
	SYMBOL	*sp;
	char	*name = get_str(3);
		
	if (name == NULL || (treep = get_sym_level()) == NULL)
		return;
	spb = splookup(name, treep);
	if (spb == NULL) {
		acc_assign_null();
		return;
		}
	sp = (SYMBOL *) spb->data;
	len = sizeof_atoms[F_INT] + sizeof_atoms[sp->s_flag] + sizeof_atoms[F_HALT];
	lp = new_list = (LIST *) chk_alloc(len);
	*lp = F_INT;
	LPUT32(lp, (long) sp->s_type);
	lp += sizeof_atoms[F_INT];
	*lp = sp->s_type;
	switch (*lp) {
	  case F_INT:
	  	LPUT32(lp, sp->s_int);
	  	break;
	  case F_FLOAT:
	  	LPUT_FLOAT(lp, sp->s_float);
	  	break;
	  case F_STR:
	  	*lp = F_RSTR;
		LPUT32(lp, (long) r_inc(sp->s_obj));
	  	break;
	  case F_LIST:
	  	*lp = F_RLIST;
		LPUT32(lp, (long) r_inc(sp->s_obj));
	  	break;
	  }
	lp += sizeof_atoms[*lp];
	*lp = F_HALT;
	acc_donate_list(new_list, len);
}
/**********************************************************************/
/*   Function  which  returns  pointer  to  a symbol table needed by  */
/*   debug support functions.					      */
/**********************************************************************/
SPTREE *
get_sym_level()
{
	/***********************************************/
	/*   Make  sure  we  have  a  level number as  */
	/*   the second arg.			       */
	/***********************************************/
	if (argv[2].l_flags != F_INT) {
		acc_assign_null();
		return NULL;
		}
	if (argv[2].l_int < 0)
		return gsym_tbl;
	if (argv[2].l_int > ms_cnt) {
		acc_assign_null();
		return NULL;
		}
	return lsym_tbl[mac_stack[argv[2].l_int].nesting_level];
}
		
void
do_profile()
{	

	if (argv[1].l_flags != F_NULL)
		pflag = argv[1].l_int & 0xffff;
	else
		pflag = !pflag;
	ewprintf("[Profiling %s]", pflag ? "ON" : "OFF");
}
void
trace_list(lp)
LIST	*lp;
{	extern int dflag;
	ref_t	*rp;
	
	if (dflag == 0)
		return;
	if (lp == NULL) {
		trace_ilog("nil\n");
		return;
		}
	trace_ilog("(");
	while (*lp != F_HALT) {
		switch (*lp) {
		  case F_INT:
			trace_log("%ld ", LGET32(lp));
			break;
		  case F_FLOAT: {
		  	double val;
			LGET_FLOAT(lp, &val);
			trace_log("%g ", val);
			break;
			}
		  case F_STR:
			trace_log("%s ", (char *) LGET32(lp));
			break;
		  case F_RSTR:
			rp = (ref_t *) LGET32(lp);
			trace_log("%s ", rp->r_ptr);
			break;
		  case F_LIT:
			trace_log("\"%s\" ", (char *) LGET32(lp));
			break;
		  case F_ID:
			trace_log("%s ", builtin[LGET16(lp)].name);
			break;
		  case F_RLIST:
		  	trace_log("ref(..) ");
			break;
		  case F_LIST: {
			trace_log("(..) ", (char *) NULL);
			lp += LGET16(lp);
			continue;
			}
		  case F_NULL:
		  	trace_log("NULL ");
			break;
		  default:
		  	trace_log("<dont-know> ");
			break;
		  }
		lp += sizeof_atoms[*lp];
		}
	trace_log(")\n", (char *) NULL);
}
/* VARARGS1 */
void
trace_ilog(str, ARGS)
char	*str;
long	ARGS;
{	int	i;
	char	buf[1024];
	static	int	old_level = -1;

	if (dflag == 0)
		return;
	if (old_level == nest_level)
		strcpy(buf, "\t");
	else
		sprintf(buf, "%02d:.....", nest_level);

	for (i = nest_level; i-- > 0; )
		strcat(buf, old_level == nest_level ? " " : ".");
	trace_log(buf);
	trace_log(str, ARGS);
	old_level = nest_level;
}
/* VARARGS1 */
void
trace_log(str, ARGS)
char	*str;
long	ARGS;

{	char	*cp;
	extern	int	dflag;

	if (dflag == 0)
		return;

	/***********************************************/
	/*   If  log  file not already open then open  */
	/*   it.  Allow  user to override name of log  */
	/*   file.				       */
	/***********************************************/
	if (debug_fp == NULL) {
		if ((cp = ggetenv("CRISP_LOG")) != NULL)
			crisp_log = cp;
		debug_fp = fopen(crisp_log, "w"); 
		}

	if (debug_fp) {
		(void) fprintf(debug_fp, str, ARGS);
		if (dflag & DB_FLUSH)
			fflush(debug_fp);
		}
}
void
list_macro(level, lp)
int	level;
LIST	*lp;
{	int	tokcnt = 0;

	for (; *lp != F_HALT && *lp != F_END; lp += sizeof_atoms[*lp]) {
		if (tokcnt++)
			trace_log(" ");
		if (*lp == F_INT)
			trace_log("%ld", LGET32(lp));
		else if (*lp == F_ID)
			trace_log("%s", builtin[LGET32(lp)].name);
		else if (*lp == F_STR) {
			char	*cp;
			char	*str = (char *) LGET32(lp);
			for (cp = str; *cp; cp++)
				if (*cp == '"' && cp != str)
					trace_log("\\\"");
				else if (*cp == '\r')
					trace_log("\\r");
				else if (*cp == '\n')
					trace_log("\\n");
				else if (*cp == '\t')
					trace_log("\\t");
				else
					trace_log("%c", *cp);
			if (str[0] == '"')
				trace_log("\"");
			}
		else if (*lp == F_LIST) {
			int	i;
			trace_log("\n    ");
			for (i = level; i-- > 0; )
				trace_log("    ");
			trace_log("(");
			list_macro(level+1, lp + sizeof_atoms[F_LIST]);
			trace_log(")");
			}
		}
	if (level == 0)
		trace_log("\n");
}
# if 0
void
dump_all_lines()
{
	BUFFER	*bp;
	LINE	*lp;
	int	line_no;
	char	buf[132];

	trace_log("Dump all Lines\n");
	for (bp = bheadp; bp; bp = bp->b_bufp) {
		trace_log("Buffer %s:\n", bp->b_fname);
		line_no = 0;
		for (lp = lforw(bp->b_linep); lp != bp->b_linep; 
		     lp = lforw(lp)) {
			(void) sprintf(buf, "\tLine %3d: used=%2d size=%2d fl=%d\n",
				++line_no, lp->l_used, lp->l_size,
				lp->l_flags);
			trace_log(buf);
			}
		trace_log("\n");
		}
}
# endif
# if 0
void
phex(str, n)
char	*str;
int	n;
{
	char	buf[256];
	int	i = 0;
	if (dflag == 0)
		return;

	buf[0] = NULL;
	while (n-- > 0) {
		(void) sprintf(buf+strlen(buf), "%02x ", *str++);
		if (++i >= 16 || n <= 0) {
			strcat(buf, "\n");
			trace_log("%s", buf);
			i = 0;
			buf[0] = NULL;
			}
		}
}
# endif
# if 0
void
print_line(lp)
LINE	*lp;
{	char	buf[128];
	sprintf(buf, "%08lx:	used=%d size=%d tell=%ld fl=%d ", 
		lp, lp->l_used, lp->l_size, lp->l_tell, lp->l_flags);
	if (lp->l_flags & L_INCORE)
		strcat(buf, "INCORE ");
	if (lp->l_flags & L_LOCKED)
		strcat(buf, "LOCKED");
	strcat(buf, "\n");
	trace_log(buf);
}
# endif
/**********************************************************************/
/*   Function to trace assignments to symbols.			      */
/**********************************************************************/
void
trace_sym(sp)
SYMBOL	*sp;
{
	if (dflag == 0)
		return;

	trace_ilog("  %s := ", sp->s_name);
	trace_sym1(sp);
}
/**********************************************************************/
/*   Function to trace references to a symbol.			      */
/**********************************************************************/
void
trace_sym_ref(sp)
SYMBOL	*sp;
{
	if (dflag == 0)
		return;

	trace_ilog("  lookup %s = ", sp->s_name);
	trace_sym1(sp);
}
/**********************************************************************/
/*   Print value of a symbol.					      */
/**********************************************************************/
static void
trace_sym1(sp)
SYMBOL	*sp;
{
	switch (sp->s_type) {
	  case F_INT:
		trace_log("%ld\n", sp->s_int);
		break;
	  case F_STR:
	  case F_RSTR:
		trace_log("'%s'\n", c_string(sp->s_obj->r_ptr));
		break;
	  case F_LIST:
	  	if (sp->s_obj)
			trace_list((LIST *) sp->s_obj->r_ptr);
		break;
	  case F_NULL:
	  	trace_log("NULL\n");
		break;
	  case F_FLOAT:
	  	trace_log("%f\n", sp->s_float);
	  	break;
	  default:
	  	trace_log("unknown type");
	  }
}
void
trace_trigger(type)
int	type;
{
	static	char	*triggers[] = {
		"REG_TYPED",
		"REG_EDIT",
		"REG_ALT_H",
		"REG_UNASSIGNED",
		"REG_IDLE",
		"REG_EXIT",
		"REG_NEW",
		"REG_CTRLC",
		"REG_INVALID",
		"REG_INTERNAL",
		"REG_MOUSE",
		"REG_PROC_INPUT",
		"REG_KEYBOARD"
		};
	trace_log("*** TRIGGER=%s ***\n", triggers[type]);
}
void
trace_refs()
{
	BUILTIN	*bp;
	char	buf[128];

	if (dflag == 0)
		return;
	for (bp = builtin; bp < &builtin[sizeof_builtin]; bp++) {
		sprintf(buf, "%5ld	%s\n", bp->reference, bp->name);
		trace_log(buf);
		}
}
char *
c_string(str)
register char	*str;
{
	static char buf[256];
	register char *bp = buf;
	if (dflag == 0)
		return str;
	while (*str) {
		if (*str == '\n')	*bp++ = '\\', *bp++ = 'n';
		else if (*str == '\r')	*bp++ = '\\', *bp++ = 'r';
		else if (*str == '\t')	*bp++ = '\\', *bp++ = 't';
		else if (*str == '\\')	*bp++ = '\\', *bp++ = '\\';
		else
			*bp++ = *str;
		str++;
		if (bp > &buf[sizeof buf - 10]) {
			*bp++ = '.';
			*bp++ = '.';
			*bp++ = '.';
			break;
			}
		}
	*bp = NULL;
	return buf;
}
