;
; ----------------------------------------------
; PC-TAGS(tm) RETRO Program for the BRIEF Editor
; ----------------------------------------------
;
; BRIEF Macros to Search a PC-TAGS-Generated Tagfile and Retrieve a
; Function Definition
;
; Copyright (C) 1989 by Moderne Software.  All rights reserved.
; Moderne Software, P.O. Box 3638, Santa Clara, CA 95055-3638
;
; Limited permission is given to registered PC-TAGS users to modify this
; file for their own personal use only.  This file may not be used for any 
; purpose other than in conjunction with the PC-TAGS software package.
;
; Entry Points
; ------------
;       _init               -- declare global variables
;       pctags_auto         -- uses word under cursor for search
;       pctags_prompt       -- prompts for a word to use in search
;
; Support macros
; --------------
;       delimiter           -- determines if character is a word delimiter
;       convert_slashes     -- convert slash characters in string to backslashes
;       get_tagfile         -- retrieve the next tagfile to search
;       parse_file_spec     -- break multiple tagfile specifications into
;                               individual directory/filename parts
;       dos_shell           -- execute RETROEXEC environment value thru DOS
;       file_already_loaded -- determine if file is already loaded into buffer
;       position_line_within_window -- position current line to third line in
;                               current window, if possible
;       pctags_main         -- search and retrieval workhorse code
;
; History
; -------
;  1 Oct 89 -- Version 1.00
;

;***************************************
; Global data

; Standard boolean values
#define TRUE                1
#define FALSE               0

; Return values
#define SUCCESSFUL          1
#define UNSUCCESSFUL        0

; Search case sensitivity settings
#define CASE_SENSITIVE      1
#define CASE_INSENSITIVE    0

; Values for pctags_first_call global variable
#define FIRST_CALL          1
#define NOT_FIRST_CALL      0

;***************************************
; Macro : _init
; Syntax: ( _init )
; Entry : None.
; Exit  : Global variables used by the PC-TAGS macros are declared.
; Notes : This macro is called by BRIEF automatically upon loading.

( macro _init
    (

; Case-sensitivity setting for tagname search.  You can change the
; CASE_SENSITIVE setting to CASE_INSENSITIVE if you want the search for a
; matching tagname to ignore case.
        ( int           pctags_tagname_case )
        ( global        pctags_tagname_case )
        ( = pctags_tagname_case CASE_SENSITIVE )

; Flag for first-call to get_tagfile ( 1=first call, 0=all subsequent calls)
        ( int           pctags_first_call )

; Storage for environment tagfile specifications and current tagfile directory
        ( string        pctags_env_tagfiles )
        ( string        pctags_env_path )


        ; Make all declared variables global to this file
        ( global pctags_first_call )
        ( global pctags_env_tagfiles )
        ( global pctags_env_path )

    )
)   ; macro _init

;***************************************
; Macro : pctags_auto
; Syntax: ( pctags_auto )
; Entry : None.
; Exit  ; Non-zero if function under cursor is found, file containing function
;           definition is loaded and cursor is placed at beginning of
;           definition.
;         Zero if specified function is not found.
; Description :
;         Function under cursor is extracted from line and used to search thru
;           the tagfile(s).  If a match is found, the file containing the
;           function's definition is loaded and the cursor is placed at the
;           start of the definition.

( macro pctags_auto
    (   ; 1

        ( int       first_char
                    last_char
                    length
                    row
        )
        ( string    char
                    current_word
        )


        ; Read cursor character
        ( = char (read 1) )

        ; Verify that cursor is under a valid word
        ( if (delimiter char)
            (   ; 2
                ( error "Invalid word" )
                ( beep )
                ( return UNSUCCESSFUL )
            )   ; 2
        )

        ; Save current position
        ( save_position )

        ; Find the last character in this word
        ; Move to the next character
        ( if (! (next_char))
            (   ; 2a
                ; At end of file, current position is last char in word
                ( inq_position row last_char )
            )   ; 2a
        ; else
            (   ; 2b
                ; Advance through word until delimiter or eof
                ( while (! (delimiter (read 1)))
                    (   ; 3
                        ; Move to next char
                        ( if (! (next_char))
                            (   ; 4
                                ; Reached end of file, current position is last
                                ; character in word
                                ( inq_position row last_char )
                                ( break )
                            )   ; 4
                        )
                    )   ; 3
                )

                ; If last_char != 0, reached eof, else reached delimiter
                ( if (== last_char 0)
                    (   ; 3
                        ; Previous character is the last char in the word
                        ( prev_char )
                        ( inq_position row last_char )
                    )   ; 3
                )
            )   ; 2b
        )

        ; Restore position to cursor and save again
        ( restore_position )
        ( save_position )

        ; Find first character of word
        ; Work your way backwards
        ( if (! (prev_char))
            (   ; 2a
                ; At start of file, current position is first char in word
                ( inq_position row first_char )    ; Should be 1
            )   ; 2a
        ; else
            (   ; 2b
                ; Backtrack thru word until delimiter or start of file
                ( while (! (delimiter (read 1)))
                    (   ; 3
                        ; Move to previous character
                        ( if (! (prev_char))
                            (   ; 4
                                ; Reached start of file, current position is
                                ; first char in word
                                ( inq_position row first_char )    ; Should be 1
                                ( break )
                            )   ; 4
                        )
                    )   ; 3
                )

                ; If first_char != 0, reached beginning of file, else reached
                ; delimiter
                ( if (== first_char 0)
                    (   ; 3
                        ; Next character is first char in word
                        ( next_char )
                        ( inq_position row first_char )
                    )   ; 3
                )
            )   ; 2b
        )

        ; We now have index to first and last chars in word and current position
        ; is at the start of the word.
        ; Calculate length of word
        ( = length (+ (- last_char first_char) 1))  ; length = last - first + 1

        ; Read word
        ( = current_word (read length) )

        ; Append a blank to the word
        ( = current_word (+ current_word " ") )

        ; Restore original position in file
        ( restore_position )

        ; Perform pctags operation and return SUCCESSFUL or UNSUCCESSFUL
        ( return (pctags_main current_word) )

    )   ; 1
) ; macro pctags_auto

;***************************************
; Macro : pctags_prompt
; Syntax: ( pctags_prompt {function_name} )
; Entry : Function name for which to search and retrieve.
; Exit  : Non-zero if specified function is found, file containing function
;           definition is loaded and cursor is placed at beginning of
;           definition.
;         Zero if specified function is not found.
; Description :
;       User is prompted for the name of a function.  The macro loads the file
;       containing the function definition and places the cursor on the
;       definition's line.

( macro pctags_prompt
    (

        ( string    function_name )


        ; Prompt the user for a function name and save in function_name variable
        ( if (! (get_parm 0 function_name "Enter tagname: "))
            (
                ; User pressed <Esc> key
                ( message "Command aborted." )
                ( return UNSUCCESSFUL )
            )
        )

        ; If empty string entered, abort
        ( if (== (strlen function_name) 0)
            (
                ; Simply exit
                ( return UNSUCCESSFUL );
            )
        )

        ; Append a blank to the function name
        ( = function_name (+ function_name " ") )

        ; Perform pctags operation and return SUCCESSFUL or UNSUCCESSFUL value
        ( return (pctags_main function_name) )

    )
)  ; macro pctags_prompt

;***************************************
; Macro : delimiter
; Syntax: ( delimiter char )
; Entry : char = character to check as a delimiter
; Exit  : Non-zero if character is a delimiter, else zero.
; Note  : Character is considered a delimiter if it is not an alphanumeric
;           or underscore or dot (for Turbo Pascal 5.5 only) character.

( macro delimiter
    (

; Return values
#define DELIMITER       1
#define NOT_DELIMITER   0

; ASCII values
#define DOT             0x2E
#define ASCII_0         0x30
#define ASCII_9         0x39
#define ASCII_A         0x41
#define ASCII_Z         0x5A
#define UNDERSCORE      0x5F

; Miscellaneous
#define GET_ASCII       0

        ( int       ascii_value )
        ( string    char )


        ; Get the character parameter
        ( get_parm 0 char )

        ; Convert char to uppercase
        ( = char (upper char) )

        ; Convert char to its ASCII value
        ( = ascii_value (atoi char GET_ASCII) )

        ; Check for alpha char between 'A' and 'Z', inclusive
        ( if (&& (>= ascii_value ASCII_A) (<= ascii_value ASCII_Z))
            ( return NOT_DELIMITER )
        )

        ; Check for numeric character between '1' and '9', inclusive
        ( if (&& (>= ascii_value ASCII_0) (<= ascii_value ASCII_9))
            ( return NOT_DELIMITER )
        )

        ; Check for underscore char
        ( if (== ascii_value UNDERSCORE)
            ; Yes, it's an underscore
            ( return NOT_DELIMITER )
        )

        ; Check for dot character (for Turbo Pascal 5.5 only)
        ( if (== ascii_value DOT)
            ( return NOT_DELIMITER )
        )

        ; Else, it must be a delimiter
        ( return DELIMITER )

    )
)   ; macro delimiter

;***************************************
; Macro : convert_slashes
; Syntax: ( convert_slashes str )
; Entry : str is a string which may contain slash characters
; Exit  : String with all slash characters converted to backslashes

( macro convert_slashes
    (

        ( int       pos )
        ( string    str )


        ; Get argument
        ( get_parm 0 str )

        ; Search string for a slash
        ( = pos (index str "/") )

        ; While there are still slashes in string, convert them
        ( while pos
            (
                ;Convert slash to backslash
                ( = str (+ (+ (substr str 1 (- pos 1)) "\\")
                           (substr str (+ pos 1))) )

                ; Check for more slashes
                ( = pos (index str "/") )
            )
        )

        ; Return converted string
        ( returns str )

    )
)   ; macro convert_slashes

;***************************************
; Macro : get_tagfile
; Syntax: ( get_tagfile )
; Entry : None.
; Exit  : Return tagfile name or empty string if none left.
;
; Use of Global Variables:
;       pctags_first_call       -- access and assign
;       pctags_env_tagfiles     -- access only
;       pctags_env_path         -- access only

( macro get_tagfile
    (   ; 1

        ( string    file_spec )


        ; If this is the first call to this macro in this pctags invocation,
        ; get the RETRO environment variable setting, if any
        ( if pctags_first_call
            (   ; 2
                ; Go thru this code once per pctags invocation
                ( = pctags_first_call NOT_FIRST_CALL )

                ; Get RETRO environment variable
                ( = file_spec (inq_environment "RETRO") )

                ; Convert any slashes to backslashes and all chars to lowercase
                ( = file_spec (lower (convert_slashes file_spec)) )

                ; If no such variable, use the default
                ( if (== file_spec "")
                    ( = file_spec "*.tag" )
                )

                ; Parse any multiple file specs into single specs and set
                ; the file pattern to the first parsed file_spec (may contain
                ; wildcards).
                ( parse_file_spec file_spec )
            )   ; 2
        )

        ; Get a tagfile name.  If file_spec is empty, check for more tagfile
        ; locations in environment variable setting (pctags_env_tagfiles).
        ( while (! (find_file file_spec))
            (   ; 2
                ; Any more file specs in pctags_env_tagfiles?
                ( if (== pctags_env_tagfiles "")
                    ; Nope, this is the end of the road
                    ( break )
                ; else
                    ; Yes, parse out the next location (sets
                    ; pctags_env_path) and set the file pattern to the
                    ; extracted filespec.
                    ( parse_file_spec pctags_env_tagfiles )
                )
            )   ; 2
        )

        ; Convert filename to lowercase and concatenate any drive:path
        ( if (!= file_spec "")
            (   ; 2
                ; Filename found, convert to lowercase
                ( = file_spec (lower file_spec) )

                ; Insert drive:path
                ( = file_spec (+ pctags_env_path file_spec) )
            )   ; 2
        )

        ; Return tagfile name (or empty if none left)
        ( returns file_spec )

    )   ; 1
)   ; macro get_tagfile

;***************************************
; Macro : parse_file_spec
; Syntax: ( parse_file_spec file_spec )
; Entry : file_spec = file specification to parse.  May contain wildcards.  May
;           also contain multiple specs, each separated by a semicolon.  For
;           example, "c:\*.tag;d:\brief\tagfiles\*.tag;c:\pctags.tag".
; Exit  : Any drive:path in the first file spec is stored in pctags_env_path.
;         Any subsequent file specs are stored in pctags_env_tagfiles.
;         The filename portion of the spec is returned (may contain wildcards).
;         The search pattern is set for future (find_file) calls.
;
; Use of Global Variables:
;       pctags_env_tagfiles     -- assigns
;       pctags_env_path         -- assigns

( macro parse_file_spec
    (   ; 1

        ( int       last_path_separator
                    last_slash
                    semicolon
        )
        ( string    file_spec )


        ; Get parameter
        ( get_parm 0 file_spec )

        ; Does file_spec contain multiple specs?
        ( if (! (= semicolon (index file_spec ";")))
            ; No, file_spec contains one drive:path.  Clear pctags_env_tagfiles.
            ( = pctags_env_tagfiles "" )
        ; else
            (   ; 2
                ; Yes, store subsequent specs in pctags_env_tagfiles
                ( = pctags_env_tagfiles (substr file_spec (+ semicolon 1)) )

                ; Place first spec only in file_spec
                ( = file_spec (substr file_spec 1 (- semicolon 1)) )
            )   ; 2
        )

        ; Set file search pattern for future calls to (find_file)
        ( file_pattern file_spec )

        ; Does file_spec contain a drive or path spec?
        ; Find index to last path separator
        ( = last_path_separator (rindex file_spec "\\") )

        ; If no path was specified, check for a drive
        ( if (== last_path_separator 0)
            ( = last_path_separator (rindex file_spec ":") )
        )

        ; If drive and/or path were specified, extract from file_spec and
        ; store in pctags_env_path.
        ( if (! last_path_separator)
            ; No drive:path specified, clear pctags_env_path.
            ( = pctags_env_path "" )
        ; else
            (
                ; Extract drive:path, save in pctags_env_path
                ( = pctags_env_path (substr file_spec 1 last_path_separator) )
            )
        )

    )   ; 1
)   ; macro parse_file_spec

;***************************************
; Macro : dos_shell
; Syntax: ( dos_shell command_line file_spec )
; Entry : Command_line is the DOS command line to have the shell execute.  It
;           may contain special parameters documented below.  Case of the
;           special parameters is significant.  It may also contain the DOS
;           standard output redirection characters > or >>, but the command
;           must then be bracketed with double quotes.  The quotes will be
;           removed before sending the command line to DOS.
; Exit  : The command will have been executed.  No return value is sent back.
; Special command_line parameters:
;           %s      - entire file specification of desired file (file_spec parm)
;           %d      - drive of desired file
;           %p      - path (no drive) of desired file (no terminating (back)slash)
;           %f      - filename of desired file
;           %c      - current directory (in drive:path format suitable for CD'ing)
;           %u      - user-defined substitution string
;           %%      - single %

( macro dos_shell
    (   ; 1

        ( int       last_index
                    parm_index
        )
        ( string    command_line
                    current_dir
                    drive
                    filename
                    file_spec
                    path
                    sub_str
        )


        ; Get parameters
        ( get_parm 0 command_line )
        ( get_parm 1 file_spec )

        ; Remove any leading and trailing whitespace from command line
        ( = command_line (ltrim (trim command_line)) )

        ; If command_line is bracketed by double quotes, remove them
        ( if (&& (== (substr command_line 1 1) "\"")
                 (== (substr command_line (strlen command_line)) "\""))
            ; Remove double quotes
            ( = command_line (substr command_line 2 (- (strlen command_line) 2)) )
        )

        ; Create special parameter strings
        ; Get drive:path
        ( = path (substr file_spec 1 (rindex file_spec (substr file_spec 3 1))) )

        ; If not in root, truncate last "\"
        ( if (> (strlen path) 3)
            ; Truncate last path separator
            ( = path (substr path 1 (- (strlen path) 1)))
        )

        ; Break drive:path into separate strings
        ( = drive (substr path 1 2) )       ; %d special parm
        ( = path (substr path 3) )          ; %p special parm

        ; Get filename only (%f special parm)
        ( = filename (substr file_spec (+ (rindex file_spec (substr file_spec 3 1)) 1)) )

        ; Get current directory
        ( getwd "" current_dir )            ; %c special parm

        ; Parse command line, replacing special parameters
        ( = parm_index 1 )
        ( while (&& (= parm_index (+ (- parm_index 1) (index (substr command_line parm_index) "%")))
                    (!= parm_index last_index))
            (   ; 2
                ; Save current parm_index
                ( = last_index parm_index )

                ; Get substitution string
                ( switch (substr command_line (+ parm_index 1) 1)
                    "s"     ; Entire file specification of desired file
                            ( = sub_str file_spec )
                    "d"     ; Drive of desired file
                            ( = sub_str drive )
                    "p"     ; Path (no drive) of desired file
                            ( = sub_str path )
                    "f"     ; Filename of desired file
                            ( = sub_str filename )
                    "c"     ; Current directory
                            ( = sub_str current_dir )
                    "u"     ; User-defined
                            ( if (! (get_parm NULL sub_str "Enter sub string: "))
                                ; User pressed <Esc> key, use empty string
                                ( = sub_str "" )
                            )
                    "%"     ; Single percent sign
                            ( = sub_str "%" )
                    ; default
                    NULL    ; Replace with empty string
                            ( = sub_str "" )
                )

                ; Replace special parameter with substitution string
                ( = command_line (+ (substr command_line 1 (- parm_index 1))
                         (+ sub_str (substr command_line (+ parm_index 2)))) )

                ; Advance past first char of replacement (in case replacement
                ; was a single "%")
                ( ++ parm_index )

            )   ; 2
        )

        ; If ">&" found in command_line, display message (Brief will not alter
        ; the screen when redirection char (> or >>) are followed by ampersand).
        ( if (index command_line ">&")
            ; Let user know what's going on
            ( message "Executing RETROEXEC command" )
        )

        ; Send command line to DOS for execution
        ( dos command_line )

    )   ; 1
)   ; macro dos_shell

;***************************************
; Macro : file_already_loaded
; Syntax: ( file_already_loaded filename )
; Entry : filename is the complete name (using backslashes as path separators)
;           of a file
; Exit  : TRUE if filename is already loaded into a BRIEF buffer, else FALSE

( macro file_already_loaded
    (

        ( int       org_buff_id
                    already_loaded
                    cycle
        )

        ( string    filename
                    curr_name
        )


        ; Get argument
        ( get_parm 0 filename )

        ; Save current buffer id
        ( = org_buff_id (inq_buffer) )

        ; Set default return value
        ( = already_loaded FALSE )

        ; Cycle thru all buffers, looking for our file
        ( = cycle TRUE )
        ( while cycle
            (
                ; Get the complete name of the file
                ; associated with the current buffer
                ( inq_names curr_name )

                ; Convert any slash path separators to backslashes and convert
                ; chars to lowercase
                ( = curr_name (lower (convert_slashes curr_name)) )

                ; Compare current filename to the filename we're looking for
                ( if (== curr_name filename)
                    (
                        ; Set return code to indicate the file is already loaded
                        ( = already_loaded TRUE )
                    )
                )

                ; Advance to the next buffer
                ( set_buffer (next_buffer) )

                ; If we've cycled back around to the
                ; starting buffer, exit while-loop
                ( if (== org_buff_id (inq_buffer))
                    (
                        ( = cycle FALSE )
                    )
                )
            )
        )

        ; Return already_loaded flag
        ( return already_loaded )

    )
)   ; macro file_already_loaded

;***************************************
; Macro : position_line_within_window
; Syntax: ( position_line_within_window )
; Entry : None.
; Exit  : If current window is at least five lines high, the current line
;           is positioned on the third line in the window.  If the window
;           is less than five lines high, no change.

( macro position_line_within_window
    (   ; 1

        ( int       row )

        ; If possible, position current line on third in window.  Window
        ; must be at least five lines high to do so.
        ; Get the size of the window
        ( inq_window_size row )
        ( if (>= row 5)
            (   ; 2
                ; Current line must be at least third line in file
                ( inq_position row )
                ( if (>= row 3)
                    (   ; 3
                        ; Display current line in third line of window
                        ( set_top_left (- row 2) )
                    )   ; 3
                )
            )   ; 2
        )

    )   ; 1
)   ; macro position_line_within_window

;***************************************
; Macro : pctags_main
; Syntax: ( pctags_main function_name )
; Entry : function_name = function name to locate and retrieve the file in
;           which it is declared.
; Exit  : Non-zero value if function definition is found, file containing the
;           definition is loaded and cursor is placed on the definition's
;           line.
;         Else return zero.
; Description :
;         RETRO operation is performed.  This requires the following steps:
;               -- Cycle through all required tagfiles.  Default = *.tag or
;                       all tagfiles specified in RETRO environment variable
;                       which may contain multiple file specs, each with
;                       wildcards, separated by semicolons.  For example,
;                       SET RETRO=c:\brief\tagfiles\*.tag;c:\prog\*.tag
;               -- Search each tagfile for the tagname.
;               -- If not found in any tagfile, display message and quit.
;               -- If found, get name of file and definition line from tagfile.
;               -- Close tagfile.
;               -- Load file for editing.
;               -- If file cannot be found and environment varible RETROEXEC is
;                       defined, execute the command line value assigned to it.
;                       Try to load the file again.
;               -- Search file for definition line.
;               -- Place definition line on third line in window (if possible).
;               -- If any of these operations fail, display appropriate error
;                       message and quit.
;
; Use of Global Variables:
;       pctags_first_call       -- assign
;       pctags_tagname_case     -- access

( macro pctags_main
    (   ; 1

#define SYSTEM_BUFF         1
#define NONSYSTEM_BUFF      0

#define PAUSE               1
#define NO_PAUSE            0

#define NO_RE               0
#define REMOVE              0

        ( int       buff_id
                    column
                    continue_search
                    decl_start
                    exec_env
                    file_already_in_buffer
                    file_start
                    found_match
                    line_number
                    org_buff_id
                    row
                    special_chars
        )
        ( string    decl_line
                    env_pctagsexec
                    file_spec
                    function_name
                    line
                    tagfile_name
        )


        ; Get function name we're looking for (passed as a parameter)
        ( get_parm 0 function_name )

        ; Save current context
        ( = org_buff_id (inq_buffer) )          ; Save current buffer id

        ; Cycle thru all RETRO (or default) tagfiles until done or match found
        ( = found_match FALSE )
        ( = pctags_first_call FIRST_CALL )      ; Global flag for (get_tagfile)
        ( while (&& (! found_match) (!= (= tagfile_name (get_tagfile)) ""))
            (   ; 2
                ; Create tagfile buffer and make it current
;( message "main: tagfile=%s" tagfile_name )
;( while (!= 32 (& (read_char) 0xff)) )
                ( = buff_id (create_buffer "pctags" tagfile_name SYSTEM_BUFF) )
                ( set_buffer buff_id )

                ; Set up a loop to search through the tagfile.  Multiple
                ; searches may be required because a match COULD be found on a
                ; string in the tagfile other than the function-name portion of
                ; a line.
                ; Search tagfile for function name
                ( message "Searching %s" tagfile_name )
                ( = continue_search TRUE )
                ( while continue_search
                    (   ; 3
                        ; Search tagfile for function name
                        ( if (<= (search_fwd function_name NO_RE pctags_tagname_case) 0)
                            (   ; 4a
                                ; Did not find function name in this tagfile.
                                ; Set current buffer back to original and remove
                                ; tagfile from memory.
                                ( set_buffer org_buff_id )
                                ( delete_buffer buff_id )

                                ; Stop searching this tagfile, cycle to next one
                                ( = continue_search FALSE )
                            )   ; 4a
                        ; else
                            (   ; 4b
                                ; Found a match.
                                ; The match MUST start in the first column.  If
                                ; it doesn't then a match was found somewhere
                                ; other than in the function-name portion of the
                                ; line.
                                ( inq_position row column )
                                ( if (!= column 1)
                                    (   ; 5a
                                        ; Advance buffer position so we don't
                                        ; keep finding the same match.
                                        ( next_char )
                                        ; Continue searching tagfile
                                    )   ; 5a
                                ; else
                                    (   ; 5b
                                        ; Found a match!
                                        ; Stop searching this and all other
                                        ; tagfiles
                                        ( = continue_search FALSE ) ; This tagfile
                                        ( = found_match TRUE )      ; All tagfiles

                                        ; Read matching line, including newline
                                        ( = line (read) )

                                        ; Done with tagfile.  Reset buffer id
                                        ; and close it.
                                        ( set_buffer org_buff_id )
                                        ( delete_buffer buff_id )

                                        ; Format of line:
                                        ; Column 1: Function name searching for (variable length)
                                        ; Single space terminator
                                        ; Optional RETRO commands (! and/or #)
                                        ; Complete file specification of file containing
                                        ;   definition of preceding function (variable length)
                                        ; Single space terminator
                                        ; Caret (^) character signifying the start of the
                                        ;   definition line
                                        ; Entire line from preceding file that defines the
                                        ;   column-1 function (variable length)
                                        ; End of line is the last character in the definition
                                        ;   line (i.e. newline)
                                        ; As an example:
                                        ; "function c:\dir\file.c ^int function( arg1, arg2)\n"

                                        ; Determine starting indices of file spec
                                        ; and definition line
                                        ( = file_start (+ (index line " ") 1) )
                                        ( = decl_start (+ (index (substr line file_start) " ") file_start) )

                                        ; Extract file spec and decl line
                                        ( = file_spec (substr line file_start (- (- decl_start file_start) 1)) )
                                        ; Note: Skip leading '^' of decl line
                                        ( = decl_line (substr line (++ decl_start)) )

                                        ; Check file_spec for special leading characters
                                        ( = continue_search TRUE )
                                        ( = exec_env FALSE )
                                        ( = line_number FALSE )
                                        ( = special_chars 1 )   ; Base one
                                        ( while continue_search
                                            (   ; 6
                                                ( switch (substr file_spec special_chars 1)
                                                    "!" (   ; If file_spec not found,
                                                            ; execute RETROEXEC environment
                                                            ; variable command line.  This is
                                                            ; usually used to extract the file
                                                            ; from a source-control library.
                                                            ( = exec_env TRUE )

                                                            ; Increment index
                                                            ( ++ special_chars )
                                                        )

                                                    "#" (   ; Line number of definition is
                                                            ; stored in tagfile instead of
                                                            ; line contents.
                                                            ( = line_number TRUE )
                                                            ( ++ special_chars )
                                                        )

                                                    ; default
                                                    NULL    ; Stop parsing special chars
                                                        ( = continue_search FALSE )
                                                )
                                            )   ; 6
                                        )

                                        ; Remove all leading special chars from file_spec
                                        ( = file_spec (substr file_spec special_chars) )

                                        ; Verify that file_spec exists.
                                        ( if (! (exist file_spec))
                                            (   ; 6
                                                ; If exec_env active, get RETROEXEC value
                                                ( if exec_env
                                                    (   ; 7
                                                        ( if (!= (= env_pctagsexec (inq_environment "RETROEXEC")) "")
                                                            ; Execute the variable's value
                                                            ( dos_shell env_pctagsexec file_spec )
                                                        )
                                                    )   ; 7
                                                )

                                                ; Check for file's existence now
                                                ( if (! (exist file_spec))
                                                    (   ; 7
                                                        ( error "%s does not exist.  Update tagfile."
                                                                file_spec )
                                                        ( beep )
                                                        ( return UNSUCCESSFUL )
                                                    )   ; 7
                                                )
                                            )   ; 6
                                        )

                                        ; Determine if file is already loaded
                                        ( = file_already_in_buffer (file_already_loaded file_spec) )

                                        ; Load the file containing the function definition.
                                        ( edit_file file_spec )

                                        ; Get Brief's file-spec format
                                        ( inq_names file_spec )

                                        ; Save current position in file in case we have
                                        ; to return to it, force to top of file
                                        ( save_position )
                                        ( top_of_buffer )

                                        ; Searching for line contents or line number?
                                        ( if (== line_number TRUE)
                                            (   ; 6a
                                                ; Goto specified line number in source file
                                                ( goto_line (atoi decl_line) )

                                                ; Position line
                                                ( position_line_within_window )

                                                ; Display filename
                                                ( message "File: %s" file_spec )

                                                ; Discard saved file position from
                                                ; stack (do not restore).
                                                ( restore_position REMOVE )

                                            )   ; 6a
                                        ; else
                                            (   ; 6b
                                                ; Search for definition line.
                                                ( while TRUE
                                                    (   ; 7
                                                        ( if (> (search_fwd decl_line
                                                                 NO_RE      CASE_SENSITIVE) 0)
                                                            (   ; 8a
                                                                ; Found a match
                                                                ; Match must be at start of line
                                                                ; to be valid.
                                                                ( inq_position row column )
                                                                ( if (!= column 1)
                                                                    (   ; 9
                                                                        ; Invalid match, keep searching
                                                                        ; Advance buffer position so we
                                                                        ; won't keep finding the same
                                                                        ; match.
                                                                        ( next_char )
                                                                        ( continue )    ; Back up to while-loop
                                                                    )   ; 9
                                                                )

                                                                ; Found function definition!
                                                                ( position_line_within_window )

                                                                ; Display filename
                                                                ( message "File: %s" file_spec )

                                                                ; Discard saved file position
                                                                ; from stack (do not restore).
                                                                ( restore_position REMOVE )

                                                                ; Break out of while-loop
                                                                ( break )

                                                            )   ; 8a
                                                        ; else
                                                            (   ; 8b
                                                                ; Definition line not found in file.
                                                                ; Note: Go through this process of
                                                                ; constructing the error message to
                                                                ; maintain compatibility with previous
                                                                ; versions of BRIEF that did not
                                                                ; accept more than one string
                                                                ; parameter.
                                                                ; Construct output string.
                                                                ( sprintf line "Update %s with " tagfile_name )
                                                                ( = line (+ line file_spec) )

                                                                ; Display string
                                                                ( error line )
                                                                ( beep )

                                                                ; Recover by deleting this file
                                                                ; and returning to the original
                                                                ; context.
                                                                ; Get the decl-file's buffer id
                                                                ( = buff_id (inq_buffer) )

                                                                ; If the decl-file was in the
                                                                ; original buffer, do not re-
                                                                ; attach as doing so will screw
                                                                ; up the top-left corner of the
                                                                ; window.
                                                                ( if (!= buff_id org_buff_id)
                                                                    (
                                                                        ; Attach the original
                                                                        ; buffer to the current
                                                                        ; window.
                                                                        ( attach_buffer org_buff_id )

                                                                        ; Set original buffer to
                                                                        ; current buffer.
                                                                        ( set_buffer org_buff_id )
                                                                    )
                                                                )

                                                                ; If file had to be loaded from
                                                                ; disk, delete it.
                                                                ( if (! file_already_in_buffer)
                                                                    (
                                                                        ( delete_buffer buff_id )
                                                                    )
                                                                )

                                                                ; Restore original position in file
                                                                ( restore_position )
                                                                ; Return error code
                                                                ( return UNSUCCESSFUL)
                                                            )   ; 8b
                                                        )
                                                    )   ; 7
                                                )
                                            )   ; 6b
                                        )
                                    )   ; 5b
                                )
                            )   ; 4b
                        )
                    )   ; 3
                )
            )   ; 2
        )

        ; If match not found in any tagfile, that's not good...
        ( if found_match
            ; Congratulations
            ( return SUCCESSFUL )
        ; else
            (   ; 2
                ; Display message
                ; Note: function_name ends with a single blank char
                ( message "%snot found." function_name )
                ( return UNSUCCESSFUL )
            )   ; 2
        )

    )   ;1
)   ; macro pctags_main

;***************************************
