% -*- SLang -*-
%
% run compiler in a subshell and/or parse error messages
%
% Changes made by Alexander Demenshin <aldem@barnet.kharkov.ua>
%
% Public functions:
%   compile_parse_errors		parse next error
%   compile_previous_error		parse previous error
%   compile_parse_buf    		parse current buffer as error messages
%   compile			        run program and parse it output
%   compile_select_compiler             set compiler for parsing error messages
%   compile_add_compiler                add a compiler to database
%
% Public variables:
%   Compile_Default_Compiler
%
% The file also contains a database for various compilers.
%---------------------------------------------------------------------------

% These variables are public because they are used by acompile.sl
variable Compile_Output_Buffer = "*shell-output*";
if (is_defined ("get_process_input"))
{
   Compile_Output_Buffer = "*compile*";
}
variable Compile_Line_Mark = 0;

static variable Compile_Src_Dir = Null_String;
static variable Error_Regexp;

%
%  These variables are used when parsing GNU's Make output (directory changes).
%  I don't know what kind of output generated by other Make, so substitute
%  it if needed. <aldem>
%
#ifdef UNIX
static variable Compile_Dir_Enter = "^g?make\\[\\d+\\]: Entering directory `\\(.+\\)'";
static variable Compile_Dir_Leave = "^g?make\\[\\d+\\]: Leaving directory `\\(.+\\)'";

static define compile_parse_make_chdir ()
{
   variable beg_mark, end_mark;
   variable end_line;

   push_spot ();
   EXIT_BLOCK
     {
	pop_spot ();
     }

   beg_mark = create_user_mark ();
   end_mark = create_user_mark ();

   forever
     {
	goto_user_mark (end_mark);

	end_line = 0;

	if (re_bsearch (Compile_Dir_Leave))
	  {
	     if (up_1 ())
	       end_line = what_line ();

	     move_user_mark (end_mark);
	  }

	goto_user_mark (beg_mark);

	!if (up_1 ()) return Null_String;

	if (re_bsearch (Compile_Dir_Enter))
	  {
	     if (not(end_line)
		 or (what_line () > end_line))
	       break;
	     move_user_mark (beg_mark);
	  }
	else return Null_String;
     }
   regexp_nth_match (1);
}

#endif

static define compile_find_file (file, line)
{
#ifdef UNIX
   variable dir;

   dir = compile_parse_make_chdir ();
   if (strlen (dir) and (file[0] != '/'))
     file = dircat (dir, file);
#endif

   if (1 != file_status (file))
     {
	file = Compile_Src_Dir + file;
	while (1 != file_status (file))
	  {
	     file = read_file_from_mini ("Find this file's errors:");
	  }
     }

   Compile_Src_Dir = path_dirname (file);

   () = find_file (file);
   widen_buffer ();
   goto_line (line);
   bol_skip_white ();
}

static define compile_parse_errors_dir (next_error_fun, next_line_fun)
{
   variable cbuf, obuf = Compile_Output_Buffer;
   variable line, file;

   Compile_Line_Mark = 0;

   !if (bufferp(obuf))
     {
	flush ("Did you compile?");
	return;
     }

   if (MINIBUFFER_ACTIVE) return;

   cbuf = pop2buf_whatbuf (obuf);

   if (@next_error_fun ())
     {
	file = regexp_nth_match (1);	% file name
	line = regexp_nth_match (2);	% line number (string)
	!if (strlen (line)) return;

	bol();
	Compile_Line_Mark = create_line_mark (3);

	@next_line_fun ();

	compile_find_file (file, integer (line));
	cbuf = whatbuf ();
	sw2buf (obuf);
     }

   pop2buf (cbuf);
}

static define compile_find_next_error_fun ()
{
   eol ();
   if (eobp ())
     {
	message ("No more errors!");
	return 0;
     }

   bol ();
   !if (re_fsearch (Error_Regexp))
     {
	eob ();
	return 0;
     }

   1;
}

static define compile_find_prev_error_fun ()
{
   bol ();
   if (bobp ())
     {
	message ("No more errors!");
	0;
     }

   !if (re_bsearch (Error_Regexp))
     {
	bob ();
	return 0;
     }

   1;
}

public define compile_parse_errors ()
{
   compile_parse_errors_dir (&compile_find_next_error_fun, &go_down_1);
}

public define compile_previous_error ()
{
   compile_parse_errors_dir (&compile_find_prev_error_fun, &bol);
}

public define compile ()
{
   variable b, n;
   variable cmd = NULL;
   
   if (_NARGS != 0)
     cmd = ();

   b = whatbuf();
   call ("save_buffers");

   if (cmd == NULL) do_shell_cmd ();
   else shell_perform_cmd (cmd);

   bob();
   pop2buf(b);

   compile_parse_errors ();
}

%
%  Parse current buffer as error output
%
public define compile_parse_buf ()
{
   Compile_Output_Buffer = whatbuf();
   bob ();
   compile_parse_errors ();
}

$1 = "acompile.sl";
if (is_defined ("get_process_input"))
{
   () = evalfile ($1);
}


% The current implementation for the database uses an associative array.
static variable Compiler_Database = Assoc_Type [String_Type, ""];

public define compile_select_compiler (name)
{
   variable c;
   c = Compiler_Database[name];
   !if (strlen (c))
     verror ("Compiler %s is not supported.  See compile.sl for more information", name);
   Error_Regexp = c;
}

public define compile_add_compiler (name, regexp)
{
   Compiler_Database [name] = regexp;
}

%---------------------------------------------------------------------------
% Compiler database
%---------------------------------------------------------------------------
%Borland bcc/tcc compilers
%Error foo.c 4: Undefined symbol 'x' in function main
%Warning foo.c 34: Possible use of 'y' before definition in function main
compile_add_compiler ("bcc", "^[EW][a-r]+ \\(.+\\) \\(\\d+\\):");
compile_add_compiler ("tcc", "^[EW][a-r]+ \\(.+\\) \\(\\d+\\):");
%--------------------------------------------------------------------------
%Ultrix cc compiler
%ccom: Error: t.c, line 14: LC_ALL undefined
compile_add_compiler ("ultrix_cc", "Error: +\\(.+\\)\\\", line \\(\\d+\\):");
%--------------------------------------------------------------------------
%hp cc compiler
%cc: "t.c", line 3: error 1588: "ddkldkjdldkj" undefined.
compile_add_compiler ("hp_cc", "^cc: +\\\"\\(.+\\)\\\", line \\(\\d+\\):");
%--------------------------------------------------------------------------
%Sun acc compiler
%"filename.c", line 123: error: buffer undefined
%"filename.c", line 123: warning: fin not used
compile_add_compiler ("sun_acc", "^\\\"\\(.+\\)\\\", line \\(\\d+\\):");
%--------------------------------------------------------------------------
%AIX compiler, which may be referenced under any of these names.
%The Fortran compiler has the same format, so allow that too
%"foo.c", line 13.4: 1506-045 (S) Undeclared identifier bar.
%"foo.f", line 21.20: 1515-019 (S) Syntax is incorrect.
%@aix;
%@xlc;
%@xlf;
compile_add_compiler ("aix", "^\\\"\\(.+\\)\\\", line \\(\\d+\\)\\.");
compile_add_compiler ("xlc", "^\\\"\\(.+\\)\\\", line \\(\\d+\\)\\.");
compile_add_compiler ("xlf", "^\\\"\\(.+\\)\\\", line \\(\\d+\\)\\.");
%--------------------------------------------------------------------------
%The GNU cc compiler
%cmds.c:33: warning: initialization of non-const * pointer...
%cmds.c:1041 (cmds.o): Undefined symbol _Screen_Height referenced...
%In file included from /usr/local/src/jed/src/xterm.c:10:
compile_add_compiler ("gcc", "^\\([^ ]+\\):\\(\\d+\\) ?(?.*)?:");
%--------------------------------------------------------------------------
%The WATCOM compiler wcc
%keymap.c(71): Error! E1011: Symbol 'show_memory' has not been declared
%event.c(22): Warning! W202: Symbol 'xx' has been defined, but not referenced
%Warning(1028): PhGetMsgSize_ is an undefined reference
%file event.o(/home/qnx/rwm/photon/event.c): undefined symbol PhAttach_
compile_add_compiler ("wcc", "^\\(.+\\)(\\(\\d+\\)): [EW].+[rg]! [EW]\d+:.*");
%--------------------------------------------------------------------------
%The Java compiler javac
%Test.java:151: Method getNumber() not found in class java.lang.String.
%@javac;
compile_add_compiler ("javac", "^\\(.+\\):\\(\\d+\\):");
%--------------------------------------------------------------------------
%Microsoft Visual C
%cob.cpp(30) : warning C4091: no symbols were declared
%cob.cpp(32) : error C2665: 'COBFileHeader::COBFileHeader' : none of the
%2 overloads can convert parameter 1 from type 'char [34]' (new behavior;
%please see help)
%cob.cpp(38) : warning C4091: no symbols were declared
%cob.cpp(45) : warning C4091: no symbols were declared
%cob.cpp(50) : error C2239: unexpected token '{' following declaration of
%'COBChunkHead'
%@vc;
compile_add_compiler ("vc", "^\\(.+\\)(\\(\\d+\\)) : [ew].+:.*");
%---------------------------------------------------------------------------
% End of data base
%---------------------------------------------------------------------------

%!%+
%!%\variable{Compile_Default_Compiler}
%!%\usage{variable Compile_Default_Compiler = "gcc";}
%!%\description
%!%  This variable specifies the default compiler to be assumed when parsing
%!%  error messages in the compile buffer.  If not set, "gcc" is assumed.
%!%  Currently supported compilers include:
%!%#v+
%!%      gcc              (GNU C Compiler)
%!%      bcc              (Borland C Compiler)
%!%      tcc              (Turbo C Compiler)
%!%      ultrix_cc        (Ultrix C Compiler)
%!%      hp_cc            (HP C compiler)
%!%      sun_acc          (Sun ANSI C compiler)
%!%      aix, xlc, xlf    (Various AIX C compilers)
%!%      wcc              (Watcom C compiler)
%!%      javac            (Java Compiler)
%!%      vc               (Microsoft Visual C)
%!%#v-
%!%\notes
%!%  The primary purpose of this variable is to select a compiler prior to 
%!%  loading compile.sl.  Once compile.sl has been loaded, the value of this
%!%  variable has no effect.  To switch compilers, the \var{compile_select_compiler}
%!%  function must be used.
%!%\seealso{compile_select_compiler, compile_add_compiler}
custom_variable ("Compile_Default_Compiler", "gcc");

compile_select_compiler (Compile_Default_Compiler);
