/* ** BRIEF -- Basic Reconfigurable Interactive Editing Facility ** ** Written by Dave Nanian and Michael Strickman. */ /* Revision history: ** ----------------- ** 1) IO 29/01/90 ** . Added 0-9 to some of the searches so that they cope with ** numbers in file extensions (which is legal) */ /* ** errorfix.cb: ** ** This file contains the main driver and support macros for BRIEF's ** error location facility. */ #define TRUE 1 #define FALSE 0 #define WARNING 1 #define ERROR 2 #define DISPLAY_NEXT_ERROR 0 #define POP_UP_ERROR_WINDOW 1 #define PROBE_AND_DISPLAY 2 #define JUST_PROBE 3 string add_to_path (string path, string file_name); string escape_re (string original, ~string); void center_line (void); void _exit (); int get_error_file (string full_name); int find_error_file (string full_name); int delete_error_file (string full_name, int delete_buffer_too); void add_error_file (string full_name); int next_error (~int action); int _call_next (string error_extension, string file_name); int _call_prev (string error_extension, string file_name); int _next_error (~string extension); int _prev_error (~string extension); int _error_info (string error_file, int, int, string, string file_name); int _error_buf_id, // Holds the error buffer. _cancel_errorfix; // Flag to show whether or not Cancel done from menu. /* ** _init: ** ** This macro gets run when the macro is loaded. It creates the ** error buffer, and sets _error_buf_id to the error buffer id. */ void _init (void) { _error_buf_id = create_buffer ("Error Stack", NULL, 1); } /* ** compile_it ** ** This is a replacement compile macro. It first deletes the error ** file, if one exists. The file name is also removed from the error ** stack, and then the actual compilation is executed. ** ** Returns: ** ** Result of the original compile macro. */ replacement compile_it () { string file_name; // Name of the file that's being compiled. inq_names (file_name); /* ** Before compiling the file, we ensure that its error file ** has been removed from the error stack and the buffer has been ** deleted from memory. This ensures the file can be written ** by the compiler, and is up to date. */ delete_error_file (file_name, TRUE); returns (compile_it ()); } /* ** get_error_file: ** ** This routine obtains the most recent error file name, which is ** located at the top of the error stack file. It returns the name ** in the passed parameter. ** ** Returns: ** ** TRUE if a file was obtained from the error stack ** FALSE if a file could not be obtained from the error stack */ int get_error_file (string full_name) { int old_buf_id = set_buffer (_error_buf_id), loc; string line; move_abs (1, 1); if ((full_name = read ()) != "\n") { /* ** Trim is used to remove the newline. */ full_name = trim (full_name); } else full_name = ""; put_parm (0, full_name); set_buffer (old_buf_id); returns (full_name != ""); } /* ** find_error_file: ** ** This routine finds the location of the given file and path ** in the error stack file. The current line in the error stack ** is left at the found line. ** ** ** Returns: ** ** 0 if the file name and path are not found. ** Otherwise, the line number of the file path in the error stack file. */ int find_error_file (string full_name) { int old_buf_id = set_buffer (_error_buf_id), error_file_line; top_of_buffer (); /* ** Since the full_name includes the drive letter, we don't need ** to worry about embedded paths (i.e., accidentally finding ** "\foo\foo\foo.c" instead of "\foo\foo.c"). */ if (search_fwd (full_name + "\n", 0)) inq_position (error_file_line); set_buffer (old_buf_id); returns (error_file_line); } /* ** delete_error_file: ** ** This routine finds and if found, deletes, the given file and path ** from the error stack file. */ int delete_error_file (string full_name, int delete_buffer_too) { int ret_code, old_buf_id = set_buffer (_error_buf_id), buffer_id, loc; if (ret_code = find_error_file (full_name)) delete_line (); /* ** If there is already a buffer for the error file, we ** "create" it (create_buffer returns the ID of a buffer ** that already existed) and then delete it immediately. ** Note that under some very obscure circumstances, the ** create_buffer call could fail. If it does, it'll ** return 0, which is an invalid buffer id. We check for ** this case since delete_buffer does not. */ loc = rindex (full_name, substr (full_name, 3, 1)); if (rindex (substr (full_name, loc + 1), ".")) full_name = substr (full_name, 1, rindex (full_name, ".")) + "err"; else full_name += ".err"; if (delete_buffer_too && (buffer_id = create_buffer ("Error File", full_name, 1)) && !inq_views (buffer_id)) delete_buffer (buffer_id); set_buffer (old_buf_id); returns (ret_code); } /* ** add_error_file: ** ** This routine adds an error file to the error stack file. ** The given file name (and its path) is inserted at the top of the ** error stack file. A call is made to delete_error_file, to find ** and delete a previous occurrence of this file name in the error ** stack file (if it exists). */ void add_error_file (string full_name) { int old_buf_id = set_buffer (_error_buf_id); delete_error_file (full_name, FALSE); top_of_buffer (); insert (full_name + "\n"); set_buffer (old_buf_id); } /* ** fix_error_name: ** ** This routine constructs the necessary names from the given file_name, ** and checks to see if we've already attached the error file to another ** file. If so, we return that file's name rather than the name passed. ** This ensures that, when you've got an include file and a source file with ** the same name, you keep the error file attached to the source. ** */ void fix_error_name (string file_name, string error_file, string error_extension) { int loc, old_buf_id = set_buffer (_error_buf_id); /* ** This loop'll go through once for an unaliased error file, and twice ** if we find it attached to another buffer. In essence, it's only a ** loop to save having to repeat the parsing code. */ for (;;) { top_of_buffer (); loc = rindex (file_name, substr (file_name, 3, 1)); if (rindex (substr (file_name, loc + 1), ".")) { error_extension = substr (file_name, loc = rindex (file_name, ".")); error_file = substr (file_name, 1, loc - 1); } else { error_file = file_name; error_extension = "."; } if (!search_fwd (file_name + "\n", 0) && search_fwd ("<" + escape_re (error_file) + "[~/\\\\\n]@>")) file_name = trim (read ()); else break; } put_parm (0, file_name); put_parm (1, error_file + ".err"); put_parm (2, error_extension); } /* ** next_error: ** ** This routine is the engine for BRIEF's error location facility. It ** reads in the error file and calls the appropriate routines to display ** either an individual error message or a window full of error message ** information. ** ** If no parameter (or zero) is passed to next_error, the error file is ** read into a buffer and searched for error messages. If the buffer is ** empty, the message "No errors." is displayed and the error buffer is ** removed. If the buffer is not empty, and a message can be located, the ** text of the message is displayed on the message line, the cursor is placed ** on the first non-blank character of the line containing the error, and that ** line is centered in the window. If no messages can be located, the error ** file is presented in a pop-up window. ** ** If a "1" is passed to next_error, the error information is displayed in ** a window, with the current message highlighted. If no messages could be ** recognised, the highlight is not displayed, and the cursor can be moved ** anywhere within the file. ** ** If a "2" is passed to next_error, the error file is tested to see if ** any messages can be recognised. If so, the same action is taken as with ** next_error 0. If not, no message is displayed and the error file buffer ** is deleted. ** ** If a "3" is passed, a test is done and a return code is returned, but ** no message is displayed in either case. ** ** In all cases, next_error returns 0 if no error was found, and non-zero ** if there was an error. ** ** If an error file is not found for the current file, then the error ** stack is accessed to obtain the error file accessed most recently. */ int next_error (~int action) { int error_buf, old_buf_id = inq_buffer (), line, col, loc, attempts, error_type, more_errors, prev_errors, errors_exist, total_error_lines, windowed; string error_msg, error_file, error_file_name, curr_file_name, buf_name, parms, error_extension; _cancel_errorfix = FALSE; inq_names (error_file_name, NULL, buf_name); curr_file_name = error_file_name; /* ** Find the error file for the current buffer. If one does ** not exist, then get the most recently accessed error file ** from the error stack. */ while (TRUE) { /* ** From the current error_file_name, construct the error_file and ** error_extension (which is either the error_file_name's extension ** or "." if there is no extension). If there's a similar file ** already in the error list, use it instead. */ fix_error_name (error_file_name, error_file, error_extension); if (!(error_buf = create_buffer ("Error File", error_file, TRUE))) { /* ** Note that we could have gone through this list a number ** of times before this failure happens, so we must ensure the ** current buffer is reset. */ set_buffer (old_buf_id); return (FALSE); } set_buffer (error_buf); /* ** errors_exist is set to TRUE if there is data in the file. ** We assume that an empty file means no file, and that a file ** with data in it is worth looking into. */ save_position (); top_of_buffer (); errors_exist = end_of_buffer (); inq_position (total_error_lines); restore_position (); if (!errors_exist) { /* ** If there aren't any errors in the file, we need to pop the ** buffer off the error stack (if it's there), and check the one ** at the top of the stack. If there isn't one, we're done. ** ** The attempts variable basically keeps track of the number ** of buffers we've examined that were actually on the error ** stack. This lets us distinguish between the "No errors" ** and the "No more errors" cases. */ attempts += delete_error_file (error_file_name, TRUE); /* ** If we're just supposed to check whether or not there are ** errors in the current file, we stop here. */ if (action >= PROBE_AND_DISPLAY) break; } else { /* ** Now that we know the file isn't empty, we check to ** see if there are "real" errors in the file. This is done ** with the next/prev error routine appropriate to the file's ** extension. We set the more_errors and prev_errors variables ** based on this test, and reset the errors_exist variable based ** on both of them. */ add_error_file (error_file_name); parms = error_extension + " " + buf_name; windowed = (action == POP_UP_ERROR_WINDOW); save_position (); more_errors = execute_macro ("_call_next " + parms); if (more_errors && windowed) execute_macro ("_call_prev " + parms); else { down (); save_position (); prev_errors = execute_macro ("_call_prev " + parms); restore_position (); } restore_position (); errors_exist = (more_errors || prev_errors); break; } /* ** If there are any more error files on the error stack, we check ** them out the same way. */ if (!get_error_file (error_file_name)) { if (attempts == 0) { set_buffer (old_buf_id); message ("No errors."); return (0); } break; } more_errors = prev_errors = FALSE; } /* ** If we're supposed to pop up a window, or if the file isn't ** empty and we don't recognize any error messages, we pop up an ** error window. In the latter case, we do it so the user can ** see what's going on. */ if (windowed = (action < PROBE_AND_DISPLAY && (windowed || !errors_exist))) { int lines, cols; keyboard_push (); assign_to_key ("", "_exit"); assign_to_key ("", "_error_cancel"); assign_to_key ("", "left"); assign_to_key ("", "right"); assign_to_key ("", "beginning_of_line"); assign_to_key ("", "end_of_line"); /* ** If there are recognizable errors in the file, we assign the ** up and down keys to the error search functions. Otherwise, we ** just move up and down normally. This allows the user to browse ** the file even though we don't know what's going on. */ if (errors_exist) { save_position (); drop_anchor (3); assign_to_key ("", "_call_prev " + parms); assign_to_key ("", "_call_next " + parms); } else { assign_to_key ("", "up"); assign_to_key ("", "down"); } beginning_of_line (); inq_screen_size (lines, cols); create_window (5, lines - 4, cols - 5, 3, " or  to move, to select, to exit"); attach_buffer (error_buf); refresh (); process (); if (errors_exist) { restore_position (); raise_anchor (); } delete_window (); /* ** Note that we set the current buffer back before doing the ** keyboard_pop: this ensures that the local keyboard (if any) ** is restored properly. */ set_buffer (old_buf_id); keyboard_pop (); set_buffer (error_buf); } /* ** At this point the cursor is pointing at the requested error ** message. If the user didn't cancel, we display the chosen message ** and move the cursor to the appropriate line. */ if (!_cancel_errorfix && action != JUST_PROBE) { string macro_to_call = error_extension + "_error_info"; if (!inq_macro (macro_to_call)) macro_to_call = "_error_info"; if ((windowed && errors_exist || more_errors) && (error_type = execute_macro (macro_to_call, buf_name, line, col, error_msg, curr_file_name, error_file_name))) { int error_file_buf = inq_buffer (curr_file_name); if (error_file_buf != old_buf_id) { set_buffer (old_buf_id); /* ** If the buffer is in the system, and it's being viewed in ** a window, we switch to it. Otherwise, we use edit_file to ** read it in (if necessary) and attach it to the current window. */ if (error_file_buf != 0 && inq_views (error_file_buf)) { int curr_window = inq_window (), window_buf_id; do { curr_window = next_window (curr_window); inq_window_info (curr_window, window_buf_id); } while (window_buf_id != error_file_buf); set_window (curr_window); old_buf_id = error_file_buf; } else if (edit_file (curr_file_name) > 0) old_buf_id = inq_buffer (); } else set_buffer (old_buf_id); if (error_type < 0) error_type = -error_type; if (error_type >= 3) goto_line (line); else goto_old_line (line); center_line (); if (col) move_abs (0, col); else { beginning_of_line (); next_char (strlen (trim (read ())) - strlen (ltrim (trim (read ())))); } if (error_type == ERROR || error_type == -ERROR) error (error_msg); else message (error_msg); } else if (windowed) message (trim (ltrim (read ()))); else { if (errors_exist) message ("No more errors."); delete_error_file (error_file_name, TRUE); } } else if (_cancel_errorfix) message (""); set_buffer (old_buf_id); returns (more_errors); } /* ** _call_next: ** ** This macro is used to call the _next_error function. It checks to ** see if there is a file extension specific function first. If not, it ** calls the generic function. */ int _call_next (string error_extension, string file_name) { string macro_name = "_next_error"; int ret_code, mark_type, col; if (mark_type = inq_marked ()) raise_anchor (); if (inq_macro (error_extension + macro_name)) macro_name = error_extension + macro_name; inq_position (NULL, col); restore_position (); ret_code = execute_macro (macro_name + " " + error_extension + " " + file_name); save_position (); move_abs (0, col); if (mark_type) drop_anchor (mark_type); returns (ret_code); } /* ** _call_prev: ** ** This macro is used to call the _prev_error function. It checks to ** see if there is a file extension specific function first. If not, it ** calls the generic function. */ int _call_prev (string error_extension, string file_name) { string macro_name = "_prev_error"; int ret_code, mark_type, col; if (mark_type = inq_marked ()) raise_anchor (); inq_position (NULL, col); restore_position (); save_position (); if (up ()) end_of_line (); if (inq_macro (error_extension + macro_name)) macro_name = error_extension + macro_name; restore_position (!(ret_code = execute_macro (macro_name + " " + error_extension + " " + file_name))); save_position (); move_abs (0, col); if (mark_type) drop_anchor (mark_type); returns (ret_code); } void _error_cancel () { _cancel_errorfix = 1; _exit (); } /* ** _next_error, _prev_error, _error_info: ** ** These routines are the default routines for locating error ** information. They can be enhanced with file extension specific ** error location functions that are written for a specific compiler. ** ** The _next_error and _prev_error macros move the cursor to the ** next and previous error message, respectively. They return TRUE ** if another error message is found, and FALSE otherwise. ** ** _error_info takes a given line in the error buffer (located by ** the _next and _previous error routines) and returns the information ** needed by BRIEF to locate the error message. It returns 0 if there ** is no error message, 1 if the error is a warning, and 2 if it's an ** error. It also returns the line and column in the file the error ** corresponds to, as well as the message that should be displayed at ** the bottom of the window. */ /* ** _next_error, _prev_error: ** ** Both of these routines are passed the extension of the file that ** has been compiled and the full file name. Although the generic ** routines don't make use of this information, extension-specific ** routines could. */ int _next_error (~string) { returns (search_fwd ("<[\\\\0-9a-zA-Z]+.[0-9a-zA-Z][~ \t\n]@[ \t,(:][ \t]@{line }@\\c[0-9]", 1, 0)); } int _prev_error (~string) { returns (search_back ("<[\\\\0-9a-zA-Z]+.[0-9a-zA-Z][~ \t\n]@[ \t,(:][ \t]@{line }@\\c[0-9]", 1, 0)); } /* ** _error_info: ** ** This routine parses a given error line and returns information ** about it. It is passed the following parameters: ** ** Parameter Description ** --------- ----------- ** 0 The name of the file the error was in. ** ** The following are write-only parameters passed back to the calling ** function: ** ** Parameter Description ** --------- ----------- ** 1 The line of the file the error was on. ** 2 The column of the file the error was on. ** If 0 is passed back, the first non-blank ** character is assumed. ** 3 The actual error message, minus any useless ** information. ** ** Finally, this last parameter is the buffer identifier of the source ** buffer. ** ** Parameter Description ** --------- ----------- ** 4 The full file name of the file the errors are ** in. This macro may reset this name if the ** errors are found in a different buffer. ** ** 5 The file that "owns" the errors -- could be ** different than parameter 4 if we're in an ** include file, and the error file is owned ** by a source file. ** ** _error_info returns an integer corresponding to the level of the ** error encountered. ** ** Error Code Meaning ** ---------- ------- ** 0 No error ** 1 Warning error in source file ** -1 Warning error in another file ** 2 "Normal" error in source file ** -2 "Normal" error in another file ** 3 Actual line (not original) error in source file. ** -3 Actual line (not original) error in another file. ** 4 Actual line (not original) warning in source file. ** -4 Actual line (not original) warning in another file. ** ** This information is used by the next_error driver to determine ** how to display the message. */ int _error_info (string error_file, int, int, string, string file_name, string base_name) { int length, position, error_level, error_line; string error_text; if (!(error_line = atoi (read (10)))) if (read (1) == "0") error_line = 1; else return (FALSE); save_position (); beginning_of_line (); error_text = trim (ltrim (read ())); restore_position (); error_level = WARNING + !index (lower (error_text), "warning"); /* ** If the error file name isn't found in the error text, we have to do ** more work... */ if (!index (lower (error_text), lower (error_file))) { position = search_string ("<*\\c[~ \t]+.[0-9a-zA-Z][~ \t\n]@[ \t,(:]", error_text, length); error_file = substr (error_text, position, length - 1); if (!index ("/\\", substr (error_file, 1, 1)) && substr (error_file, 2, 1) != ":") file_name = add_to_path (substr (base_name, 1, rindex (base_name, substr (base_name, 3, 1)) - 1), error_file); else file_name = error_file; error_level *= -1; } error_text = trim (ltrim (read ())); error_text = substr (error_text, search_string ("['`\"a-zA-Z]", error_text)); put_parm (1, error_line); put_parm (2, 0); put_parm (3, error_text); put_parm (4, file_name); returns (error_level); }