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

int	user_buf = -1;	/* Buffer containing users errors */
int	compile_buf = -1;
int	err_line_no = 1;
string	compile_filename;
/**********************************************************************/
/*   List  of  macros  from the .crinit file passed on startup so we  */
/*   can make sure we save them on exit.			      */
/**********************************************************************/
list	load_list;
int	load_index;
/**********************************************************************/
/*   Macro  called  if  user  puts a 'load:' entry in the init file.  */
/*   We  remember  the  argument  passed to us and then call load to  */
/*   load up any private startup macros.			      */
/**********************************************************************/
void
set_load(string arg)
{
	load_list += arg;
	load(arg);
	execute_macro(arg);
}
/**********************************************************************/
/*   Macro  called  on  exit  to put back the list of startup macros  */
/*   we were passed on startup.					      */
/**********************************************************************/
string
get_load()
{
	return load_list[load_index++];
}
/**********************************************************************/
/*   Macro to compile and load a macro in the current buffer.	      */
/**********************************************************************/
void
load()
{
	string filename, ext, buf, error_file, s;
	string	compiled_file;
	int	i;
	int	oldbuf, mtime1, mtime2;

	/***********************************************/
	/*   If  we  get  passed a filename argument,  */
	/*   then  try  and  find  the macro the user  */
	/*   is referring to.			       */
	/***********************************************/
	if (get_parm(0, filename) > 0) {
		s = find_macro(filename);
		if (s == "") {
			error("File %s not found.", s);
			return;
			}
		filename = s;
		}
	else {
		if (inq_modified())
			write_buffer();
		inq_names(filename);
		}
	i = rindex(filename, ".");
	ext = substr(filename, i+1);
	filename = substr(filename, 1, i-1);
	/***********************************************/
	/*   If   its  a  .m  file  we  can  load  it  */
	/*   directly in source code form.	       */
	/***********************************************/
	err_line_no = 1;
	switch (ext) {
	  case "m":
	  	cm();
		return;
	  case "cm":
	  	load_macro(filename);
		return;
	  case "cr":
	  	break;
	  default:
		error("Dont know how to compile .%s files.", ext);
		return;
	  }
	/***********************************************/
	/*   Look  to  see  if  we have an up to date  */
	/*   compiled file and load that instead.      */
	/***********************************************/
	compiled_file = filename + ".cm";
	if (exist(compiled_file)) {
		file_pattern(filename + "." + ext);
		find_file(NULL, NULL, mtime1);
		file_pattern(compiled_file);
		find_file(NULL, NULL, mtime2);
		if (mtime1 < mtime2) {
			load_macro(filename);
			message("Macro successfully loaded.");
			return;
			}
		}
	sprintf(error_file, "/tmp/cr%d.errs", getpid());
	sprintf(buf, "crunch -e %s %s.cr", error_file, filename);
	message("crunch %s.cr", filename);

	if (shell(buf, 1) == 0) {
		load_macro(filename);
		remove(error_file);
		message("Macro successfully loaded.");
		return;
		}

	oldbuf = inq_buffer(error_file);
	if (oldbuf)
		delete_buffer(oldbuf);
	edit_file(error_file);
	remove(error_file);
	compile_buf = inq_buffer();
	default_next_error();
}
void
make(string arg)
{
	int	curwin,
		curbuf,
		win,
		wait_stat,
		line, col;
	string	s;

	err_line_no = 1;

	curwin = inq_window();
	curbuf = inq_buffer();
	win = sized_window(12, 66, "");
	set_window(win);
	switch_to_compile_buffer();
	attach_buffer(compile_buf);

	connect(0x8001, "/bin/sh");
	refresh();
	wait_for(10, "\$");
	insert("exec make " + arg + "\n");
	inq_position(line, col);
	set_process_position(line, col);
	insert_process("exec make " + arg + "\n");
	refresh();
	/*----------------------------------------
	/*   Wait for process to die.
	/*----------------------------------------*/
	wait(wait_stat);
	/***********************************************/
	/*   Let   user   see   end  of  make  before  */
	/*   blasting it away.			       */
	/***********************************************/
	get_parm(NULL, s, "Make completed - press key to continue: ");

	/***********************************************/
	/*   Tidy windows & buffers up.		       */
	/***********************************************/
	delete_window();
	set_buffer(curbuf);
	set_window(curwin);
	/*----------------------------------------
	/*   If any errors occured, display first one screen.
	/*----------------------------------------*/
	assign_to_key("<Ctrl-N>", "default_next_error");
	assign_to_key("<Ctrl-P>", "default_prev_error");
	if (wait_stat)
		default_next_error();
	else
		message("Make completed successfully.");
}
/**********************************************************************/
/*   Create  a  compile  buffer if its not already been created, and  */
/*   clear it if necessary.					      */
/**********************************************************************/
void
switch_to_compile_buffer()
{
	if (compile_buf >= 0) {
		set_buffer(compile_buf);
		clear_buffer();
		return;
		}
	compile_buf = create_buffer("Compile-Buffer", NULL, 0);
	set_buffer(compile_buf);
	/***********************************************/
	/*   Dont   save  undo  information  for  the  */
	/*   compile buffer.			       */
	/***********************************************/
	set_buffer_flags(NULL, BF_NO_UNDO);
}
/*************************************************************
/*		Object routines for finding errors.
/*************************************************************/
list	cc_errors = {
 /* PCC: "fred.c", line 49: ... */
{"<\"\\c[^():,\"]+\"", 	1,  "[0-9]+:", 	1},

/* cpp: fred.c: 49: ...	  */
{"<[^():]+:", 		1,  "[0-9]+:", 	1}, 

/* SUNOS lint: fred.c(49): ... */
{"<[^(]+(",		1,  "[0-9]+):",	2},  

/* Xenix cc: fred.c(49) : ... */
{"<[^(]+(",		1,  "[0-9]+) :",3}  
		};
void
default_next_error()
{
	next_error(
"<{\"*\", line [0-9]+:}|{*:[0-9]+:[ \t]}|{*([0-9]+) @:}",
		cc_errors);
}

/***********************************************************/
/*							   */
/*  Function:  errors   				   */
/*							   */
/*  Description:					   */
/*							   */
/*   Given   a   buffer  containing  the  output  of  a    */
/*   compiler   (error  messages  and  warnings),  move    */
/*   cursor  to  next  error  in file and display error    */
/*   message on bottom of screen.			   */
/*   							   */
/*   When  called  the  first time, assumes the current    */
/*   buffer  contains  error  messages  and  saves  the    */
/*   buffer  number  for  this one. Subsequent calls to    */
/*   this function go back to that old buffer.		   */
/*							   */
/*							   */
/***********************************************************/
void
errors()
{
	int	old_compile_buf = compile_buf;
	
	if (user_buf == -1) {
		user_buf = inq_buffer();
		assign_to_key("<Ctrl-N>", "errors");
		assign_to_key("<Ctrl-P>", "errors");
		}
		
	compile_buf = user_buf;
	default_next_error();
	compile_buf = old_compile_buf;
}
/*************************************************************
/*
/*	lint macro. This macro expects there to be a makefile entry for
/* lint, so that we can automatically generate the output via a
/* 'make lint'. The output is then massaged so that the output from
/* lint looks like the normal C compiler error messages, which allows
/* the display error macro to then be used to skip from one error to
/* the next.
/*
/*************************************************************/
void
lint()
{
	string	filename,
		warning;

	/*----------------------------------------
	/*   Do a make lint.
	/*----------------------------------------*/
	make("lint");

	set_buffer(compile_buf);

	/*----------------------------------------*/
	/*   Convert all lines of the form:
	/*		28) (31) (45) ...
	/*   have each '(..)' start on a separate line so we
	/*   can then prefix each line with the file name.
	/*----------------------------------------*/
	message("Putting errors in separate lines.");
	top_of_buffer();
	while (re_search(NULL, "^[ \t]+([0-9]+)  \t") > 0) {
		drop_anchor(MK_LINE);
		re_translate(SF_GLOBAL | SF_BLOCK, ")", ")\n");
		raise_anchor();
		delete_line();
		}
	/*----------------------------------------*/
	/*   Now take lines of the form:
	/*    alarm   	llib-lc(15) :: clock.c(45)
	/*   and move the file-name/line-no to the start of the line,
	/*   append the lint error occuring before this block.
	/*----------------------------------------*/
	message("Massaging lint summary errors.");

	top_of_buffer();
	while (re_search(NULL, " :: ") > 0) {
		up();
		beginning_of_line();
		warning = ltrim(trim(read()));
		down();
		while (read(1) == " ") {
			re_translate(NULL, "{<*}\t*:: {*$}", "\\1: \\0 - ");
			end_of_line();
			insert(warning);
			next_char();
			}
		down();
		beginning_of_line();
		}
	/*----------------------------------------*/
	/*   Delete all leading spaces/tabs
	/*----------------------------------------*/
	message("Removing leading white space.");
	top_of_buffer();
	re_translate(SF_GLOBAL, "^[ \t]+", "");
	/*----------------------------------------*/
	/*   Now suffix all of the above lines [ (..) ] with the
	/*   warning message that preceeds them.
	/*----------------------------------------*/
	message("Putting error messages on each line.");
	top_of_buffer();
	while (re_search(NULL, "^warning") > 0) {
		warning = trim(read());
		/*----------------------------------------*/
		/*   Look at following lines for lines beginning with '('
		/*----------------------------------------*/
		down();
		while (read(1) == "(") {
			end_of_line();
			insert(warning);
			beginning_of_line();
			down();
			}
		}
	/*----------------------------------------*/
	/*   Ensure all lines with errors/warnings put the filename
	/*   at the beginning of the line.
	/*----------------------------------------*/
	message("Making errors into a normalised format.");
	top_of_buffer();
	while (re_search(NULL, "^==============$") > 0) {
		up();
		filename = trim(ltrim(compress(read())));
		move_rel(2, 0);
		if (index(filename, " "))
			filename = substr(filename, 1, index(filename, " ") - 1);
		save_position();
		drop_anchor(MK_LINE);
		if (re_search(NULL, "^==============$") <= 0) 
			end_of_buffer();
		re_translate(SF_GLOBAL | SF_BLOCK, "<{([0-9]+)}", filename + "\\0:");
		raise_anchor();
		restore_position();
		}
	/*----------------------------------------*/
	/*   Trim down some of the error messages.
	/*----------------------------------------*/
	message("Tidying errors up.");
	top_of_buffer();
	re_translate(SF_GLOBAL, "( number ) ", "");
	top_of_buffer();
	re_translate(SF_GLOBAL, "( arg {[0-9]} )", "(\\0)");
	/*----------------------------------------*/
	/*   Display first error.
	/*----------------------------------------*/
	err_line_no = 1;
	default_next_error();
}
void
next_error(string regexp, list token_list)
{

	display_error(regexp, token_list);
}
void
display_error(string regexp, list token_list)
{
	string	filename_pat,
		line_no_pat,
		line;
	int	curbuf,
		filename_trail,
		line_no_trail,
		line_no,
		length,
		n,
		offset;
	list	re_list;

	curbuf = inq_buffer();
	set_buffer(compile_buf);

	goto_line(err_line_no);
	if (re_search(NULL, regexp) <= 0) {
		err_line_no = 1;
		message("No more errors.");
		set_buffer(curbuf);
		return;
		}
	inq_position(err_line_no);
	++ err_line_no;

	beginning_of_line();
	line = trim(ltrim(compress(read())));

	/*----------------------------------------
	/*   Scan token_list trying to find an entry which matches the
	/* filename.
	/*----------------------------------------*/
	n = 0;
	offset = -1;
	while (n < length_of_list(token_list)) {
		re_list = token_list[n];
		filename_pat = re_list[0];
		filename_trail = re_list[1];
		line_no_pat = re_list[2];
		line_no_trail = re_list[3];
		offset = re_search(NULL, filename_pat, line, NULL, length);
		++ n;
		if (offset > 0 && re_search(NULL, line_no_pat, line))
			break;
		}
	set_buffer(curbuf);
	/*----------------------------------------
	/*   Extract file-name.
	/*----------------------------------------*/
	if (filename_trail >= 0) {
		if (offset <= 0) {
			error("Couldn't locate file-name");
			return;
			}
		compile_filename = substr(line, offset, length - filename_trail);
		}

	/*----------------------------------------
	/*   Extract line number.
	/*----------------------------------------*/
	offset = re_search(NULL, line_no_pat, line, NULL, length);
	if (offset <= 0) {
		error("Couldn't locate line_no");
		return;
		}

	line_no = atoi(substr(line, offset, length - line_no_trail));

	/*----------------------------------------
	/*   Trim off filename & line no at start of line.
	/*----------------------------------------*/
	line = substr(line, offset + length);

	/*----------------------------------------
	/*   Goto specified file & line no.
	/*----------------------------------------*/
	if (!exist(compile_filename)) {
		error("%s: %s", compile_filename, ltrim(line));
		return;
		}
	edit_file(compile_filename);
	goto_old_line(line_no);
	beginning_of_line();

	/*----------------------------------------
	/*   Now display error -- this ensures that error appears
	/*   last on the display, rather than any filename stuff
	/*   generated by the (edit_file) macro above.
	/*----------------------------------------*/
	error("%s", ltrim(line));
	search_hilite(strlen(read()));
}
